Dr. Vermes Mátyás
2005. június
Ha fatális hibát észleltünk, végrehajtottuk az errorblokkot. A default errorblokk általában értesít a hiba körülményeiről (kiírja a stacket) majd kilépteti a programot.
Ha nem akartuk, hogy egy bizonyos hibára a program kilépjen, kicseréltük az errorblokkot {|x|break(x)}-re, az esetleges breaket pedig elkaptuk egy recoverrel. Ezzel a technikával az a baj, hogy ilyenkor a recover más hibákat is elkap, amivel megnehezül a program tesztelése, mivel a recover után már nincs meg a hiba környezete.
Megjegyzés: A (régi) break semmi mást nem csinált, mint visszaállította a stacket, és a legbelső recover ágnak átadta a vezérlést. Ha a break körül nem volt begin-recover utasítás, akkor a program csendben kilépett. Nem túl szerencsés ez a csendbeni kilépés, de ilyen volt a Clipper.
Megtehettük, hogy a hibakezelés gyanánt eval(errorblock(),x) helyett rögtön break(x)-et írtunk. Így a korábbi eszközökkel is lehetett programozni az errorblokk cserélgetése nélkül:
function tranzakcio() local e begin tranzakcio1() //break lehet benne recover using e //hibakezelés end
A probléma az, hogy a hiba keletkezésének helyén el kell dönteni, hogy eval(errorblock(),x), vagy break(x) legyen-e a reakció.
Ha a tranzakciót hívó program nem cseréli ki az errorblokkot, akkor a hiba környezetéről részletes jelentés készül, majd a program kilép. Ha a tranzakciót hívó program kicseréli az errorblokkot, akkor átveheti a hibakezelést, de ilyenkor bajlódnia kell a bezavaró hibákkal is, amiknek már nincs is meg a környezete.
Csak akkor célszerű alkalmazni, ha ismeretes, hogy a tranzakciót hívó program recoverrel készült fel a hiba elkapására (máskülönben a program csendben kilép). Nehéz a nagy programokat összehangolni.
A fatális hibákat (különösen a programozási hibákat) jobb a default errorblokkal elintézni. A magasabb szintű tranzakciós hibákat gyakran jobb breakkel visszaadni a hívónak. A nagy rendszerek egyes részeinek egyet kell érteniük abban, hogy mikor melyik eset áll fenn.
A struktúrált kivételkezelés eszközöket ad ahhoz, hogy a hibakezelést az egyszerűbb, breakes módszerre alapozzuk. Az első intézkedés: Az el nem kapott kivétel nem csendben kilép, hanem kiértékeli az errorblokkot (enyhe inkompatibilitás). Így a recover nélküli break is teljes hibajelentést ad.
Az új break nem egyszerűen a legbelső recovert keresi meg, hanem az egymásba skatulyázott (vagy ilyennek tekinthető) begin-recover utasítások között belülről kifelé haladva addig keres, amíg megfelelő típusú recovert talál, és arra adja a vezérlést. Ez lehetővé teszi, hogy egy recover elkapjon egy bizonyos típusú hibát, miközben nem semmisíti meg más olyan hibák környezetét, amit nem tud, vagy nem akar kezelni.
function ff(x) local e begin /*sequence*/ ? "HOPP-1" break(x) ? "HOPP-2" recover e <specerror> //elkapja specerror leszármazottait ? "rec1", e:classname recover e <error> //elkapja error leszármazottait ? "rec2", e:classname recover e <c> //elkapja a stringeket ? "rec3", upper(e) recover /*using*/ e //bármit elkap (régi szintaktika) ? "rec4", e recover //ez is bármit elkapna (felesleges) ? "ide nem jöhet" finally ? "lefut a begin-recover elhagyásakor" end
Új szintaktika: a sequence kulcsszó elhagyható (zajszó)!
Új szintaktika: a using kulcsszó elhagyható (zajszó)!
Új szintaktika: több recover lehet lineárisan felsorolva!
Új szintaktika: a recover változó után megadható egy kifejezés!
Új szintaktika: opcionális finally klóz!
A lineárisan felsorolt recovereknek ugyanaz a hatása, mintha a begin-recover utasítások egymásba volnának skatulyázva (feltéve, hogy a recover ágakból nem indul újabb break):
function ff(x) local e begin begin begin begin begin ? "HOPP-1" break(x) ? "HOPP-2" recover e specerrNew() ? "rec1", e:classname end recover e errorNew() ? "rec2", e:classname end recover e "" ? "rec3", upper(e) end recover using e ? "rec4", e end recover ? "ide nem jöhet" finally ? "lefut a begin-recover elhagyásakor" end
A recover utasításban az újdonság a végére írt típuskifejezés. A típuskifejezés formái a következők:
Tehát a recover változó után írt kifejezéssel lehet beállítani a recover típusát és ezáltal szűrni, hogy milyen típusú hibákat kapjon el az adott recover. A kivétel elkapásának szabályai:
Megjegyzés: Az új kivételkezelés a régi Clipper természetes kiterjesztése, abban az értelemben, hogy speciális esetként tartalmazza a régi Clipperből ismert formákat. Megmarad a (majdnem teljes) kompatibilitás a korábbi forrásokkal, ui. az új működés új szintaktikához kapcsolódik.
Megjegyzés: A kivétel típustól függő elkapásához típusinfót kell rendelnünk a recoverekhez, ezt szolgálják a recover változók után írt kifejezések. Az összes ilyen kifejezés a begin utasítás előtt hajtódik végre. Ha van mellékhatásuk, azzal a programban számolni kell. A kifejezések kiértékelésének sorrendje implementációfüggő, azaz nem szabad számítani egy meghatározott sorrendre. Alapesetben a kifejezések értéke CCC szintű változókon keresztül nem érhető el, éppen ezért az értékhez tartozó memóriaobjektumokat a szemétgyűjtés bármikor (akár azonnal) megszüntetheti, de ez nem okoz gondot, mivel a kifejezéseknek csak a típusára van szükség, a tényleges értékére nem. A <symbol> alakú típuskifejezések eleve létre sem hozzák a kérdéses memóriaobjektumokat, ezért hatékonyabbak.
Amikor a vezérlés elhagyja a begin-recover utasítást, végrehajtódik a finally ág . A finally ág lefut,
Ha a kivételt egyáltalán semmi sem kapja el, akkor a break eredeti környezetében kiértékelődik az errorblock. Ilyenkor a finally ág nem hajtódik végre, hiszen a vezérlés nem hagyta el a begin-recover utasítást. Ha például a hibát a default errorblokk kezeli, és az error objektumban candefault==.t., akkor a break még vissza is térhet.
A régi Clipper tiltotta a begin-recover közül történő kiugrást return, loop, exit utasításokkal. A CCC új kivételkezelése (a Jávához hasonlóan) ezt lehetővé teszi, és az elhagyott begin-recover utasítások finally ágait (belülről kifelé) végrehajtja.
A régi programokat nem kötelező átalakítani. Például a Kontó minden változtatás nélkül is kiválóan fut. Érdemes azonban tudni a régi és új kivételkezelés eltéréseiről Az eltérések az alábbiakban foglalhatók össze:
A következőkben megvizsgáljuk a részleteket, miközben eljárást dolgozunk ki arra, hogyan lehet viszonylag mechanikusan átállni az új kivételkezelésre.
A recover nélküli break az eddigiektől eltérően nem csendben kilép, hanem végrehajtja az errorblokkot. Emiatt egy el nem kapott break(x) hatása megegyezik eval(errorblock(),x) hatásával. A break még vissza is térhet, ha pl. a default errorblokk kezeli le, és a dobott errorban candefault==.t. vagy canretry==.t..
Szabály: break(x) majdnem ugyanaz, mint eval(errorblokk(),x), kivéve, hogy a break begin-recoverrel eltéríthető. Speciálisan, ha nincs recover, akkor break(x) ugyanaz, mint eval(errorblock(),x).
Megjegyzés: Tudni kell, ha az errorblokk {|x|break(x)}-re van cserélve, akkor a recover nélküli break végtelen rekurziót okoz.
Megjegyzés: Ezt a működést a régi Clipperben és a vele kompatibilis (korábbi) CCC-ben nem lehetett megvalósítani. Ha ui. a main-eket átírjuk a következő módon
function main() local e begin main1() recover using e eval(errorblock(),e) endakkor ugyan elkaphatunk minden (eredetileg) recover nélküli breaket, viszont elveszítjük a hibák eredeti környezetét, azaz nem lesznek használható hibaüzeneteink.
Szinte minden eval(errorblock(),x)-t break(x)-re kell cserélni, az alapkönyvtárban is, és az alkalmazásokban is. Ez a változtatás elvileg mindenhol végrehajtható a működés változása nélkül.
Kivételesen ott változhat a működés, ahol az errorblokk le van cserélve, de nem a szokásos {|x|break(x)}-re, hanem valami másra. Az ilyen eseteket külön meg kell vizsgálni.
Ott is vátozhat a működés, ahol nincs lecserélve errorblokk, viszont a programban egy korábban semmit el nem kapó recover van. A csere aktivizálhat egy ilyen alvó recovert.
Azokban az esetekben, amikor az errorblokk egy tranzakció erejéig {|x|break(x)}-re van cserélve, majd visszaállítva, a cserebere megszüntethető. Az egész programra kiterjedő errorblokk cseréket meg kell őrizni. Például a Kontó a hibákat az errorblokkban naplózza. A z editor bármilyen hiba esetén kilépés előtt az editált szöveget menti.
Ha a feleslegessé vált errorblokk cserét bennefelejtjük a programban, az nem okoz hibát.
A CCC belső hibáira és a programozási hibákra (pl. argument error) error objektumot dobunk (most is így van). Az ilyeneket általában nem fogjuk elkapni, kivéve, ha mindenképpen meg kell akadályozni a program elszállását.
Azokra a hibákra, amiket esetleg el akarunk kapni error leszármazottat dobunk. Nem errort, azért, hogy az alkalmazási hibák elkülönüljenek a programozási hibáktól.
Azoknál a hibáknál jó error-t dobni, ahol a hibát nem a kivétel elkapásával, hanem a program kijavításával kell kezelni.
Ilyesmi lehetne:
error -> apperror error -> apperror -> invalidoptionerror error -> apperror -> invalidformaterror error -> apperror -> invalidformaterror -> invalidstructerror error -> apperror -> invalidformaterror -> xmlsyntaxerror error -> apperror -> invalidformaterror -> xmlsyntaxerror -> xmltagerror error -> apperror -> tabobjerror error -> apperror -> tabobjerror -> tabindexerror error -> apperror -> tabobjerror -> tabstructerror error -> apperror -> tabobjerror -> memoerror error -> apperror -> tabobjerror -> tranlogerror error -> apperror -> ioerror error -> apperror -> ioerror -> eoferror error -> apperror -> ioerror -> fnferror error -> apperror -> ioerror -> readerror error -> apperror -> ioerror -> writeerror error -> apperror -> ioerror -> socketerror
Nagyon lényeges kérdés, hogy a kivételosztály hierarchia jól el legyen találva.
A Jávától eltérően az az alapállás, hogy a hibákat nem kapjuk el, hanem hagyjuk, hogy a program elszálljon, és magától kiíródjon a hiba környezete. Csak akkor kapunk el egy hibát, ha érdemben ki is tudjuk javítani, vagy mindenképpen meg kell akadályozni a program elszállását. A kivétel osztályokat ennek megfelelően kell megtervezni.
A könyvtárakat ki kell egészíteni olyan modulokkal, amik definiálják az adott könyvtár által használt kivétel osztályokat, és dokumentálni kell, hogy honnan, milyen kivétel jöhet, mint ahogy az a Jávában is van.
Ha az eddigieket végrehajtjuk