Gyakorlati alapok
Kivételesen beszéljünk a kivételkezelésről
Bevezetés
A programok rengetegféle funkciót teljesíthetnek, ezért futásuk számtalan ok miatt szakadhat meg, például:
-
Godzilla rálép az atomerőműre és emiatt elmegy az áram,
-
a takarítónő lecsapja a főkapcsolót,
-
a program nem fér hozzá a kijelölt erőforráshoz → távoli adatbázisban kell lekérdezést futtatnia, de az internetes kapcsolat megszakad,
-
megtelik a műveleti memória,
-
a programozó rosszul, hibákkal telve programozza a kódot, ezért programozási hibák (bug) keletkeznek benne → nullával való osztás.
A fenti példákból jól láthatjuk, hogy kétféle futási probléma merülhet fel:
-
amelyik nem rajtunk múlik, például áramkimaradás, távoli szerver, erőforrás összeomlása,
-
amelyik kizárólag rajtunk múlik, ezért adott körülmények között kísérletet tehetünk lekezelésére, megszüntetésére. Ezek tipikusan a programozási hibák. Sőt néha az is lehetséges, hogy ezeket nem is maga a Java-alkalmazás programozója, hanem egy programozási szinttel lejjebb előzetesen a JVM rendszerprogramozója követi el. Programozótól függetlenül ezen hibákra aztán hackerek vadásznak, hogy a felfedezett exploitokat (biztonsági réseket) valamilyen módon pénzzé tegyék. De természetesen az alkalmazás-szintű hibát magának a programozónak kell lekezelnie.
A Java-alkalmazás futásának megszakadása esetén 2 végrehajtási irány lehetséges:
-
meg kell kísérelni visszahozni a programot egy újfent futtatható állapotba,
-
ha ez nem lehetséges, olyan korrekt állapotba kell terelni, hogy a program viszonylag "normálisan" álljon le.
A fentiekből nyilvánvalóan következik, hogy a programozónak előre kell látnia azon hibalehetőségeket, amelyek csakis rajta vagy a futási körülményeken múlnak és elhárításukra határozott, jól megvalósítható programozási lépéseket kell tennie. A Java nyelv ezt natívan képes támogatni.
A Java-nyelv a programfutási hibát kivételnek (exception) nevezi. Lekezelése előre beépítetten, osztályszinten történik (java.lang.Throwable), amely által egységes, a különböző hibatípusokat is jól detektáló feldolgozást képes biztosítani.
Programfutási hiba során a JVM olyan művelettel vagy problémával szembesül, amelyet nem tud végrehajtani. Ezt érzékelve a JVM létrehoz egy kivételkezelő objektumot, amelyik fontos információkat tartalmaz a hiba fajtájáról és a program aktuális állapotairól. A pillanatnyilag kialakult programozási helyzetet úgy is nevezhetjük, hogy a hibás metódus dobott egy kivételt (throw an exception), más szavakkal: kiváltott egy kivételt.
Az alábbi Java-kódban nem túl elegáns módon elkövetjük a "klasszikus" programozási hibát: 0-val osztunk. Nagy valószínűséggel a hibát így direktben senki sem fogja elkövetni, de minél bonyolultabb egy program, benne minél több változó értéke változik folyamatosan (sőt minél több párhuzamos programszálon fut), annál nagyobb a valószínűsége az ehhez hasonló kivételeknek. A program futása hibaüzenettel megszakad, de a hibadetektálás magas szintű, hiszen a JVM képes volt megállapítani a hiba jellegét és főbb tulajdonságait:
public class Main {
public static void main(String[] args) {
System.out.println(4 / 0);
}
}
Végeredmény (hibaüzenet):
Exception in thread "main" java.lang.ArithmeticException:
/ by zero
at Main.main(Main.java:152)
A magas szintű hibadetektálás ellenére a program futása mégis megszakad; a hiba utáni rész már nem fut le:
public class Main {
public static void main(String[] args) {
System.out.println(4 / 0);
System.out.println("Ez már nem fog
lefutni.");
}
}
Végeredmény (hibaüzenet):
Exception in thread "main" java.lang.ArithmeticException:
/ by zero
at Main.main(Main.java:152)
Alapvető programozási követelmény tehát olyan kód megalkotása, amely megbízható és "figyelmes" módon képes még időben "elkapni" a lehetséges hibákat, kivételeket (catching an exception). Ám ezen követelményen túllépve maga a Java is tovább szigorítja a kivételek lekezelését: definiál ellenőrzött és nem ellenőrzött kivételeket.
Az ellenőrzött kivételeket egyenesen kötelező lekezelni, másként fordítási hibát kapunk.
Ilyen jellegű kóddal már találkozhattunk például a Lottózzunk, avagy töltsük ki a telitalálatos szelvényt! című fejezetben. Ott a programszál futásának késleltetését kötelező volt a kivételkezelés szabványos try-catch blokkjába tennünk, másként hibaüzenetet kaptunk (az már más kérdés, hogy a catch blokkot nem kezeltük le):
try{
Thread.sleep(200);
}catch(InterruptedException ie){}
}
Az ellenőrzött kivételek bevezetésével jóval megbízhatóbb kód állítható elő, hiszen a programozó a kötelező hibafigyelés és kezelés miatt egyszerűen rá van kényszerítve biztonságos kód megalkotására.
Ám az is nyilvánvaló, hogy mindennek bizony vannak ésszerűségi határai. Ugyanis elméletileg minden művelethez hozzárendelhetünk valamilyen szintű kivétel-, hibakezelést (azaz megkíséreljük a program minden egyes műveletét programozói ellenőrzés alá vonni), amelynek következménye azonban átláthatatlanul megnövekedett kódtömeg és veszélyes mértékű túlbonyolítás lehet.
A kompromisszumos megoldás tehát -amit a Java nyelv megalkotói már megtettek-, a legfontosabb, a "legveszélyesebb", mondhatni tipikus kivételek típusos definiálása és szabványos, osztályszintű hierarchiába rendezett publikálása azon okból, hogy a felhasználó programozó a minél biztonságosabban futó kód érdekében "elkaphassa" azokat.