Kivételkezelés

A kivételkezelés egy programozási  nyelvi mechanizmus , amelyet arra terveztek, hogy leírja a program reakcióit a futásidejű hibákra és egyéb lehetséges problémákra ( kivételekre ), amelyek a program végrehajtása során merülhetnek fel, és az alapvető program általi további feldolgozás lehetetlenségéhez (értelmetlenségéhez) vezethetnek. algoritmus . Az oroszban a kifejezés rövidebb formáját is használják: " kivételkezelés ".

Kivételek

A kivételes helyzet általános fogalma

Egy program végrehajtása során olyan helyzetek adódhatnak, amikor a külső adatok, a bemeneti-kimeneti eszközök, vagy a számítógépes rendszer egészének állapota lehetetlenné vagy értelmetlenné teszi a további számításokat az alapul szolgáló algoritmus szerint. Az alábbiakban az ilyen helyzetekre mutatunk be klasszikus példákat.

A kivételek típusai

A program működése során fellépő kivételes helyzetek két fő típusra oszthatók: szinkronra és aszinkronra, amelyekre adott válaszelvek jelentősen eltérnek.

A kivételek bizonyos típusai szinkron vagy aszinkron kategóriába sorolhatók. Például egy nullával osztás utasításnak formálisan szinkron kivételt kell eredményeznie, mivel logikailag pontosan az adott utasítás végrehajtása során következik be, de egyes platformokon a mély csővezetékek miatt a kivétel valójában aszinkronnak bizonyulhat.

Kivételkezelők

Általános leírás

Az alkalmazások natív kivételkezelési mechanizmusának hiányában a kivételekre a leggyakoribb válasz a végrehajtás azonnali megszakítása, és a felhasználónak a kivétel természetéről szóló üzenet megküldése. Elmondhatjuk, hogy ilyen esetekben az operációs rendszer lesz az egyetlen és univerzális kivételkezelő. Például a dr . Watson , amely információkat gyűjt egy kezeletlen kivételről , és elküldi egy speciális Microsoft - kiszolgálóra .

Lehetséges figyelmen kívül hagyni a kivételt és folytatni, de ez a taktika veszélyes, mert hibás programeredményekhez vagy későbbi hibákhoz vezet. Például, ha figyelmen kívül hagyja az adatblokk fájl olvasási hibáját, a program nem azokat az adatokat kapja a rendelkezésére, amelyeket olvasnia kellett, hanem néhány mást. Lehetetlen megjósolni használatuk eredményét.

A rendkívüli helyzetek program általi kezelése abból áll, hogy rendkívüli helyzet bekövetkeztekor az irányítás átkerül valamilyen előre meghatározott kezelőre  - egy kódblokkra, egy eljárásra, egy funkcióra, amely elvégzi a szükséges műveleteket.

A kivételkezelők működésének két alapvetően eltérő mechanizmusa van.

Két lehetőség van a kivételkezelő programhoz való csatlakoztatására: strukturális és nem strukturális kivételkezelés.

Nem strukturális kivételkezelés

A nem strukturális kivételkezelés a függvények vagy parancskezelők regisztrálására szolgáló mechanizmusként valósul meg minden lehetséges kivételtípushoz . A programozási nyelv vagy rendszerkönyvtárai legalább két szabványos eljárást biztosítanak a programozó számára: egy kezelő regisztrálását és egy kezelő regisztrációjának törlését . Az első hívása egy adott kivételhez „köti” a kezelőt, a második hívása megszünteti ezt a „kötést”. Kivétel esetén a fő programkód végrehajtása azonnal megszakad, és megkezdődik a kezelő végrehajtása. A kezelő befejezése után a vezérlés vagy a program valamely előre meghatározott pontjára kerül át, vagy vissza arra a pontra, ahol a kivétel előfordult (a megadott feldolgozási módtól függően - visszatéréssel vagy anélkül). Függetlenül attól, hogy a program melyik része fut éppen, egy adott kivételre mindig a hozzá utoljára regisztrált kezelő válaszol. Egyes nyelveken a regisztrált kezelő csak az aktuális kódblokkon belül marad érvényben (eljárások, funkciók), ekkor nincs szükség a regisztráció törlésére. Az alábbiakban a programkód egy feltételes töredéke látható, nem strukturális kivételkezeléssel:

SetHandler (ErrorDB, Ugrás ErrorDB) // Egy kezelő van beállítva a "DB Error" kivételhez - a "GoTo Error DB" parancshoz ... // Itt vannak az adatbázis kezeléséhez szükséges operátorok JumpTo ClearErrorDB // Feltétel nélküli ugrási parancs – a kivételkezelő megkerülése ErrorDB: // címke - adatbázishiba esetén itt történik meg az átállás a telepített kezelő szerint ... // DB kivételkezelő RemoveOshDB: // címke - itt történik az ugrás, ha a vezérelt kód adatbázishiba nélkül fut le. ClearHandler (DB hiba) // Kezelő eltávolítva

A nem strukturális feldolgozás gyakorlatilag az egyetlen lehetőség az aszinkron kivételek kezelésére, a szinkron kivételeknél viszont kényelmetlen: gyakran kell parancsokat hívni a kezelők telepítéséhez/eltávolításához, mindig fennáll a veszélye, hogy a regisztráció kihagyásával megtörik a program logikáját. vagy a kezelő regisztrációjának törlése.

Strukturális kivételek kezelése

A strukturális kivételek kezelése megköveteli a programozási nyelv kötelező támogatását - speciális szintaktikai konstrukciók  jelenlétét . Egy ilyen konstrukció egy ellenőrzött kód blokkot és egy kivételkezelő(ke)t tartalmaz. Az ilyen konstrukció legáltalánosabb formája (feltételes):

StartBlock ... // Szabályozott kód ... if (feltétel) then CreateException Exception2 ... Kezelő kivétel 1 ... // Kezelőkód az Exception1-hez Kezelő kivétel2 ... // Kezelőkód az Exception2-hez HandlerRaw ... // Kód a korábban nem kezelt kivételek kezelésére EndBlock

Itt a "StartBlock" és az "EndBlock" kulcsszavak , amelyek elhatárolják a vezérelt kód blokkját, a "Handler" pedig a megfelelő kivétel kezelésére szolgáló blokk eleje. Ha kivétel történik a blokkon belül, az elejétől az első kezelőig, akkor át kell térni a hozzá írt kezelőre, ami után a teljes blokk véget ér és a végrehajtás az azt követő paranccsal folytatódik. Egyes nyelvek nem rendelkeznek speciális kulcsszavakkal a vezérelt kód blokkjának korlátozására, ehelyett kivételkezelő(k) beépíthetők néhány vagy az összes szintaktikai konstrukcióba, amelyek több utasítást kombinálnak. Így például az Ada nyelvben bármely összetett utasítás (kezdet - vége) tartalmazhat kivételkezelőt.

A "RawHandler" egy kivételkezelő, amely nem egyezik az ebben a blokkban fent leírtak egyikével sem. A kivételkezelőket a valóságban többféleképpen is leírhatjuk (egy kezelő minden kivételhez, egy kezelő minden kivételtípushoz), de elvileg ugyanúgy működnek: kivétel esetén az első megfelelő kezelő ebben a blokkban található, kódja végrehajtásra kerül, ami után a végrehajtási blokk véget ér. Kivételek előfordulhatnak programozási hibák eredményeként és a megfelelő paranccsal (a példában a „CreateException” parancs) történő kifejezett generálással. A kezelők szempontjából az ilyen mesterségesen létrehozott kivételek semmiben sem különböznek a többitől.

A kivételkezelő blokkok ismételten egymásba ágyazhatnak, akár explicit módon (szövegszerűen), akár implicit módon (például egy eljárást olyan blokkban hívnak meg, amelynek magának is van kivételkezelő blokkja). Ha az aktuális blokk egyik kezelője sem tudja kezelni a kivételt, akkor ennek a blokknak a végrehajtása azonnal véget ér, és a vezérlés átkerül a következő megfelelő, magasabb hierarchiaszintű kezelőhöz. Ez addig folytatódik, amíg meg nem talál egy kezelőt, és nem kezeli a kivételt, vagy amíg a kivétel ki nem lép a programozó által megadott kezelőkből, és át nem adják az alapértelmezett rendszerkezelőnek, amely összeomlik a program.

Néha kényelmetlen egy kivétel feldolgozását az aktuális blokkban befejezni, vagyis kívánatos, hogy amikor kivétel történik az aktuális blokkban, a kezelő végrehajtson bizonyos műveleteket, de a kivétel feldolgozása magasabb szinten folytatódik ( Ez általában akkor történik, ha a blokk kezelője nem teljesen kezeli a kivételt, hanem csak részben). Ilyen esetekben a kivételkezelőben új kivétel jön létre, vagy egy korábban talált speciális paranccsal folytatódik. A kezelőkód ebben a blokkban nem védett, ezért a benne dobott kivételt magasabb szintű blokkokban kezeljük.

Blokkok garantált befejezéssel

A kivételkezelésre szolgáló vezérelt kódblokkok mellett a programozási nyelvek támogathatják a garantált befejezési blokkokat. Használatuk kényelmesnek bizonyul, ha egy bizonyos kódblokkban, függetlenül attól, hogy történt-e hiba, bizonyos műveleteket el kell végezni annak befejezése előtt. A legegyszerűbb példa: ha egy eljárás dinamikusan hoz létre valamilyen lokális objektumot a memóriában, akkor az eljárásból való kilépés előtt az objektumot meg kell semmisíteni (a memóriaszivárgás elkerülése érdekében), függetlenül attól, hogy a létrehozása után történt-e hiba vagy sem. Ezt a funkciót a következő formátumú kódblokkok valósítják meg:

StartBlock ... // Fő kód Befejezés ... // Végkód EndBlock

A "StartBlock" és "End" kulcsszavak közé zárt utasítások (főkód) szekvenciálisan hajtódnak végre. Ha végrehajtásuk során nem adnak kivételt, akkor az "End" és az "EndBlock" (lezáró kód) kulcsszavak közötti utasítások végrehajtásra kerülnek. Ha kivétel (bármilyen) történik a főkód végrehajtása közben, akkor a kilépési kód azonnal végrehajtódik, ami után a teljes blokk befejeződik, és a keletkezett kivétel továbbra is fennáll és terjed, amíg el nem kapja valamilyen magasabb szintű kivétel. kezelő blokk.

A garantált befejezéssel rendelkező blokk és a feldolgozás közötti alapvető különbség az, hogy nem kezeli a kivételt, hanem csak egy bizonyos műveletsor végrehajtását garantálja a feldolgozási mechanizmus aktiválása előtt. Könnyen belátható, hogy a garantált befejezéssel rendelkező blokk könnyen megvalósítható a szokásos strukturált feldolgozási mechanizmussal (ehhez elegendő, ha a vezérelt blokk befejezése előtt közvetlenül kiad egy kivételt, és helyesen írja be a kezelő kódját) , de egy külön konstrukció jelenléte lehetővé teszi a kód átláthatóbbá tételét és védelmet nyújt a véletlen hibák ellen.

Többnyelvű támogatás

A legtöbb modern programozási nyelv, mint például az Ada , C++ , D , Delphi , Objective-C , Java , JavaScript , Eiffel , OCaml , Ruby , Python , Common Lisp , SML , PHP , minden .NET platformnyelv stb. rendelkezik natív támogatással strukturális kivételkezelés . Ezeken a nyelveken a nyelv által támogatott kivétel előfordulásakor a hívási verem a megfelelő típusú első kivételkezelőhöz kerül, és a vezérlés átkerül a kezelőhöz.

A kisebb szintaktikai különbségeken kívül csak néhány kivételkezelési lehetőség létezik. Ezek közül a legáltalánosabb egy kivételt egy speciális operátor ( throwvagy raise) generál, maga a kivétel pedig a program szempontjából valamilyen adatobjektum . Ez azt jelenti, hogy egy kivétel létrehozása két szakaszból áll: egy kivételobjektum létrehozása és egy kivétel létrehozása ezzel az objektummal, mint paraméterrel . Ugyanakkor egy ilyen objektum felépítése önmagában nem okoz kivételt. Egyes nyelvekben a kivételobjektum bármilyen adattípusú objektum lehet (beleértve a karakterláncot, egy számot, egy mutatót stb.), más nyelveken csak egy előre meghatározott kivételtípus (leggyakrabban a neve van Exception), és esetleg , származtatott típusai (típusok -children, ha a nyelv támogatja az objektum képességeit).

A kezelők hatóköre egy speciális kulcsszóval tryvagy csak egy blokkkezdő nyelvjelzővel kezdődik (például begin), és a kezelők leírása előtt ér véget ( catch, except, resque). Több kezelő is lehet, egyenként, és mindegyik megadhatja a kezelt kivétel típusát. Általános szabály, hogy nem választják ki a legmegfelelőbb kezelőt, és az első kezelő kerül végrehajtásra, amely típuskompatibilis a kivétellel. Ezért fontos a kezelők sorrendje: ha egy sok vagy minden típusú kivétellel kompatibilis kezelő jelenik meg a szövegben az adott típushoz tartozó konkrét kezelők előtt, akkor konkrét kezelők egyáltalán nem kerülnek felhasználásra.

Egyes nyelvek egy speciális blokkot ( else) is engedélyeznek, amely akkor kerül végrehajtásra, ha a megfelelő hatókörben nincs kivétel. Gyakoribb egy kódblokk garantált befejezésének lehetősége ( finally, ensure). Figyelemre méltó kivétel a C++, ahol nincs ilyen konstrukció. Ehelyett az objektumrombolók automatikus hívása használatos. Ugyanakkor vannak nem szabványos C++ kiterjesztések, amelyek szintén támogatják a funkcionalitást finally(például MFC -ben ).

A kivételkezelés általában így nézhet ki (bizonyos absztrakt nyelven):

próbálkozzon { line = konzol . readLine (); if ( sor . hossza () == 0 ) throw new EmptyLineException ( "A konzolból kiolvasott sor üres!" ); konzol . printLine ( "Hello %s!" % line ); } catch ( EmptyLineException kivétel ) { console . printLine ( "Helló!" ); } catch ( Kivétel kivétel ) { konzol . printLine ( "Hiba: " + kivétel . üzenet ()); } else { konzol . printLine ( "A program kivétel nélkül futott" ); } végül { konzol . printLine ( "A program leáll" ); }

Egyes nyelveknek csak egy kezelője lehet, amely önállóan kezeli a különböző típusú kivételeket.

Előnyök és hátrányok

A kivételek alkalmazásának előnyei különösen a tömeges felhasználásra orientált eljárások és szoftverkomponensek könyvtárainak fejlesztésekor szembetűnőek. Ilyen esetekben a fejlesztő gyakran nem tudja pontosan, hogyan kell kezelni a kivételt (egy univerzális eljárás írásakor egy fájlból való olvasáshoz, lehetetlen előre megjósolni a hibára adott reakciót, mivel ez a reakció a használó programtól függ az eljárás), de erre nincs szüksége - elég egy A kivételt dobni, amelynek kezelőjét az összetevő vagy eljárás felhasználója biztosítja a megvalósításhoz. A kivételek egyetlen alternatívája ilyenkor a hibakódok visszaadása, amelyeket a program több szintje közötti láncon kénytelenek továbbadni, amíg el nem jutnak a feldolgozás helyére, zsúfolva a kódot és csökkentve annak érthetőségét. A kivételek használata a hibaelhárításhoz javítja a kód olvashatóságát azáltal, hogy elkülöníti a hibakezelést magától az algoritmustól, és megkönnyíti a harmadik féltől származó összetevők programozását és használatát. A hibakezelés pedig a -ban központosítható .

Sajnos a kivételkezelési mechanizmus megvalósítása erősen nyelvfüggő, és még az ugyanazon a nyelven ugyanazon a platformon lévő fordítók is jelentős eltéréseket mutathatnak. Ez nem teszi lehetővé a kivételek transzparens átadását a különböző nyelveken írt program részei között; Például a kivételeket támogató programkönyvtárak általában nem alkalmasak olyan programokban való használatra, amelyek nem azok a nyelvek, amelyekre tervezték őket, és még inkább azokon a nyelveken, amelyek nem támogatják a kivételkezelési mechanizmust. Ez az állapot jelentősen korlátozza a kivételek alkalmazásának lehetőségét például UNIX-ban és klónjaiban, illetve Windows alatt, mivel ezeknek a rendszereknek a legtöbb rendszerszoftvere és alacsony szintű könyvtárai C nyelven íródnak, ami nem támogatja a kivételeket. Ennek megfelelően az ilyen, kivételeket használó rendszerek API-jával való munkához olyan wrapper könyvtárakat kell írni, amelyek függvényei elemeznék az API függvények visszatérési kódjait, és szükség esetén kivételeket generálnának.

A kivételek támogatása bonyolítja a nyelvet és a fordítót. Csökkenti a program sebességét is, mivel a kivételek kezelésének költsége általában magasabb, mint egy hibakód kezelésének költsége. Ezért a program sebességkritikus helyein nem ajánlott kivételeket felállítani és feldolgozni, bár meg kell jegyezni, hogy az alkalmazásprogramozásban nagyon ritkák az olyan esetek, amikor a kivételek és a visszatérési kódok feldolgozási sebessége között valóban jelentős a különbség. .

A kivételek helyes megvalósítása nehéz lehet az automatikus destruktorhívással rendelkező nyelveken . Ha egy blokkban kivétel történik, akkor automatikusan meg kell hívni az ebben a blokkban létrehozott objektumok destruktorait, de csak azokat, amelyeket a szokásos módon még nem töröltek. Ezenkívül az aktuális művelet megszakításának követelménye kivétel esetén ütközik a kötelező automatikus törlés követelményével az automatikus destruktorral rendelkező nyelveken: ha kivétel történik a destruktorban, akkor a fordító kénytelen lesz törölni egy nem teljesen felszabadított objektumot. , vagy az objektum létező marad, vagyis memóriaszivárgás lép fel . Ennek eredményeként bizonyos esetekben egyszerűen tilos a meg nem fogott kivételek létrehozása a destruktorokban.

Joel Spolsky úgy véli, hogy a kivételek kezelésére tervezett kód elveszti linearitását és kiszámíthatóságát. Ha a klasszikus kódban csak ott találhatók kilépések egy blokkból, eljárásból vagy függvényből, ahol a programozó kifejezetten jelezte, akkor a kivételes kódban kivétel (potenciálisan) bármelyik utasításban előfordulhat, és nem lehet pontosan kideríteni, hogy hol fordulhatnak elő kivételek magát a kódot elemezve. A kivételekre tervezett kódban lehetetlen megjósolni, hogy a kódblokk hol fog kilépni, és minden utasítást potenciálisan az utolsónak kell tekinteni a blokkban, ennek eredményeként nő a kód bonyolultsága és csökken a megbízhatóság. [egy]

Ezenkívül az összetett programokban nagy "halom" operátorok try ... finallyés try ... catch( try ... except) vannak, ha nem használunk szempontokat.

Ellenőrzött kivételek

Néhány probléma az egyszerű kivételkezeléssel

Kezdetben (például a C ++-ban) nem volt formális fegyelem a kivételek leírására, előállítására és kezelésére: bármely kivétel a programban bárhol előállítható, és ha nincs hozzá kezelő a hívási veremben, akkor a program végrehajtása rendellenesen megszakadt. Ha egy függvény (főleg egy könyvtárfüggvény) kivételeket dob, akkor az azt használó programnak mindegyiket el kell fognia, hogy stabil legyen. Ha valamilyen oknál fogva a lehetséges kivételek egyikét nem kezelik, a program váratlanul összeomlik.

Az ilyen hatások ellen szervezeti intézkedésekkel lehet fellépni: a könyvtári modulokban előforduló lehetséges kivételek leírásával a megfelelő dokumentációban. De ugyanakkor mindig van esély a szükséges kezelő kihagyására egy véletlen hiba vagy a dokumentáció kóddal való eltérése miatt (ami egyáltalán nem ritka). A kivételkezelés elvesztésének teljes kiküszöböléséhez speciálisan hozzá kell adni egy „minden más” kivételkezelő ágat a kezelőihez (ami garantáltan elkap minden, még korábban ismeretlen kivételt is), de ez a kiút nem mindig optimális. Sőt, az összes lehetséges kivétel elrejtése olyan helyzethez vezethet, amikor súlyos és nehezen fellelhető hibákat rejtenek el.

Ellenőrzött kivételi mechanizmus

Később számos nyelv, például a Java, bevezette az ellenőrzött kivételeket . Ennek a mechanizmusnak a lényege, hogy a következő szabályokat és korlátozásokat adja hozzá a nyelvhez:

  • Egy függvény (vagy osztálymetódus) leírása kifejezetten felsorolja az összes kivételtípust, amelyet dobhat.
  • A deklarált kivételekkel függvényt vagy metódust meghívó függvénynek vagy mindegyik kivételhez tartalmaznia kell egy kezelőt, vagy viszont meg kell jelölnie az általa generált típust a leírásában.
  • A fordító ellenőrzi, hogy van-e kezelő a függvény törzsében, vagy nincs-e kivétel bejegyzés a fejlécében. A be nem jelentett és a kezeletlen kivétel jelenlétére adott reakció eltérő lehet. Például Java-ban, ha a fordító olyan kivétel lehetőségét észleli, amely nincs leírva a függvény fejlécében, és nincs benne feldolgozva, akkor a program hibásnak minősül, és nem fordítják le. A C++ nyelvben egy nem deklarált és kezeletlen kivétel előfordulása egy függvényben a program azonnali leállását okozza; ugyanakkor a deklarált kivételek listájának hiánya egy függvényben jelzi az esetleges kivételek lehetőségét és azok külső kóddal történő kezelésének standard sorrendjét.

Külsőleg (Java nyelven) ennek a megközelítésnek a megvalósítása így néz ki:

int getVarValue ( String varName ) SQLException { ... // metóduskódot dob , esetleg olyan hívásokat tartalmaz, amelyek SQLExceptiont dobhatnak } // Fordítási hiba - nem volt deklarálva vagy elkapva kivétel int eval1 ( String kifejezés ) { ... int a = prev + getVarValue ( "abc" ); ... } // Helyes - kivétel deklarálva és továbbadva int eval2 ( String kifejezés ) throws SQLException { ... int a = prev + getVarValue ( "abc" ); ... } // Helyes - a kivétel a metóduson belül van elkapva, és nem megy ki int eval3 ( String kifejezés ) { ... try { int a = prev + getVarValue ( "abc" ); } catch ( SQLException ex ) { // Kivétel kezelése } ... }

Itt a getVarValue metódus deklaráltan SQLExceptiont dob. Ezért minden ezt használó metódusnak vagy el kell fogadnia a kivételt, vagy ki kell dobnia. Ebben a példában az eval1 metódus fordítási hibát fog eredményezni, mivel meghívja a getVarValue metódust, de nem fogja el és nem deklarálja a kivételt. Az eval2 metódus deklarálja a kivételt, az eval3 metódus pedig elkapja és kezeli, mindkettő helyes a getVarValue metódus által dobott kivétel kezelésére.

Előnyök és hátrányok

A bejelölt kivételek csökkentik azoknak a helyzeteknek a számát, amikor egy kezelhető kivétel kritikus hibát okozott a programban, mivel a fordító nyomon követi a kezelők jelenlétét . Ez különösen olyan kódmódosításoknál hasznos, ahol egy olyan metódus kezdi ezt megtenni, amely korábban nem tudott X típusú kivételt dobni; a fordító automatikusan nyomon követi a használatának minden előfordulását, és ellenőrzi a megfelelő kezelőket.

Az ellenőrzött kivételek másik hasznos tulajdonsága, hogy hozzájárulnak a kezelők értelmes megírásához: a programozó jól látja a program egy adott helyén előforduló kivételek teljes és helyes listáját, és ehelyett mindegyikhez tud értelmes kezelőt írni. egy "minden esetre » Egy közös kezelő minden kivételhez, amely egyformán reagál minden abnormális helyzetre.

Az ellenőrzött kivételeknek vannak hátrányai is.

  • Olyan kivételkezelők létrehozására kényszerítenek, amelyeket a programozó elvileg nem tud kezelni, például egy webalkalmazás I / O hibákat. Ez "hülye" kezelők megjelenéséhez vezet, amelyek nem csinálnak semmit, vagy megkettőzik a rendszerkritikus hibakezelőt (például megjelenítik a kivételhívás veremét), és ennek eredményeként csak a kódot szennyezik.
  • Lehetetlenné válik új ellenőrzött kivétel hozzáadása egy könyvtárban leírt metódushoz , mivel ez megszakítja a visszafelé kompatibilitást . (Ez igaz a nem könyvtári módszerekre is, de ebben az esetben a probléma kevésbé jelentős, mivel az összes kód elérhető és újrahasznosítható).

E hiányosságok miatt ezt a mechanizmust gyakran megkerülik, ha ellenőrzött kivételeket használnak. Például sok könyvtár az összes metódust úgy deklarálja, hogy néhány általános kivételosztályt dob ​​(például Exception), és a kezelők csak az ilyen típusú kivételekhez jönnek létre. Ennek az az eredménye, hogy a fordító arra kényszeríti a kivételkezelők írását ott is, ahol objektíven nincs rá szükség, és lehetetlenné válik a források olvasása nélkül meghatározni, hogy a deklarált kivételek mely alosztályait dobja a metódus, hogy különböző kezelőket akasszon rájuk. őket. Helyesebb megközelítés a metóduson belüli, a hívott kód által generált új kivételek elfogása, és szükség esetén a kivétel továbbadása – a metódus által már visszaadott kivételbe „csomagolása”. Például, ha egy metódust úgy változtatnak meg, hogy a fájlrendszer helyett az adatbázishoz kezd hozzáférni, akkor maga is elkaphat SQLExceptionés eldobhat egy újonnan létrehozott metódust IOException, megjelölve az eredeti kivételt okként. Általában ajánlott először pontosan azokat a kivételeket deklarálni, amelyeket a hívó kódnak kezelnie kell. Mondjuk, ha a metódus bemeneti adatokat kér le, akkor célszerű deklarálnia IOException, ha pedig SQL lekérdezésekkel működik, akkor a hiba természetétől függetlenül deklarálnia kell SQLException. Mindenesetre alaposan meg kell fontolni a metódusok által kiváltott kivételek halmazát. Ha szükséges, érdemes saját kivételosztályokat létrehozni, amelyek az Exception-ből vagy más megfelelő ellenőrzött kivételekből származnak.

Kivételek, amelyek nem igényelnek ellenőrzést

Lehetetlen az összes kivételt általánosságban ellenőrizhetővé tenni, mivel egyes kivételes helyzetek természetüknél fogva olyanok, hogy előfordulásuk a program bármely vagy szinte bármely pontján lehetséges, és a programozó nem tudja megakadályozni. Ugyanakkor értelmetlen az ilyen kivételeket a függvényleírásban feltüntetni, hiszen ezt minden függvénynél meg kellene tenni anélkül, hogy a program világosabbá válna. Alapvetően ezek a két típus egyikéhez kapcsolódó kivételek:

  • Kivételek, amelyek olyan súlyos hibák, amelyek "elméletileg" nem fordulhatnak elő, és amelyeket a programnak általában nem szabad kezelnie. Ilyen hibák előfordulhatnak a programhoz viszonyított külső környezetben és azon belül is . Ilyen helyzet például egy Java program futásidejű hibája. Ez potenciálisan lehetséges bármely parancs végrehajtásával; a legritkább kivételektől eltekintve egy alkalmazási programnak nem lehet értelmes kezelője egy ilyen hibához - elvégre ha a végrehajtási környezet nem működik megfelelően, amint azt a kivétel ténye is jelzi, nincs garancia arra, hogy a kezelő is helyesen végrehajtva.
  • Futásidejű kivételek, amelyek általában programozói hibákhoz kapcsolódnak. Az ilyen kivételek a fejlesztő logikai hibái vagy a kód elégtelen ellenőrzése miatt merülnek fel. Például egy inicializálatlan (null) mutató elérésekor fellépő hiba általában azt jelenti, hogy a programozó vagy elmulasztotta a változó inicializálását valahol, vagy nem ellenőrizte, hogy a memória valóban le lett-e foglalva a dinamikus memória lefoglalásakor. Az első és a második is a programkód javítását igényli, nem pedig kezelők létrehozását.

Logikátlan és kényelmetlen az ilyen hibákat kivenni a kivételkezelő rendszerből, már csak azért is, mert néha mégis elkapják és feldolgozzák őket. Ezért az ellenőrzött kivételekkel rendelkező rendszerekben néhány kivételtípus kikerül az ellenőrzési mechanizmusból, és a hagyományos módon működik. A Java-ban ezek a java.lang.Error - végzetes hibák és a java.lang.RuntimeException - futásidejű hibákból örökölt kivételosztályok, amelyek általában kódolási hibákhoz vagy a programkód elégtelen ellenőrzéséhez kapcsolódnak (rossz argumentum, hozzáférés nulla hivatkozással, kilépés a tömb határain kívül , helytelen monitorállapot stb.).

A „javítható” és a „végzetes” hiba közötti határ nagyon önkényes. Például egy asztali program I/O hibája általában "javítható", és erről tájékoztatni lehet a felhasználót, és folytatni tudja a program futtatását. Egy webes szkriptben ez is „végzetes” - ha megtörtént, akkor valami rossz történt a végrehajtási környezettel, és meg kell állnia egy üzenet megjelenítésével.

Lásd még

Jegyzetek

  1. 13 - Joel a szoftverről . Letöltve: 2012. október 7. Az eredetiből archiválva : 2012. október 22..

Linkek