Známe to všichni – píšu kód, termín hoří, chci co nejdříve
mít něco, co se alespoň dá spustit, napíšu další řádek kódu a najednou mě IDE
začne upozorňovat, že mám ošetřit nějakou výjimku. No dobře, nechám IDE, ať mi
pomůže, a ono se mi vygeneruje toto:
try {
return serviceClass.getConstructor(URL.class).newInstance(serviceUrl);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Co s tím? Výjimky nechci programově ošetřovat. Když
nastanou, je problém v nějaké konfiguraci, aplikace si s tím stejně
neporadí. Potřebuji se o nich jen nějak dozvědět.
Jak to neudělat dobře
Nechat to tak, jak je. A smazat TODO komentáře, ať mi to
v IDE nesvítí. Chyba se mi někam vypíše, snad to bude někam, kde to půjde
přečíst. Kód ale bude pokračovat, jako by k chybě nedošlo, takže se objeví
nová chyba, která už bude pravou příčinu problému trochu mlžit.
Nebo zrušit odchytávání výjimek a deklarovat u metody
propagaci výjimek o úroveň výše. Stejný problém se ale bude muset řešit
v místě, kde se metoda volá. Pokud se to i tam vyřeší stejně, bude v
aplikaci přibývat metod s velkým množstvím deklarovaných výjimek, které
nebude chtít nikdo řešit, ale bude muset.
Jak to udělat předpisově
Odchytit výjimky a vyhodit nové, které se ale nemusí ošetřit
(Runtime exception). Do těchto výjimek ještě nejlépe přidat informace o
kontextu, v jakém k chybě došlo – v ukázkovém případě například
informace o objektu serviceClass a
parametru serviceUrl.
Vyhazovat pro každý typ odchytávané výjimku „obalovací“
Runtime výjimku vždy stejného typu? Pokud chceme být předpisový, tak ne. Každá
výjimka může nést jiné informace o kontextu (např. u NoSuchMethodException by na rozdíl od ostatních měla být informace
o metodě). Dále by se neměla komplikovat situace volající metodě, kdyby chtěla
výjimky řešit. Hledat příčinu chyby procházením hierarchie výjimek není
nejlepší řešení (už jen proto, že místo v hierarchii, kde pravou příčinu
hledat, s největší pravděpodobností nebude nikde popsáno a už vůbec nebude
deklarováno, že se to v budoucnu nezmění).
Vyřešit situaci předpisově je poměrně časově náročná záležitost.
Otázka je, zda vynaložené úsilí bude odpovídat přínosu.
Jak to udělat normálně
Odchytit výjimky a vyhodit nové, které se ale nemusí ošetřit
(Runtime exception). Chceme si ale co nejméně komplikovat život
s vytvářením nových výjimek. A to i za cenu, že další práce
s výjimkou bude komplikovanější. Můžeme si to dovolit? Tuto otázku bychom
si měli pokaždé položit a podle odpovědi se přizpůsobit.
Pokud nastane NoSuchMethodException,
co mi stačí, abych situaci vyřešil? Pravděpodobně bude nějaký nepořádek
v knihovnách. Bude mi stačit v logu výpis chyby včetně stacktrace, ve kterém bude název třídy a
metody. Nic víc nepotřebuji.
Pokud chceme být poctiví, použijeme jako „zabalovací“
výjimku nějakou již existující Runtime výjimku se stejným významem. Na to to
chce trochu zkušeností. Kdo dokáže najít během pár minut Runtime ekvivalent
k NoSuchMethodException?
Jestliže jsme Runtime ekvivalent nenašli, vyrobíme si ho.
Všichni na projektu by měli mít přehled o těchto výjimkách – ať je ve stejných
situacích taky používají.
Častěji kolem sebe ale vidím, že v těchto situacích
vznikají vlastní Runtime výjimky, které svým názvem popisují příslušnost do
nějakého modulu našeho projektu – např. pro náš příklad ServiceCreatorException. Ano, urychlí to vyřešení ošetření výjimky.
Ne, nezlepší to řešení situace, až výjimka opravdu nastane.
Jak to hacknout
Zajímavou možností je transformace výjimky na Runtime
s tím, že výjimka zůstane stále stejného typu. V tomto
článku je popis tohoto triku. Lze pak napsat:
public static void main(final String[] args) {
Unchecked.rethrow(new IOException());
}
Výstupem je:
Exception in thread "main" java.io.IOException
at cz.cca.Unchecked.main(Unchecked.java:19)
Toto je velice elegantní řešení – výjimka není ničím
obalená, prostě jako by byla Runtime.
Pozor, článek se jmenuje „Hack of the day“. Ne každý bude
nakloněn tomuto řešení. Já osobně nechápu, proč toto ještě není v Java
standardu.
Jak na to po Googlovsku
Google si je evidentně vědom četnosti řešení tohoto problému
a proto má ve svém projektu guava
třídu Throwables,
která toto řeší. Jednoduše se řekne, co za výjimky nechcete řešit a to se
propaguje dál obalené do java.lang.RuntimeException.
try {
someMethodThatCouldThrowAnything();
} catch (IKnowWhatToDoWithThisException e) {
handle(e);
} catch (Throwable t) {
Throwables.propagateIfInstanceOf(t, IOException.class);
Throwables.propagateIfInstanceOf(t, SQLException.class);
throw Throwables.propagate(t);
}
someMethodThatCouldThrowAnything();
} catch (IKnowWhatToDoWithThisException e) {
handle(e);
} catch (Throwable t) {
Throwables.propagateIfInstanceOf(t, IOException.class);
Throwables.propagateIfInstanceOf(t, SQLException.class);
throw Throwables.propagate(t);
}
A tady už to začíná být na hraně. Je správně, když se
výjimky zabalí do obecné java.lang.RuntimeException?
throw new RuntimeException?
A co cesta nejmenšího odporu – zabalit výjimku do java.lang.RuntimeException? Jde asi o
nejrychlejší způsob, jak situaci vyřešit. Skoro bych to zařadil do kapitoly
„Jak to udělat normálně“ až na to, že pokud bude třeba výjimku programově
řešit, bude se muset odchytávat java.lang.RuntimeException.
Zpracovat výjimku java.lang.RuntimeException
je zas o fous složitější než zpracovat vlastní výjimku typu ServiceCreatorException. Přeci jenom ServiceCreatorException nese alespoň informaci
o tom, že k chybě došlo v našem modulu ServiceCreator.
Asi každý nástroj pro kontrolu kódu má pravidlo
kontrolující, že nikde v kódu není vyhození obecné výjimky java.lang.RuntimeException. Důvodem je,
že by se nemělo RuntimeException
odchytávat a i když se odchytne, špatně se zjišťuje pravá příčina výjimky.
Co když jsem si ale jistý, že výjimku, kterou obaluji do RuntimeException, nebude nikdy nikdo
potřebovat odchytávat? V tom případě by všechny argumenty proč nevyhazovat
java.lang.RuntimeException padly. Nikdy si nemůžeš být jistý, že nikdy nikdo nebude
potřebovat výjimku odchytávat.
Jaká je praxe?
Kromě Google guava lze vyhazování java.lang.RuntimeException vysledovat na mnoha velkých projektech.
Stačí si stáhnout jejich zdrojové kódy a prohledat je na výskyt „throw new
RuntimeException“. Namátkou v Apache CXF je vyhození RuntimeException na 347 místech.
Proč je tato praktika tak rozšířená, když ji velké
akademické autority odsuzují?
Podle mého jednoduše proto, že si lidé řeknou: „Tohle
opravdu nebude nikdy nikdo odchytávat. A když náhodou jednou ano, tak to pak
vylepším“.
Autor článku: Jan Herma, CCA Group a.s.
Autor článku: Jan Herma, CCA Group a.s.
Skoda, ze neni uveden autor.. Pote bych se rozhodl zda precist ;)
OdpovědětVymazat