rescue
kulcsszót követően írhatjuk a kivételkezelő részeket. Deferred illetve external típusú alprogramok értelemszerően nem rendelkezhetnek kivételkezelő résszel, mivel deferred alprogram esetében nem lenne értelme kivétekezelést írni, hiszen a törzset nem is definiáltuk, external alprogram esetében pedig a külső programozási eszköz nem ismeri az Eiffel-ben implementált kivételkezelést. (Ld. még alprogramok.)
EXCEPTIONS
osztálya által nyújtott lehetőségeken keresztül.
Ezek a kategóriák az exception megnyilvánulási módjait különböztetik meg, nem a valódi okát.
check
utasítással vizsgálni az elérhető memóriahely mennyiségét, vagy minden összeadás előtt check
utasítással vizsgálni, hogy az eredmény beleesik-e a gép által ábrázolt határok közé. Az ilyen esetekben az a priori check
-ek drágák, és valószínőleg csak egy kis százalékuk nem eredményes. Ezek azok az esetek, amikor az exceptionre szükség van. Egy abnormális helyzet felismerése, és esetleges helyereállítása - miután bekövetkezett.
Mi történhet egy exception után? Mit tehetünk, amikor a váratlan bekövetkezett?
Az exception egy olyan esemény bekövetkezése, ami nem engedi, hogy egy komponens teljesítse vállalt kötelezettségét. Nem elfogadható reakció lenne úgy terminálni a komponenssel, hogy csendben visszatér, és a felhasználó azt hiszi, hogy minden rendben. Mivel a dolgok nem mentek normálisan - a szerződés nem lett teljesítve, így egy ilyen politika majdnem elkerülhetetlenül bajt csinálna.
Az Eiffel nyelv fejlesztői három csoportra osztották az esetleg előforduló kivételeket:
EXCEPTIONS
osztály. (Ld. még szabványos könyvtárak.)
Mivel ezek a mechanizmusok eljárás-szinten vannak definiálva, az alacsonyabb szintő komponensekre - mint pl. egy utasítás vagy egy hívás - nincs nyelvi mechanizmus. Ez azt jelenti, hogy egy ilyen komponens végrehajtására tett sikertelen kisérlet (pl. egy konstruktor, amikor nincs elég memória, vagy egy void objektumra meghívott függvény) esetén csak a szervezett pánik politika lehetséges, vagyis a komponens végrehajtása sikertelen lesz.
A rescue
záradék segítségével lehet megadni egy rutin válaszát olyan exception
-ekre, amelyek a rutin végrehajtás során fellépnek. Ez a rutin deklaráció egy opcionális része, amit a rescue
kulcsszó vezet be.
Példa:
do
.. záradék), mint exception
(tetszőleges exception!) a rescue záradék végrehajtását fogja eredményezni. Itt ezt a záradékot a reset eljárás hívása alkotja, ami azt jelenti, hogy visszaviszi az objektumot egy stabil állapotba, ami kielégíti az osztályinvariánst.
A rescue
záradék terminálása egyben a rutin végrehajtás terminálását is jelenti - és ekkor (ha nincs retry utasítása rescue
záradék végén) a rutin végrehajtása elszáll.
Eiffel-ben az ANY
osztályban (minden felhasználói osztály ősosztályában) definiálva van egy default_rescue
nevő metódus, melynek törzse alapértelmezésben csak egy üres utasítás. Ez az a feature, ami minden olyan esetben implicit módon meghívódik, amikor explicite nem adunk meg egy alprogram végén rescue
részt, és az alprogram mőködése során mégis kivétel váltódik ki. Mivel minden felhasználói osztály az ANY
class leszármazottja, ezért minden osztály felüldefiniálhatja ezt a metódust, saját igényei szerint. További öröklés esetén a felüldefiniált feature a leszármazottakra is érvényes lesz. Mivel Eiffel-ben a konstruktorok feladata az osztály-invariáns beállítása, ezért a default_rescue
-t gyakran a paraméter nélküli konstruktor szinonímájaként szokták definiálni, hogy szervezett pánik esetén biztosítsa az invariáns visszaállítását. (Ld. még helyességbizonyítás.)
Példa:
Eiffel-ben lehetőség van kivétel kiváltódása esetén az alprogram törzsének újbóli végrehajtására. Természetesen ennek csak akkor van értelme, ha valamilyen módon biztosítjuk, hogy másodszorra a törzsnek egy másik, alternatív része hajtódjon végre. Az újrakezdést úgy tudjuk biztosítani, hogy a rescue záradék végén feltüntetjük a retry
kulcsszót.
Példa:
impossible
nevő publikus feature-ben -, hogy a végrehajtás sikertelen volt.
rescue
záradékban mindig megnövelve, amíg el nem ér egy maximális értéket.
Retry
csak rescue
záradékban fordulhat elő. Például a rescue
által hívott rutin nem tartalmazhat retry
-t, így a default_rescue
átdefiniálása sem. Más szóval a default_rescue
használata nem vezethet újrakezdéshez. Ennek oka az egyszerőség és az olvashatóság támogatása, a rescue
záradékon kívül egy retry
nem elég informatív - mit is próbáljunk újra?
Szervezett pánik esetén egy kivétel lép fel a hívóban, ha sikertelen volt a végrehajtás. De mi történik, ha nincs hívó? Ez csak akkor fordulhat elő, ha ez egy original call volt, a gyökérbeli létrehozó eljárás során. Ez ugyanis általában úgy mőködik, hogy hív más rutinokat, és azok ismét másokat. Az original call egy sikertelensége a rendszer failure-hez vezet. A rendszer végrehajtása befejeződik, miközben egy megfelelő diagnosztikát ad arról, hogy a rendszer nem tudja végrehajtani a feladatát.
A hiba általában nem a legfelsőbb szinten szokott bekövetkezni, hanem valahol mélyen a hívásláncban. Ha újrakezdéssel nem lett lekezelve, akkor felér a gyökérbeli induláshoz.
Például, ha egyik rutinnak sincs rescue
záradéka, egy osztály sem definiálja át a default_rescue
-t, akkor minden exception továbbadódik, és az eredmény a rendszerhiba.
Mi történik ekkor? A rendszernek van egy eszköze (ami nem a nyelv része), az ún. history tábla, ez egy diagnosztikát ad a hibás hívási láncról.
Az exception kezelési szemantika definíciójához jó tudni, hogy minden rutinnak van egy explicit vagy implicit rescue
záradéka.
Egy C
osztály egy tetszőleges r
rutinjának van egy rb
rescue blokkja, ami egy Compound (összetett utasítás), a következőképpen definiálva:
r
-nek van egy rescue záradéka, akkor rb
az ezen záradékban található Compound.
rb
a C
-beli default_rescue
-ra vonatkozó hívásból álló egyetlen utasítás.
retry
-t hajt végre, a rutin törzse újra végrehajtódik, ez a kurrens exception processzt befejezi. Egy exception bármely új kiváltása egy új exception, amit megfelelően le kell kezelni.
retry
nélkül ért véget, ez a kurrens exception processzt befejezi és az r
kurrens végrehajtását is, ezen végrehajtás egy failure-t eredményez. Ha van hívó rutin, akkor ez az exception itt egy kivételt vált ki, ami rekurzív módon le lesz kezelve az itt adott szemantikának megfelelően. Ha nincs hívó rutin, akkor r
a gyökérbeli create eljárás, a végrehajtása terminálni fog.
A definíció azt mondja, hogy ezt egy olyan rutinra kell alkalmazni, ami se ignorálva nem lett, sem folytatva. Ez a Kernel Library-beli EXCEPTIONS
osztály feature-eire vonatkozik, amit a hamis riasztás eseteinél alkalmazhatunk:
retry
az r
törzsét csak újra végrehajtja, de nem ismétli meg az argumentum átadást és a lokális entity inicializálást! Ez teszi lehetővé, hogy egy új próbálkozásnál más ösvényt válasszunk.
A rescue záradék szerepe, hogy váratlan eseményekkel is bánni tudjunk. Bár egy jól megtervezett rendszerben ezeket csak ritkán fogjuk végrehajtani, bizonyos speciális feltételek vannak, hiszen az objektumok konzisztenciáját fenn kell tartani.
Egy rutin failure az aktuális objektumot (a legutolsó hívás célobjektumát) egy konzisztens állapotban kell hagyja, ami kielégíti az invariánst, hogy ne gátolja meg, hogy esetleg egy másik rutin, amelyik használja és képes pl. újrakezdésre, egy jó eredményt adjon. Hasonlóan, egy retry utasítás után is, ami újraindítja a rutin törzsét, a rutin előfeltételének igaznak kell lenni.
Ezek vezetnek az exception helyesség fogalmához, ez egy olyan feltétel, ami az osztály helyességhez kell. (Egy osztály helyes, ha konzisztens (minden rutin, ami egy előfeltételnek megfelelő állapotban indul, az utófeltételnek és az osztály invariánsnak megfelelő állapotban fejeződik be), ciklus-korrekt, check-korrekt és exception-korrekt.)
Egy C
osztály egy r
rutinja exception-korrekt akkor és csak akkor, ha a rescue blokkjának minden b
ágára a következő igaz:
b
egy retry-jal végződik: {true} b {INVC and prer}
b
nem retry-jal végződik: {true} b {INVC}
EXCEPTIONS
osztály
Ha az előbbiekben leírt szolgáltatásoknál többet kívánunk igénybe venni a kivételkezelő részekben, akkor a Kernel Library
EXCEPTIONS
osztályát kell használnunk. Ezt a következőképpen tehetjük meg: azt az osztályt, melyben alkalmazni kívánjuk az EXCEPTIONS
osztály elemeit, származtatnunk kell az EXCEPTIONS
osztálytól is. (Ld. még szabványos könyvtárak.)
Néhány feature az EXCEPTIONS
osztályból:
assertion_violation : BOOLEAN --
az utolsó kivétel ellenőrzés megsértése volt-e;
class_name : STRING --
azon osztály neve, melyben az a kivétel kiváltódott, mely az utolsó kivételhez vezetett;
comment : STRING --
az utolsó kivétel szöveges leírása;
developer_exception_name : STRING --
az utoljára (raise-zel) kiváltott fejlesztői kivétel neve;
exception : INTEGER --
az utoljára kiváltódott kivétel kódja;
external_event : BOOLEAN --
az utolsó kivételt külső esemény (OR szignál) okozta-e;
raise(ex_name : STRING) --
fejlesztői kivételt kiváltó feature;
routine_name : STRING --
azon alprogram neve, melyben az utolsó kivétel eredetét jelentő kivétel kiváltódott;
tag_name : STRING --
a megsértett állítás-záradék neve, ha egy állítás megsértéséből származik az exception.
EXCEPTIONS
egy integer kódot vezet be minden elképzelhető exception típusra (pl. class_invariant, invalid_inspect_value, precondition, postcondition, loop_invariant, loop_variant
, stb.). Az exception
jellemző többek között ezek közül veszi fel a kiváltott exception értékét. Ez lehetővé teszi, hogy egy rescue záradékban hivatkozzunk rájuk.
Mivel a téves riasztás
kategóriába eső kivételek viszonylag ritkán fordulnak elő, ezért kezelésükről nem nyelvi elem gondoskodik, mint a másik két esetben, hanem az EXCEPTIONS
osztály metódusai. Mivel a fejlesztők feltételezték, hogy téves riasztást csak kívülről érkező esemény (szignál) okozhat, ezért ezek az eszközök csak ilyen típusú kivételek eseténhasználhatók (előfeltételükben szerepel, hogy a paraméterük csak szignál lehet).
ignore(except : INTEGER) --
a továbbiakban az adott kódú szignál nem okoz kivételt;
catch(except : INTEGER) --
az előző feature ellentéte;
continue(except : INTEGER) --
miután meghívtuk ezt az eljárást, minden adott kóddal érkező szignál hatására végrehajtódik az EXCEPTIONS osztályban definiált continue_on_signal
metódus, melynek eredetileg üres a törzse, de bármely leszármazottban felüldefiniálható;