Na navigaci | Klávesové zkratky

To Throw or not to Throw Exception? (1.)

V programovacích jazycích se pro detekci chyb obvykle používá jeden ze tří způsobů:

  • testování návratové hodnoty
  • testování přes globální proměnnou či funkci
  • výjimky

Výjimky se považují za nejpokročilejší ze všech metod. Mají úzkou vazbu na jazyk a jejich užití vypadá velmi elegantně.

Na základě delších teoretických úvah si však troufám tvrdit, že elegantně skutečně jen vypadají. Než však budu moci toto tvrzení obhájit, musíme trošku zabřednout do teorie.

Co jsou to chyby?

Abychom se vůbec byli schopni domluvit, určeme si některé pojmy. Jde o ryze soukromé definice, tedy nezaměňujte význam zde uvedených termínů chyba a varování za ten, který mají třeba v PHP nebo jiném jazyku.

S pojmem chyba velmi úzce souvisí pojem funkce. Funkce je základní úsek programu, nad kterým budeme nadále uvažovat. Za chybu považujme takový stav, kdy funkce není schopna dokončit svou úlohu. Ukončí se předčasně a nějakým způsobem volající funkci tuto skutečnost sdělí.

Během vykonávání funkce může dojít i k výjimečnému stavu, který je třeba oznámit programátorovi, ale nebrání funkci v dalším běhu. Tomuto říkejme varování.

Mezi varováním a chybou je zásadní rozdíl. Zatímco chyba se oznamuje volající funkci (tedy program sděluje programu), tak varování je adresováno programátorovi. Proto je v případě chyb klíčový její kód (ať už vyjádřen jakkoliv), kdežto u varování spíš vysvětlující popis.

Životnost chyby

Všimněte si, že z výše uvedených definic vyplývá, že „nepříjemné“ stavy, které si funkce umí sama ošetřit a zpracovat, neoznačujeme za chyby. A dále, že chyba končí společně s ukončením funkce. Nadřazená funkce má totiž vlastní hodnotový žebříček chyb a proto ji třeba vůbec nemusí zajímat chyby vzniklé v podfunkcích.

Příklad:

function test() {
  ...
  $s = file_get_contents('cache.dat');
  if ($s === false) ...
  ...
}

Funkce se pokusí načíst obsah keše ze souboru. Pokud soubor neexistuje, dojde k chybě uvnitř file_get_contents. Pro jednu funkci je to chyba, pro druhou naopak záležitost, se kterou počítá.

Narážíme na první úskalí

V uvedeném příkladě jsme se dopustili vážného pochybení. Předpokládáme totiž, že pokud se soubor nepodařilo načíst, tak je to z důvodu, že neexistuje. Jenže co když je to tak, že nemáme ke čtení dostatečná práva? Nebo cache.dat je adresářem? Nebo došlo k úplně jiné neočekávané chybě?

Funkce test() by potřebovala rozlišit tři stavy:

  1. soubor se povedlo načíst
  2. soubor se nepovedlo načíst, protože zatím neexistuje (a pak pokračovat dle plánu)
  3. soubor se nepovedlo načíst z jiného důvodu (a pozor – je jen na funkci test(), jestli z toho vyvodí chybu, varování nebo situaci vyřeší jinak!)

K rozlišení případů je třeba návratový kód ve strojově zpracovatelné podobě (např. číslo). V případě třetí varianty by bylo vhodné informaci doplnit o textový popis a také jména souborů a čísla řádků (callstack), kde k chybě došlo (pomiňme nyní, že file_get_contents je interní funkce).

Vedle toho má návratová hodnota false poněkud malou vypovídací hodnotu…

Dobrá, dejme tomu, že v případě chyby nevrátíme false, ale strukturu, která tyto informace bude obsahovat. To je záležitost zcela snadno realizovatelná v netypových jazycích (PHP) a naprosto neřešitelná v jazycích typových (Delphi, C++, Java).

Optikou výjimek

Záchranou by mohly být výjimky. Především dokáží zmíněnou strukturu velmi snadno vygenerovat:

  • kód je představován třídou výjimky a případně číslem chyby
  • textový popis lze také doplnit
  • automaticky zaznamená jméno souboru a pozici, kde k ní došlo

Navíc výjimky umí obejít mechanismus návratových hodnot, takže je možné je předávat i v typových jazycích.

Ovšem – je tu jeden velký rozdíl od nastíněného způsobu chápání chyb. Zatímco naše chyby končí s ukončením funkce, výjimky končí až s jejich ošetřením.

Tedy objevivší se problém zanořené funkce se automaticky stává kritickým problémem i funkcí nadřazených. To přináší docela jiný náhled na věc. A není nakonec takovéto chápání lepší?

Odpověď si nechám na příště :-)

Komentáře

  1. error414 #1

    avatar

    Takze tohle je duvod proc pouzivas v dibi misto throw tak return?

    Nevyhodi se vyjimka ale jen se vrati instance vyjmecne tridy s informacemi o chybe?

    Jen je spatne ze porad musim pouzivat bloky try a catch, kdyby sme implemetovali vlastni tridu na ktera by mela stejny vzor jako exception tak by jsme nepotrebovali vubec ty bloky.

    před 18 lety | reagoval [3] johno [5] Jakub Podhorský
  2. lubos #2

    jednej veci nerozumiem – ako to, ze chyba konci spolocne s ukoncenim funkcie? ved aj v nadradenej funkcii predsa musis osetrit chyby vzniknute v podradenej funkcii. takze tvoje chyby (ok, povedzme, ze len niektore, ale to sa tyka aj vynimiek) v podradenej funkcii koncia tiez az ich osetrenim niekde inde
    ale mozno nechapem, kam tym celym mieris, takze si pockam na pokracovanie

    před 18 lety | reagoval [10] David Grudl
  3. johno #3

    avatar

    DGX: Vôbec netuším kam teraz mieriš. Ak chceš odôvodniť return new MyBeautyfulException; ako spomínal #1 error414, tak som zvedavý, ale myslím, že to vôbec nebude v istých prípadoch dobrý napád.

    Príklad a vysvetlenie si nechám ako komentár na inokedy.

    před 18 lety
  4. Radek Svoboda #4

    avatar

    Používám vyjímky v C++, nevím detaily jak je to v PHP, ale trošku nerozumím v článku, co je na nich špatně. Pokud nadřazená funkce má řešit (nebo naopak ne) vyjímky vzniklé ve volané funkci, jaký je rozdíl mezi nějakým switchem s testováním variant návratové hodnoty a sadou catch bloků?

    Vyjímky mají tu obrovskou výhodu, že se MUSÍ někde zpracovat. Není nutné stále testovat návratové hodnoty, ale na druhou stranu se ani nedá na to kašlat (což je typický nešvar kódu bez vyjímek – ála .. ono to přece vždycky ten fajl najde ..)

    Další výhodou je propagace nahoru, takže to skutečně řeší až ta funkce, která ví, co s tím. Jednoduše lze tak implementovat jeden z idiomů ABORT, RETRY, IGNORE (ale i další).

    před 18 lety
  5. Jakub Podhorský #5

    avatar

    #1 error414, pokud se nepletu tak blok try a catch ti bude fungovat jenom ve spojení s tím že vyjímku vyhodíš pomočí throw při jejím vracení budeš muset kontrolovat chyby pomocí if a instanceof

    ale nejsem si jistý ale z principu vyjímek by to takhle mělo být

    před 18 lety | reagoval [6] error414
  6. error414 #6

    avatar

    #5 Jakube Podhorský, jj mas pravdu ty bloky mohu vypustit, nevim proc me to predtim nefungovalo.

    před 18 lety
  7. David Majda #7

    avatar

    O vyjímkách vs. chybových kódech už psalo hodně lidí. Nějaké články jsem kdysi zmínil ve svém zápisníku.

    Hezký článek na toto téma taky nedávno napsal Damien Katz.

    před 18 lety | reagoval [12] David Grudl
  8. Pavel #8

    Od té doby, co jsem se začal učit psát v Javě, mě PHP tak znechutilo, že už jsem přes rok nesplodil ani jednu „echo řádku“… ? Nechám se překvapit, co z Davida vyleze, pač si například v Javě jiný efektivnější přístup zpracovaní chyb ani nedovedu představit.

    před 18 lety
  9. dizzyn #9

    Vyjimky zhorsuji vykon aplikace, pokrocile jsou jen v tom, ze jsou slozite na implementaci, jinak neni o co stat.

    před 18 lety | reagoval [11] Borek
  10. David Grudl #10

    avatar

    #2 lubosi, Pokud za chybu považuješ stav, který vede k ukončení funkce bez toho, aby splnila svůj úkol (v příkladu file_get_contents nemohla načíst obsah souboru), pak v nadřazené funkci už o chybu nejde – v příkladu s touto situací funkce test() počítá a rozhodně to nevede k jejímu ukončení – tedy to není dle definice chyba.

    Samozřejmě chyba v file_get_contents může vést i k chybě v test(), ale už je jen věcí funkce test(), jestli se to tak stane.

    před 18 lety
  11. Borek #11

    #9 dizzyne, OOP zhoršuje výkon aplikace, pokročilé je jen v tom, že je složité na implementaci, jinak není o co stát. :)

    před 18 lety
  12. David Grudl #12

    avatar

    #7 Davide Majdo, dobré odkazy. Vidím to podobně jako Joel Spolsky.

    před 18 lety
  13. emilk #13

    … objevivší se problém zanořené funkce se automaticky stává kritickým problémem i funkcí nadřazených

    to zalezi, jak jako programator nastalou situaci posoudis
    brani ti neco zpracovat danou vyjímku hned za blokem, kde vznikla, pokud je to vsem nadrazenym funkcim vzdy jedno?

    před 18 lety
  14. mka #14

    Na výjimkách je hezké pravě to, že je můžu ošetřit i v nadřazené funkci, kdežto chybu obvykle ošetřit musím. Takže můžu obsluhu mimořádných stavů stejného typu napsat jen jednou ve „vyšší“ funkci a nemusím se s tím patlat na každém místě, kde může nastat.

    Další z výhod výjimek je jejich hierarchie – pokud mě nezajímají detaily, můžu ošetřit dohromady všechny IOException, pokud potřebuju, můžu věnovat zvláštní péči např. FileNotFoundException (která je podtyp IOException)

    V kompiovaných jazycích navíc v každé funkci můžu deklarovat, jaké výjimky může házet. Ve „vyšších“ funkcích je buď musím ošetřit, nebo opět deklarovat, že stále ošetřeny nejsou, anebo mi kompilátor vynadá, že si mám udělat pořádek v kódu.

    před 18 lety

Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.


phpFashion © 2004, 2024 David Grudl | o blogu

Ukázky zdrojových kódů smíte používat s uvedením autora a URL tohoto webu bez dalších omezení.