Java a výjimky – zpracování výjimek
Známe již objektovou hierarchii výjimek a víme, z jakých základních pohledů nahlížet na vzniklou výjimku. V tomto článku se podíváme na špatné příklady práce s výjimkami, jako je například požírání výjimek, a vysvětlíme si, na jaké konstrukce dát pozor.
Než se zakousneme do výjimek pořádně, uděláme si čisto na stole a špatné návyky hodíme do koše. Následující případy demonstrují špatnou práci s výjimkami.
Požírání výjimek
Pohlcení výjimky bez jakéhokoli zpracování patří mezi základní chyby, kterých bychom se měli vyvarovat. Snad nejhorší možnost nastane v případě, kdy deklarujeme chycení výjimky na úrovni předka (Exception
). Zvažme následující příklad:
public class Account{
…
…//business logika
…
public long getAccountBalance(){
long balance = 0;
try{
.
//operace pro zjištění zůstatku
.
}catch(Exception e){}
return balance;
}
}
Máme třídu Account
, která představuje účet. Třída má metodu getAccountBalance()
, která vrací zůstatek na účtu. Zjištění zůstatku, implementované v metodě getAccountBalance()
, je uzavřeno v chráněném bloku try
. K tomuto chráněnému bloku náleží blok zpracování – catch
, kde jsou všechny výjimky odvozené od Exception
pohlceny, a to právě díky prázdnému tělu tohoto bloku.
Představme si příklad, kdy se zjištění zůstatku provádí operacemi nad databází. V určitou chvíli se přeruší spojení s databází, které vyvolá vznik výjimky. Takto vzniklá výjimka je zachycena naším prázdným catch
blokem a metoda je ukončena vrácením nulového zůstatku. Klient se pak může vzteky potrhat, neboť systém se navenek chová zcela korektně.
Další nepříjemnou vlastností výše uvedeného kódu je, že případná výjimka není nikde zaznamenána. Nevíme tak, jestli se jedná skutečně o výjimku signalizující chybu systému, nebo o jiný typ výjimky, například že účet je zablokován.
Základní pravidla pro práci s výjimkami
- Nikdy nepohlcujte výjimky.
- Porušíte-li pravidlo jedna, připojte vysvětlující komentář.
Pravidlo dva existuje pro výjimečné případy, které neznamenají chybu. Základní API Javy například definuje výjimku InterruptedException
, která se pojí s vlákny a jejich přerušením. I v takovém případě je dobrým zvykem vložit do prázdného catch
bloku vysvětlující komentář.
Ošetřování Exception
Ošetřování výjimek na úrovni předka (Exception
) není závažné jako pohlcování výjimek, ale o to je zrádnější. Představme si kód, který je nucen zpracovat kontrolovanou výjimku. Tento případ ilustruje metoda fooBar
, která volá metodu doSomethingFishy
, jež deklaruje kontrolovanou výjimku:
public void doSomethingFishy() throws CheckedException{
…
…
}
public void fooBar(){
doSomethingFishy();
…
…
}
Základní chybou je ošetřovat nebo propagovat výjimku CheckedException
na úrovni předka, tedy Exception
. Oba případy ilustrují následující ukázky.
Ošetření
public void fooBar(){
try{
doSomethingFishy();
}catch(Exception e){
//ošetření Exception
…
…
…
}
}
V prvním případě jsme se sice rozhodli kontrolovanou výjimku ošetřit, ale učinili jsme tak na úrovni předka Exception
. Na první pohled se to nemusí jevit jako chybné, ale je potřeba si uvědomit, že ošetřením na úrovni Exception
zachytíme i všechny nekontrolované výjimky, které mohou signalizovat vážné selhání systému, například nenalezení odpovídající třídy (ClassNotFound
).
Další úskalí představuje otázka, jak s takto obecnou výjimkou naložit. Jakým způsobem ji ošetřit? Máme v daném kontextu dostatek prostředků pro její zpracování? Většinou se v takovýchto případech použije pouhý výpis výjimky. Prostým „chytáním“ výjimky na úrovni Exception
připravujeme aktuální kontext o možnost zotavení, neboť na výjimku můžeme nahlížet pouze jako na objekt Exception
a nikoli jako na její generalizaci poskytující specifické informace. V případě propagace výjimky stavíme vyšší vrstvy před ten samý problém s obecnou (nekonkrétní) výjimkou.
Pokud je nezbytně nutné ošetřit výjimku na úrovni Exception
, musíme oddělit výjimky kontrolované od nekontrolovatelných, tak jako v následující upravené ukázce:
public void fooBar(){
try{
doSomethingFishy();
}catch(Exception e){
//otestujeme jestli se jedná o běhovou výjimku
if(e instanceof RuntimeException){
//Běhovou výjimku necháme pokračovat
throw (RuntimeException) e;
}
//zpracujeme všechny kontrolované výjimky
…
…
…
}
}
Díky testu na běhovou výjimku jsme zajistili bezproblémový průchod těchto závažných výjimek. Na druhou stranu existují opodstatněné případy, kdy se na úrovni Exception
výjimka chytá. Typickým příkladem je Controller
webové aplikace, kdy je zapotřebí, nejlépe na centrálním místě, zpracovat všechny výjimky a nabídnout uživateli rozumnou formou informaci o vzniku výjimky a umožnit mu tak další práci.
Ošetření – na pořadí záleží
Výše uvedené řešení, s kontrolou na běhovou výjimku, lze přepsat pomocí deklarativního zpracování. Jedná se o to, že každý chráněný blok může mít několik bloků catch
pro zpracování různých typů výjimek. V našem příkladu bychom upravili kód následujícím způsobem:
public void fooBar(){
try{
doSomethingFishy();
}catch(RuntimeException re){
throw re;//běhovou výjimku necháme pokračovat
}catch(Exception e){
//zpracujeme všechny kontrolované výjimky
…
…
…
}
}
Nejdříve jsme deklarovali zpracování RuntimeException
a teprve potom zpracování Exception
. Pořadí, v jakém jsme deklarovali jednotlivé typy výjimek, je důležité z hlediska zpracování. Z předchozího článku již víme, že RuntimeException
je potomek Exception
. Pokud bychom deklarovali pořadí výjimek opačně, tedy nejdříve zpracování Exception
, nedošlo by nikdy k zpracování (v našem případě opětovného vyhození běhové výjimky) RuntimeException
. RuntimeException
by skončila v prvním bloku, který jí odpovídá, což je blok s Exception
. Obecně musíme mít na paměti, že nejdříve se v rámci několika catch
bloků deklarují specializované typy výjimek a teprve poté se deklarují výjimky obecnějšího charakteru
Propagace
public void fooBar() throws Exception{
doSomethingFishy();
}
Druhý příklad, ve kterém jsme deklarovali vyhození výjimky Exception
, je principielně špatný. Nutíme totiž všechny, jež volají metodu fooBar
, aby výjimku zpracovali nebo propagovali. Odpovědnost za výjimku jsme alibisticky nechali na klientech využívajících API . Diky tomu, že vyhazujeme Exception
, nutíme klienty vyhodit Exception
a nebo ji ošetřit, aniž by k tomu měli prostředky.
Deklarace Exception
se násobně pronáší do dalších API, neboť volající si není schopen s takto obecně definovanou výjimkou poradit. V konečném stadiu nastane veliký problém při práci s kontrolovanými výjimkami a klient bude nucen testovat, jestli je chycená výjimka určitého typu.
Deklarace několika kontrolovaných výjimek
Pokud jsme mluvili o deklarovaném vyhazování Exception
, pak musíme zmínit druhý extrém, vyhazování několika výjimek.
public void fooBar() throws CheckedExceptionX, CheckedExceptionY, CheckedExceptionZ{
doSomethingFishy();
}
Klientský programátor je nucen ošetřovat zbytečné množství výjimek. V takovýchto případech je vhodnější zvolit obecnější výjimku. Od této výjimky je pak možné vytvořit jednotlivé specializace na úrovní objektů a nebo výjimku zřetězit do obecnější výjimky. Díky tomu můžeme v budoucnu ošetřit další typ výjimky, aniž by se to dotklo klientského programátora.
public class ApplicationException extends Exception{
…
…
…
}
public class ConfigApplicationException extends ApplicationException{
…
…
…
}
public class DataApplicationException extends ApplicationException{
…
…
…
}
public class Foo{
private void configure() throws ConfigApplicationException{
…
…
…
}
private void postData() throws DataApplicationException{
…
…
…
}
public void fooBar() throws ApplicationException{
configure();
postData();
}
}
Řetězení výjimek jakožto druhý způsob si s dalšími dobrými technikami necháme na příští díl našeho putování.
Starší komentáře ke článku
Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.
Mohlo by vás také zajímat
-
Nové trendy v doménách pro osobní projekty – DIY, LIVING a LIFESTYLE
9. prosince 2024 -
ZONER Webmail jako první v Česku přináší BIMI s VMC
11. července 2024
Nejnovější
-
Jak rozšířit úložiště Macu za pětinovou cenu?
16. prosince 2024 -
Nové trendy v doménách pro osobní projekty – DIY, LIVING a LIFESTYLE
9. prosince 2024 -
Jak chránit webové stránky před Web/AI Scrapingem
27. listopadu 2024 -
Jaký monitor je nejlepší k novému Macu Mini?
25. listopadu 2024