A CLU programozási nyelv

Generic, típussal való paraméterezés



7. Kivételkezelés

7.1. Kivételek kezelése
7.2. Kivételek terjedése
7.3. Az exit utasítás
7.4. Elő- és utófeltételek


A programozási nyelvek eljárásai által megvalósított leképezések általában parciálisak. Ez azt jelenti, hogy még a szigorú típusellenőrzés sem tudja garantálni, hogy az eljárásokat a nekik megfelelő paraméterekkel hívjuk. A hívónak kell gondoskodni arról, hogy a paraméterek a megengedett tartományba essenek.

Robusztus az a program, melynek futása az esetleg fellépő hibák ellenére is folytatódik, valamilyen determinisztikus módon kezelve a hibás állapotot. A robusztusság egyik követelménye, hogy az eljárások totálisak legyenek. Ha az eljárás nem tudja elvégezni a neki szánt feladatot, akkor jelzi a problémát a hívónak, s rábízza a hiba kezelését.

Ahelyett, hogy minden egyes alkalommal az eljáráshívás elején megvizsgálnánk, hogy a paraméterek a megfelelő tartományba esnek-e, érdemesebb a defenzív programozást választani, azaz minden egyes eljárás legyen képes megvédeni magát a hibáktól.

7.1. Kivételek kezelése

A CLU-ban a defenzív programozást támogató eszköz a kivételkezelés mechanizmusa. Lehetőséget ad az eljárásoknak kivételek kiváltására illetve a hívónak a kivétel kezelésére. A kivételkezeléshez ki kell terjeszteni az eddigi eljárásfogalmunkat. Ez azt jelenti, hogy a fejlécbe felveszünk egy signal részt a kivételek számára. A teljes fejléc tehát:
proc_name = proc ( ... ) returns ( ... ) signals ( ... )
A signal mögött meg kell adni a kiváltható kivételek neveit és esetleg paramétereit. Például a
search = proc ( a : array[ int ], x : int ) 
  returns ( int ) 
  signals ( not_int, duplicate( int ) )
fejlécű eljárás a not_in és a duplicate kivételeket válthatja ki, s az utóbbihoz egy egész paramétert is meg kell adni. Az int típus div művelete például a következőképpen deklarálható:
div = proc ( num, denom : int ) returns ( int ) 
  signals ( overflow, zero_divide)
A paraméter nélküli kivétel kiváltása a
signal exception_name
míg a paraméterrel rendelkező kivétel kiváltása a
signal exception_name( parameter_expression )
formában implementálható. Az előbbi példa kivételeinek kiváltása a
signal not_in 
signal duplicate( 10 )
módon történhet.

Normál módon az eljárás vagy egy return utasítás hatására vagy a blokk végének elérése miatt ér véget. Kivétellel való terminálás során az eljárás befejeződése a signal utasítás hatására történik. Egy eljárás belsejében kizárólag azok a kivételek válthatók ki, amelyek fel vannak sorolva a fejléc signals részében (kivétel ez alól a failure, amiről később lesz szó).

A CLU except kulcsszava szolgál a kivétel kezelésére, melynek formája:

<statement except <handler list end
A handler listában kell felsorolni azokat a kivételeket, amiket az adott utasítással kapcsolatban kezelni akarunk. Lehet (de nem kötelező) egy others ág is, ahol a korábbi ágakban fel nem sorolt kivételeket kezelhetjük. Minden kivételkezelő részhez tartozik egy vagy több kivételnév illetve egy kivételkezelő törzs (utasítások sorozata).
... except
  when exception_namel : handling_bodyl
  when exception_namek : handling_bodyk
  others : body
end % except
Valós számok osztásánál például együtt kezelhető a túlcsordulás és a nullával való osztás:
z := x / y
  except
    when overflow, zero_divide : z := 0
  end %except
A duplicate kivételhez tartozó kivételkezelő ágban a paramétert is át kell venni:
... except
  when duplicate( i : int ) : <body % i can be used here 
end %except
Itt az i : int deklaráció hatásköre a kivételkezelő törzs. Minden felsorolt kivétel vissza kell hogy adjon megadott számú és típusú objektumot, sőt mindezt a formális paraméterek sorrendjének megfelelő sorrendben. Ha nem akarjuk használni a kivétel által átadott paramétert, a kivétel neve mögé (*)-ot írunk.
... except 
  when duplicate( * ) : <body 
end %except
Kivételt képez a paraméter-átvétel alól az others ág. Itt elveszik az átadott objektum.
Ha azonban az
others ( name : string ) : <body
alakot használjuk, akkor a name paraméterben megkapjuk a kiváltott kivétel nevét. Ha valahol kivétel váltódik ki, megszakad a program "természetes" futása, s a vezérlés a legközelebbi olyan except utasításnál folytatódik, ahol van a kiváltott kivételhez kivételkezelő rész. Az except utasítások egymásba is ágyazhatók.

Néha a kivétel kezelése mindössze abból áll, hogy újra kiváltjuk ugyanazt a kivételt - ugyanazokkal a paraméterekkel. Erre használható a resignal utasítás, melynek formája:

resignal enames
Ez azonban csak rövid formája az összes névre a megfelelő paraméterekkel kiadott except
utasításoknak. Például az
i : search ( a, x ) resignal duplicate, not_in
sor teljes mértékben ekvivalens az
i : search( a, x ) 
  except 
    when not_in : signal not_in 
    when duplicate ( j : int ) : signal duplicate( j ) 
  end %except
programrészlettel.
 

7.2. Kivételek terjedése

A CLU nyelv minden egyes eljárása (a programozó által definiáltak is !) automatikusan rendelkezik a failure nevű kivétellel, amelynek egy string argumentuma van. Ha egy hívott eljárásban kiváltott kivételt nem kezelünk a hívó eljárásban, akkor az failure kivétellé alakul, ami paraméterként megkapja az eredeti kivétel nevét. Ez hatásában azonos az
except 
  when failure : signal failure( s ) 
  others ( s : string ) :
    signal failure( "unhandled exception" || s ) 
end %except
kivételkezelő résszel.
 

7.3. Az exit utasítás

A ciklusokkal kapcsolatban volt szó a break és a continue utasításokról. Ezeknél általánosabb vezérlésátadást tesz lehetővé az exit utasítás. A szintaktikája hasonló a signal utasítás szintaktikájához:
exit exception_name( parameter_expression )
Hatására a vezérlés átadódik az ugyanabban a törzsben levő, az exit után következő első, az adott kivételt lekezelő          except ágra. A megadott kivételnévnek valamelyik except utasításban szerepelni kell..

Az exit által kiváltott kivételt nem kell definiálni a fejlécben (a hatása lokális), de csak egy explicit módon megadott kivételkezelő ágra lehet így hivatkozni, failure-ra nem és automatikus kivételtovábbadás sem történhet.

7.4. Elő- és utófeltételek

A nyelv lehetőséget ad elő- és utófeltételek használatáhoy. Ez a kivételkezeléshez is további kellemes eszközt ad a programozónak. A CLU specifikációja az alábbiakban ismertetett szintaktikai cukrot tartalmazza, mely olvashatóvá teszi és leegyszerűsíti az elő- és utófeltételek megadását.

Előfeltételek megadása:

modifies at most (<obj_list>)
new(<obj>)
returns		% procedure returns in normal way
                 (ie. does not abort, loop or signal)
signals <name>	% procedure raises given exception
                 (which must be listed in proc. header)

Utófeltételek megadása:

normally <post_pred>
except  signals <except1> when <pre1> ensuring <post1>
        ...
        signals <exceptN> when <preN> ensuring <postN>

Az ezzel ekvivalens forma:

(returns | signals <except1> | ... | signals <exceptN>) &
(returns => <post_pred> & ~(<pre1>|...|<preN>)) &
(signals <except1> => (<pre1> & <post1>)) &
       ...
(signals <exceptN> => (<preN> & <postN>))

Az ensuring klóz értéke opcionális, alapértelmezés szerint TRUE.

Ez a felírás azt is jelenti, hogy CLU-ban megengedett a kivételek nem-determinisztikus kiváltása.