Struktúrált kivételek a CCC-ben

Dr. Vermes Mátyás

2005. június

1.  Hibakezelés a régi Clipperben
2.  Strukturált kivételkezelés
3.  Átállás az új kivételkezelésre

1.  Hibakezelés a régi Clipperben

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ó.

eval(errorblock(),x)

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.

break(x)

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.

2.  Strukturált kivételkezelés

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.

3.  Átállás az új kivételkezelésre

Mi van a régi programokkal?

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:

  1. Az el nem kapott break kiértékeli az errorblokkot. Általában nincsenek a programokban ilyen breakek, ha mégis vannak, az hiba, és ez most legalább ki fog derülni.

  2. Az új CCC könyvtárak ezentúl error-on kívül más error-ból leszármazó hibákat is dobhatnak. Az alkalmazási programoknak nem létfontosságú, hogy erről tudjanak, kezelhetik a hibákat egyszerű error-ként is.

  3. A régi stílusú hibakezelésre felkészült programok továbbra is cserélgetik az errorblokkot (feleslegesen), ez azonban nem fog hibát okozni.

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.

Recover nélküli break

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)
    end

akkor 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.

eval(errorblock(),x) cseréje break(x)-re

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.

Errorblokk cserebere megszüntetése

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 hibák minősítése

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.

Kivétel osztályok

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.

A begin-recover utasítások felülvizsgálata

Ha az eddigieket végrehajtjuk

és eközben vigyázunk, hogy a csekély számú kivételes esetet ne rontsuk el, akkor a programok működésében nem lesz változás, viszont még nem is használtuk ki az előnyöket. Ezután van értelme áttérni az új begin-recover utasítás használatára, ami lehetővé teszi, hogy a recoverekkel válogassunk az eddig ömlesztve kapott hibák között, a finally ágakkal pedig kényelmesen takarítsunk magunk után.


Learn Hungarian in Budapest in Ulysses language school. Group and private courses on affordable prices.