Murphy általános érvényű törvénye a szoftverfejlesztésben sem kerülhető ki: ha valami félremehet, akkor félre is fog menni. Ezt szerencsére maguk a szoftverfejlesztők is tudják, és az idők során változatos módszereket dolgoztak ki arra, hogy ha egy függvény (metódus) hívása során valami hiba lép fel, akkor azt jelezzék a hívó felé.
Ez legtöbbször a 0 érték visszadása volt, vagy éppen, ha sokféle hiba léphetett fel, a 0 volt a jó, és a pozitív számok jelölték a hibakódot, de az is lehetett, hogy a -1 volt a hiba, és a többi a jó. Vagy fordítva.
Vagy lehetett bármi más magic numbert visszaadni a hibás végrehajtódást jelzendő - később, ahogy a számítógép-architektúrák fejlődtek, és az int egyre szélesebb lett, egyre tágabb tér nyílt egyre mágikusabb számok használatára.
Akkoriban a szoftverfejlesztők még acélos férfiemberek voltak, akik ezeket a számokat mind fejből tudták - legalábbis a visszaemlékezések szerint, valójában rengeteg időt (=pénzt) emésztett fel, hogy kiderítsék, hogy aznap épp mi jelenti a jót és mi a rosszat.
Nem csoda, ha ma ilyen módszerrel már csak Chuck Norris dolgozik - az objektum-orientált személet ebben is nagy váltást hozott. Az OOP-s srácok lényegesen optimistábbak voltak, és azt mondták, hogy hiba, az csak kivételes esetben fordulhat elő, és gyorsan kitalálták a kivétel-kezelést, ami ma már - apróbb különbségekkel ugyan, de - minden OOP nyelvben megtalálható. A kivétel-kezelés jó, a kivétel-kezelés szép. Két egyszerű ökölszabály:
- ha hibába ütközünk, dobjunk exception-t
- ha olyat hívunk, ami kivételt válthat ki, zárjuk try-catch blokkba
Hogy mi? Vegyük például a következő kódrészletet:
Stream stream = MyFileManager.OpenFile("TheresNoSuchFile.txt"); using (StreamReader streamReader = new StreamReader(stream)) { Console.WriteLine(streamReader.ReadToEnd()); }Egyszerű, megnyitjuk egy fájlt a saját FileManager-ünk segítségével, és végigolvassuk. De mi a helyzet, ha a megnyitni szándékozott fájl nem létezik?
Amennyiben erőforrás-takarékos módon egy null-t adtunk vissza, akkor kapunk egy közönséges NullReferenceException-t. Ez legalább annyira közönséges, mint a közönséges levesteknős, vagy a közönséges húsleves.
Egy biztos: túl sok mindenre nem fog fény derülni a kivételből, jótanácsként is maximum azt kapjuk, hogy használjuk a "new" kulcsszót új objektumpéldány létrehozásához.
Ha úgy érezzük, hogy azon már túl vagyunk, hogy a new operátorral ismerkedjünk, szálljunk magunkba, és nézzük meg mit csinálhatnánk jobban. Dobhatnánk pl. egy FileNotFoundException-t a remek fájlmegnyitó-függvényünkben, egy jól fejlett message-el, amiben elmagyarázhatjuk magunknak, hogy mit nem és miért nem. Hát nem szebb?
Persze van, amikor tényleg overkill az exception dobás, pl. egy statikus gyártófüggvényben lehet jobb megoldás a return null; (főleg, ha esetleg pont azért hoztunk létre, hogy ne a konstruktorból kelljen hányni a kivételt). Ez egyedi elbírálás kérdése, de jól gondoljuk meg, nem csinálunk-e magányos Chuck Norrist magunkból, csak hogy öt sor kóddal kevesebbet írhassunk.
Aki azt gondolná, ilyet csak amatőrök követnek el: ezt a hibát konkrétan a Microsoft is elkövette a .NET Framework 1.x verziójában (a 2.0 után javították). Ha az assemblynkbe nem beágyaztnuk, hanem hozzácsatoltunk egy erőforrást - azaz az mezei fájlként ott figyelt a .dll mellett, és szükség esetén a futtatókörnyezet megnyitotta és berántotta - pont ez volt a helyzet. Ha tényleg ott volt a fájl, semmi gond, de ha véletlenül (gonoszak direkt) letöröltük, akkor az 1.1 egy szép NullReference-szel örvendeztetett meg, és lehetett pislogni, hogy mi van. A 2.0 megmondja: nincs meg ez a fájl, öreg, nem lesz ez így jó.
A témára egyébként Richard Grimes Fusion Workshopjában bukkantam (egészen pontosan itt), ami most tök offtopic - a Fusion-ről, azaz a .NET assembly betöltő mechanizmusáról szól -, viszont szintén nagyon érdekes, érdemes elolvasni.

2 megjegyzés:
Szemléletes a példa, tetszik. Az exception dobása mellett (mikor? milyet? mikor érdemes egyedi exception származtatni, ill. mikor érdemes a beépítettet használni) egyébként szerintem egy kisebb misét megér még az elkapása, illetve hogy ha elkaptuk, akkor okos-e tovább dobni, esetleg újat dobni...
Egyébként .NET exceptionökkel egy bajom mindig van: miért nem lehet adni nekik egy olyan publikus tulajdonságot, ami kiírja a Message-et, a StackTrace-t, illetve az InnerException Message és StackTrace-ét is rekurzívan (vagyis azt, amit az ASP.NET-es hibaoldalon kapunk). Csomószor van szükségem erre az infóra és minden egyes alkalommal kézzel kell ezt összeraknom.
Általános exception design guidelineok Krzysztof Cwalinától: Design Guidelines Update: Exception Throwing.
Érdemes átrágni a Framework Design Guidelines könyv ide vonatkozó részét.
Az, hogy mikor érdemes továbbdobni az exceptiont, és mikor érdemes sajátot csinálni helyette, az adott szituációtól függ.
Pl. ha van egy provideres adatrétegünk, ami alatt lehet SQL Server, Oracle, MySQL, és mi ezt egy absztrakt felületen keresztül érjük el, akkor jogos lehet az elvárás, hogy ne SQL Server, Oracle, MySQL specifikus exception-öket kapjunk hiba esetén, hanem az absztrakt provider fedje el ezt is, és általános, adatréteg-specifikus kivételeket dobjon tovább.
A property hiányára megoldás lehet egy saját ős-exception osztály, de legtöbbször megteszi a .ToString() metódus is.
Megjegyzés küldése