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:
- soubor se povedlo načíst
- soubor se nepovedlo načíst, protože zatím neexistuje (a pak pokračovat dle plánu)
- 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
error414 #1
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.
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
johno #3
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.
Radek Svoboda #4
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ší).
Jakub Podhorský #5
#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
error414 #6
#5 Jakube Podhorský, jj mas pravdu ty bloky mohu vypustit, nevim proc me to predtim nefungovalo.
David Majda #7
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.
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.
dizzyn #9
Vyjimky zhorsuji vykon aplikace, pokrocile jsou jen v tom, ze jsou slozite na implementaci, jinak neni o co stat.
David Grudl #10
#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.
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. :)
David Grudl #12
#7 Davide Majdo, dobré odkazy. Vidím to podobně jako Joel Spolsky.
emilk #13
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?
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.
Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.