Unntakshåndteringssystem

I sammenheng med funksjonelle og tvingende programmeringsspråk brukes et unntaksadministrasjonssystem eller EMS for å håndtere eksepsjonelle forhold under programutførelse. Når et unntak oppstår, blir normal programutførelse avbrutt og unntaket behandles.

De vanligste feilene / unntakene er sannsynligvis uautorisert tilgang til et minneområde ( pekermanipulasjonsfeil ) og deling med null (tilfellet hvor deleren er null forventes ikke).

Renter

Ethvert program som kjører kan være gjenstand for feil der det er mulig å oppdage og reparere strategier. Disse feilene er ikke feil, men spesielle forhold (eller eksepsjonelle forhold eller unntak) i løpet av en normal del av et program.

For eksempel er fraværet av en nyttig fil ikke en feil i programmet; på den annen side ville det ikke føre til en å ikke styre fraværet.

Behandlingen av eksepsjonelle situasjoner avslører to behov:

I programmeringsspråk uten SGE er det ikke noe verktøy for å skille den normale kjøringen og den eksepsjonelle kjøringen av programmet. En algoritme, hvis normale utførelse uttrykkes på en enkel og elegant måte, kan bli uleselig (og derfor vanskelig å vedlikeholde) når den er "belagt" av en logikk for å håndtere eksepsjonelle situasjoner; Å ha syntaks på et programmeringsspråk for å skille normal utførelse fra utførelse i en eksepsjonell sammenheng kan være nyttig.

Behandlingen av en eksepsjonell situasjon kan kreve å gå tilbake "til fortiden" av gjennomføringen av programmet, det vil si brått gå tilbake i kjeden av samtaler for å avbryte en feiloperasjon, eller til og med endre verdiene til visse operasjoner variabler, og fortsett deretter kjøringen av programmet litt før feilstedet. Derfor er behovet for å knytte spesielle operatører til den spesielle syntaksen for å utføre hopp og modifikasjoner av variabler på vilkårlige punkter i samtalekjeden.

Generelle prinsipper

Typer feil

Følgende unntak kan forekomme i praktisk talt ethvert program:

I noen objektorienterte språk må typen unntak være en klasse . Et forhåndsdefinert og utvidbart hierarki av unntakstyper, tilsvarende den type feil de representerer, er gitt. Andre språk, som C ++ , tillater også primitive typer.

Unntakshåndterer

En unntakshåndterer etablerer et sett med feilhåndteringsrutiner, definert av programmereren, på en blokk (i en funksjon eller en metode for programmet); disse rutinene aktiveres så lenge den beskyttede blokken utføres.

Begrepet utførelse av den beskyttede blokken inkluderer hele kjeden av samtaler (av funksjoner, prosedyrer eller metoder) fra denne blokken: vi sier at unntakshåndtererne er aktive i blokkenes dynamiske omfang .

Signalering av et unntak kan være automatisk, hvis det tilsvarer et unntak definert i programmeringsspråket eller et medfølgende bibliotek, eller ellers utløst av programmereren ved bruk av en primitiv signalering. Det er generelt mulig å lage nye typer unntak og planlegge signalering.

Unntakshåndtereren kan fullføres med et sett på nytt , som er rutiner som tillater modifisering av leksikale miljøer mellom rapporteringsstedet og etableringsstedet for unntakshåndtererne. En omstart tillater at en unntakshåndterer velger å reparere og starte en beregning på nytt i stedet for å forlate den helt. En omstart er også aktiv i det dynamiske omfanget av blokken som den er definert på.

Rapporterer en feil

Når det oppdages en feiltilstand (av en primitiv signalering, en prosessorfelle, operativsystemet eller programutførelsesmiljøet), sies det at det blir signalisert: en behandlingsblokk d 'feil (en behandler ) blir søkt i listen over aktive ledere. Gjennomføringen av programmet henvises til behandlingsblokken, som utfører korrigerende handlinger og bestemmer om beregningen der feilen ble signalisert er fullført eller gjenopptatt (hvis mulig, det vil si i nærvær av omstart).

Det kan være at ingen behandler er levert av programmereren, i hvilket tilfelle en standardhandler, hvis oppførsel er forhåndsdefinert, blir valgt.

Feil reparasjon, gjenoppretting

I en behandlingsblokk har programmereren to alternativer:

Operatører

I Python, for eksempel, innebærer flagging å ødelegge samtalestakken til den første tilgjengelige behandlingsblokken. I dette tilfellet er kast (eller heving) den eneste primitive signaliseringen, og reparasjon og gjenoppretting er ikke mulig.

I programmeringsspråk

GRUNNLEGGENDE

Grunnleggende språk inkluderer et feilbehandlingssystem av typen 'feilfelle', men som likevel er en unntaksadministrasjon

function toto(x as double) as double DIM f as double f = 1.0 REM on affecte la valeur 1, jusqu'ici tout va bien ON LOCAL ERROR GOTO MonException REM [try] on débute un bloc protégé f = 1.0 / 0.0 REM provoque une division par zéro, ou alors f vaut +infini f = sin(f) REM sinus de +infini est indéfini sinon toto = f REM Retourne le résultat EXIT FUNCTION REM Indispensable, sinon on entre dans la zone de traitement d'exception REM [except] =============== MonException: REM on gère les exceptions REM Autre cas: Correction puis poursuite f = 1.0 RESUME NEXT REM Passe à la ligne suivante (f = sin(f) toto = f end function;

Delphi / Lazarus

Eksempel i Delphi eller Lazarus  :

procedure TForm1.Button1Click(Sender: TObject); var f: Real; // soit f un nombre réel begin f := 1; // on affecte la valeur 1, jusqu'ici tout va bien try // on débute un bloc protégé f := 1/0; // provoque une division par zéro, ou alors f vaut +infini f := sin(f); // sinus de +infini est indéfini sinon except // on gère les exceptions on e: Exception do // on appelle e l'exception qui vient d'arriver Application.MessageBox( PChar('Message : '+e.Message), 'Exception', 0); end; // fin du bloc protégé Application.MessageBox( PChar('Valeur de f : '+FloatToStr(f)), 'Resultat', 0); end;

Java

Java tilbyr en terminal EMS, derfor uten reparasjon eller omarbeiding.

Eksempel på behandling av en divisjon med null:

public class FooBar { FooBar () { } int foo (String b) { int resultat; try { resultat = bar (b); } catch (Exception e) { System.out.println ("erreur pendant l'exécution de bar : " + e.toString ()); resultat = 666; } return resultat; } int bar (String s) { System.out.println ("tiens, un " + s); System.out.println ("faisons quelque chose de mal..."); int a = 42 / 0; // <- quelque chose de mal a = a + 7; return a; } } Spesielle egenskaper

I CLU [Lyskov-Snyder 79] og Java skilles det mellom:

  • unntakene deklarert i signaturen til en metode (CheckedException i Java); for eksempel
void foo () throws ThisExceptionType { ... },
  • kjøretids unntak (RuntimeException i Java), som tilsvarer hendelser som ikke kan lokaliseres leksikalt på kompileringstidspunkt (asynkrone unntak), eller som kan oppstå når som helst under programutførelse, for eksempel problemer med minnetildeling.

De unntakene sjekket prøver å løse et problem kontrakt . Grensesnittet til en modul (i et klassebibliotek) representerer en kontrakt mellom forfatteren av modulen og dens bruker: argumentet er at en slik kontrakt ikke skal ignorere unntakene som sannsynligvis vil spre seg utenfor modulens grenser.

Ved å spesifisere unntak i metodesignaturer introduserer vi imidlertid et problem. Faktisk må klientmetodene velge alternativet:

  • installere en GE for unntak for moduler;
  • ellers erklære disse unntakene etter tur.

Metoder som bruker avmerkede unntak forurenser kundene sine med plikten til å dekorere signaturen, hvis de ikke installerer en GE for disse unntakene. Denne forurensningen forråder delvis kontrakten om uavhengighet mellom varslingsstedet for et unntak og stedet for behandlingen, ved å avsløre alle disse erklæringer om grensesnitt i samtaleveien; kort fortalt, de bringer oss tilbake til ulempene med programmeringsspråk uten SGE (overføring av unntaket med en spesiell returverdi, gitt når som helst i samtalekjeden). De avmerkede unntakene bryter til slutt filosofien om unntak (den ikke-lokaliteten mellom tilstandsstedet og stedet for behandlingen).

Som et resultat bruker standard Java-bibliotek i praksis runtime-unntak for de vanligste operatørene (aritmetikk, samlinger, minnetildeling) for å unngå leksikal forurensning av sjekket unntak .

PHP

Behandling av en divisjon med null i PHP . Vi kan da ringe til klassen "Unntak" direkte foreslått av dette språket.

// Note : PHP_EOL représente un simple retour à la ligne function diviser($x, $y) { if ($y == 0) throw new Exception('Division par zéro'); else return ($x / $y); } try { echo diviser(1, 2) . PHP_EOL; // 1/2 echo diviser(3, 0) . PHP_EOL; // 3/0 : instruction qui déclenchera l'exception echo diviser(2, 1) . PHP_EOL; // 2/1 : cette instruction ne sera pas exécutée, la précédente ayant déclenché une exception } catch (Exception $e) { echo $e->getMessage() . PHP_EOL; // afficher le message lié à l'exception // À cet emplacement, une éventuelle instruction supplémentaire qui sera exécutée après le déclenchement de l'exception } echo "Malgré la division par zéro, l'exécution du script sera poursuivie et cette instruction echo sera prise en compte.";

Hvis variabelen y er lik 0, vil gjennomføringen være fortsatt utenfor av prøve uttalelsen avgrenset av bukseseler.

Resultatet blir derfor:

0.5 Division par zéro Malgré la division par zéro, l'exécution du script sera poursuivie et cette instruction echo sera prise en compte.

Python

Det er mulig for brukeren å definere sine egne unntak og fortelle programmet når de skal kaste dem med nøkkelordet raise.

Eksempel:

class TestError(Exception): #Cette ligne permet d'hériter de la classe de base Exception qui est une erreur basique. def __init__(self, message):#Le paramètre message se trouve dans toutes les classes d'exception. self.message=message #Ici on va tester l'erreur sur une division par 0 def diviser(dividende, diviseur): try: if diviseur != 0: return dividende/diviseur except Exception: raise TestError("division par zéro") #Ensuite on teste avec des variables x=float(input("Entrez le dividende : ")) y=float(input("Entrez le diviseur : ")) print(diviser(x, y)) #A cette ligne si y vaut 0 alors la division renverra notre exception TestError.

Småprat

I praksis kan unntakene som rapporteres være bare relativt milde eller forbigående; i dette tilfellet må et idiom som kombinerer EMS, variabler, tester, sløyfer, implementeres for å starte en beregning på nytt som ville ha mislyktes av godartede årsaker.

I Smalltalk dempes disse vanskelighetene av følgende muligheter:

  • prøv en beskyttet blokk med nye argumenter,
  • å gjenoppta utførelsen av signalberegningen, muligens ved å oppgi en "returverdi".
Prøv igjen

Retry og retryUsing nøkkelord tillater henholdsvis å utføre blokken som er beskyttet av behandleren igjen uten å bruke en eksplisitt loopback, eller å utføre en ny blokk i stedet for blokken som signaliserte unntaket. Her er et eksempel:

| fileContents | fileContents := ['myfile.txt' asFilename readStream contents] on: Error do: [:ex | | newName | newName := Dialog prompt: 'Problem reading file. Another name?'. ex retryUsing: [newName asFilename readStream contents]] Gjenoppta

Noen unntak sies å være "kontinuerlige". Dette betyr at en behandler kan sende en "CV" -melding (som overfører argumentet ved retur av rapporteringsuttrykket) til unntaket, noe som fører til at kontroll overføres ved retur av rapporteringsuttrykket.

La oss se et eksempel på et program som leser "alternativene" til en konfigurasjonsfil (variabel = verdipar). Det første fragmentet analyserer det neste alternativet i en strøm som representerer filen:

MyApplication>>readOptionsFrom: aStream | option | [aStream atEnd] whileFalse: [option := self parseOptionString. "nil if invalid" option isNil ifTrue: [InvalidOption signal] ifFalse: [self addOption: option]]

Det andre fragmentet bruker det første til å lese hele konfigurasjonen; behandleren for unntaket "InvalidOption" er definert der.

MyApplication>>readConfiguration [self readOptionsFrom: 'options' asFilename readStream] on: InvalidOption do: [:ex | (Dialog confirm: 'Invalid option line. Continue loading?') ifTrue: [ex resume] ifFalse: [ex return]] Konsekvens på rapporten

Siden vi har introdusert muligheten for å gjenoppta en beregning på instruksjonen etter signaliseringen, må vi være forsiktige med å ikke ødelegge samtalestakken på tidspunktet for signaliseringen: denne ødeleggelsen må finne sted når programmet forlater den siste lederen som var involvert. rapportere.

Common Lisp-systemet med forhold

SGE-ene fra de foregående språkene vurderer ikke muligheten for å reparere konteksten som indikerer unntaket og å starte beregningen på nytt i den sammenheng som er reparert. Smalltalk tillater at en substitusjonsreturverdi leveres for uttrykket som signaliserer et unntak, men lederen har ikke tilgang til det fornærmende leksikale miljøet .

En tilstand er en generalisering av en feil [Pitman 01]  : ikke alle forhold er uønskede.

Hierarkiet med unntakstyper for avslutning av EMS tilsvarer et hierarki av tilstandstyper, inkludert en gren for ikke-dødelige forhold. Dette hierarkiet er beskrevet med Common Lisp Object System , så det er også et hierarki med unntaksklasser.

I SCCL er en behandlingsblokk for en tilstandsbehandler en funksjon som er lukket i det leksikale miljøet til unntakshåndtereren, og som utføres i det dynamiske miljøet i rutinen der tilstanden signaliseres; den dynamiske konteksten til signalrutinen blir ikke ødelagt.

Dette betyr at signaliseringen ikke innebærer at kontrollflyten overføres på en ikke-lokal måte: anropsstakken blir ikke ødelagt på tidspunktet for signaliseringen. Signaleringen begynner med en funksjonsanrop til den aktuelle GE; det kan skrives som følger:

(defun signal (condition) (funcall (find-first-active-handler-of-type (type-of condition)) condition))


En omstart er en funksjon som inneholder instruksjonene som er nødvendige for å reparere en eksepsjonell situasjon, og lukkes i et leksikalt miljø nær rapportstedet. Den er derfor lokalisert i samtalekjeden mellom den aktuelle GE og den rapporterte tilstanden. En omstart påkalles vanligvis av behandleren av en tilstand for å endre det leksikale eller dynamiske miljøet til prosedyren som signaliserer tilstanden (fikse situasjonen) og utføre et ikke-lokalt hopp til et punkt i denne prosedyren (gjenoppta).

Operatører

Vi nevner de viktigste operatørene av tilstandssystemet.

Operatører
Etablere en GE HÅNDTERERBIND
Opprett omstart RESTART-BIND
Finn omstart FINN-OMSTART
Påkalle en omstart INVOKE-RESTART
Rapporter en tilstand SIGNAL, FEIL, ADVARSEL ...
Ikke-lokalt hopp til et merke KASTE
Merk gjeldende ramme Å FANGE

(fangstsymbol) og (kastesymbol) er tilgjengelig for Lisp-programmereren for henholdsvis å markere gjeldende ramme med et symbol, og ødelegge anropstakken ved å gå opp til det første merket som tilsvarer symbolet som ble sendt som et argument. De brukes implisitt av tilstandssystemet.

Hvis handler-bind innebærer en fangst, begynner signalprimitivene aldri med et kast. Kaste påkalles bare hvis behandleren utfører et ikke-lokalt hopp til sin leksikale kontekst (som er forskjellig fra den dynamiske konteksten når den kalles), noe som betyr at vi faktisk vil ødelegge samtalestakken.

Bruk og drift Terminalbruk

SCCL kan brukes akkurat som å avslutte EMS: du setter opp en behandler, signaliserer, lurer på hva du skal gjøre.

1 . (defun foo () 2 . (tagbody 3 . (print « foo ») 4 . (handler-bind ((some-condition 5 . (lambda (condition) 6 . (print (type-of condition)) 7 . (go after-the-fact)))) 8 . (bar)) 9 . after-the-fact 10. (print "après bar, dans foo")) 11. 12. (defun bar () 13. (print « bar ») 14. (error "C'est bien une erreur" 'some-condition) 15. (print "ce code est inatteignable"))

La oss se på dette eksemplet, fra et kontrollflytperspektiv. Sporet av et kall til foo er:

3 . foo 13. bar 6 . SOME-CONDITION 10. après bar, dans foo

Beslutningen om å ødelegge samtalestakken er tatt av "(gå etter-det-faktum)" -behandleren for en viss tilstand. Lisp-systemet må kaste rett før du kjører farten.

Bruker en omstart

Følgende diagram viser trinnene som er implementert når du bruker en omstart.

La oss ta disse trinnene (tilfelle av utvinning):

1. etablering av leder G (for typen tilstand C) i programmets dynamiske miljø (dette betyr at G er tilgjengelig i en hvilken som helst del av samtalekjeden under dets etableringsramme); G kan fange leksikale variabler fra rammen der den er erklært (det er en leksikal lukning),

2. kall av den "beskyttede" skjemaet av G,

3. ring til en ny prosedyre, der en type C-tilstand vil bli signalisert,

4. etablering av en omstart R som beskytter et uttrykk for denne prosedyren, i det leksikale miljøet til sistnevnte,

5. det beskyttede uttrykket for prosedyren signaliserer en type C-tilstand: manager G finnes i det dynamiske miljøet (det er den siste aktive manager for forholdene av type C),

6. G bestemmer seg for å gjenoppta beregningen, han påkaller omstart R,

7. R utfører, i leksikalsammenheng med prosedyren som signaliserer en reparasjon (om nødvendig) og overfører kontroll til prosedyren (ikke-lokalt hopp), som gjenopptar arbeidet (av en eller annen grunn, i nr. 4).

Selvfølgelig, hvis lederen bestemmer seg for å forlate beregningen (# 6 bis), blir det gjort et ikke-lokalt hopp til rammen der G har etablert sine leksikale lenker (og spesielt etiketten som brukes til hoppet); samtalestakken blir ødelagt, R blir ødelagt, og like etter hoppet blir G selv ødelagt.

Eksempel med utvinning

Vi utvikler ideen om å bruke tilstandssystemet til å utnytte resultatene av en begrensningsløser. En tvangsløser, når den finner en løsning, signaliserer den til rutinen som ba om beregning. Hvis rutinen er fornøyd med løsningen, kan den stoppe løseren; det kan også gjenta beregningen for å oppnå følgende løsning.

Vi starter med å definere en ny type tilstand, tilsvarende en løsning funnet av løseren, med et spor for løsningen:

(define-condition backtrack-solution (condition) ((sol :initarg solution :reader solution)))

Vi etablerer en unntakshåndterer i rutinen som trenger resultatene av løseren; her velger vi å analysere resultatet av løseren etter hvert:

1 . (defun foobar (...) 2 . (let ((solutions-we-dont-like)) 3 . (handler-bind 4 . ((backtrack-solution  ; type de la condition 5 . (lambda (condition) 6 .  ;; on décide quoi faire 7 . (let ((sol (solution condition)) 8 . (when (good-enough sol) 9 . (return-from foobar sol)) 10. (push sol solutions-we-dont-like) 11. (invoke-restart 12. (first (find-restarts condition))))))) 13.  ;; l'appel initial au solveur 14. (backtrack-with args in the env)) 15. (choose-best-amongst solutions-we-dont-like)))

Det observeres at lederen kan bestemme, i henhold til "kvaliteten" på løsningen, å returnere den (linje 9), noe som innebærer at den nåværende beregningen forlates og ødeleggelsen av rammene knyttet til denne beregningen; eller stab den (linje nr. 10) og fortsett søket (linje nr. 11 - nr. 12).

I løserkoden må vi signalisere tilstanden bactrack-løsning når en løsning er funnet:

(defun backtrack-with (vars future inst foo)  ;; s'il n'y a plus de variable à instancier, on signale une solution (if (not future) (cerror "Autres solutions" 'backtrack-solution :solution (instances vars)) ... ))

Den primitive cerroren kondenserer etableringen av en omstart og signaliseringen av en tilstand til en enkelt operasjon. Vi kunne ha skrevet, mer verbalt, men med samme effekt:

(tagbody (restart-bind ((more-solutions (lambda () (print « restarting ») (go local-restart-point)))) (error "Autres solutions" 'backtrack-solution :solution (instances vars))) local-restart-point)

Historisk

1970-tallet (PL / I-språk)

Det første språket for å systematisere unntakshåndtering var PL / I ( ca. 1970).

Det gjør det mulig å aktivere ett eller flere av følgende forhold innenfor rammen av en blokk , og å knytte en prosess med dem, og foreningen forblir gyldig til

  • slutten av blokken;
  • eller erstatning for en annen behandling;
  • eller midlertidig maskering ved annen behandling av det samme unntaket i en intern blokk.

De anerkjente forholdene er: AREA, CHECK (verdiendring av en variabel), CONDITION (tilstand definert av programmereren), CONVERSION, ENDFILE, ENDPAGE, NØKKEL, UDEFINERT FIL, FIXEDOVERFLOW, OVERFLOW, UNDERFLOW1, ZERODIVIDE, STORAGE, STRINGRANGE, SUBSCRIP FEIL, FERDIG, ANYCONDITION.

Eksempel:

on check (valeur, somme, verif) call anomalie;

utfører programmet anomali når en av tre verdier verdi , mengde eller VERIF ser verdien endring (det er dermed mulig å gjennomføre tilsvarende det som nå triggere i relasjonsdatabaser .

FEIL er aktivert for alle forhold som ikke er eksplisitt adressert, i tillegg til den implisitte behandlingen (vanligvis en feilmelding, eller ingenting i det hele tatt hvis den ikke er aktivert som standard av ytelsesårsaker, for eksempel STRINGRANGE eller SUBSCRIPTRANGE).

FINISH påkalles systematisk på slutten av ethvert program, uavhengig av årsak. I vitenskapelig beregning kan man dra nytte av dette for eksempel for å lagre all data for å gjenoppta beregning senere fra bruddpunktet.

Et unntak er anerkjent med REVERT og gått til neste nivå (siden det er nesten alltid en brus interrupt kjede ) med RESIGNAL.

80- og 90-tallet

Bruken av unntakshåndterere har blitt utbredt på PC-er med bruk av beskyttet modus under DOS og deretter med multitasking- operativsystemer . Tidligere kan en programmeringsfeil lett føre til et programkrasj eller til og med et datakrasj .

Relaterte artikler

Referanser

Prinsippet om unntakshåndtering ble studert av John Goodenough på alvor i en artikkel fra 1975 . (John B. Goodenough, Unntakshåndtering: Problemer og en foreslått notasjon . Kommun. ACM 18 (12): 683-696 (1975))