Jak fungují výjimky? rubrika: Programování: Jiné
Často vídám užívat výjimky, které vyjadřují nějaký business koncept. Dokonce se to tu objevuje jako dobré tipy v odpovědích. Víte jak výjimky fungují a proč by se k takovým věcem neměly používat?
PS. Neštiťte se detailů na úrovni hardware a hlavně CPU.
Jejich nasazení si uvědomuje mnoho lidí, definice bývá problém.
Co výjimka je
Objektové paradigma programování je jak každý školák ví založené na objektech, jejich stavu a chování. Mezi chování řadíme i vzájemné zasílání zpráv. Takové objekty zveřejňují své rozhraní - sadu veřejných metod s pevně stanovenými mantinely pro vstup a výstup. Neveřejná část zůstává ostatním objektům skryta. Tomuto principu říkáme zapouzdření. Stejný princip používají i metody, které představují chování objektu. Ať už jsou součástí veřejného rozhraní objektu či nikoliv, sami mají rovněž veřejné rozhraní složené z pevně daného vstupu a výstupu - implementace je ostatním metodám skryta. Elementární věci, které každý zná, ale evidentně si je hodně lidí kteří se dostanou k psaní kódu neuvědomuje, jinak by nedošli k tak pomýlenému využití výjimek.
Za konzistentní stav programu (či jeho části) považujeme takový stav, kdy program celý (či jeho zvolená část) je schopno dodržet smlouvu danou výše popsaným rozhraním. Dojde-li k situaci, kdy se vybraná část programu nachází ve stavu, který ji neumožňuje tuto smlouvu dodržet, dochází k výjimečnému stavu a přichází na řadu výjimky.
Výjimka je zpráva celku o neschopnosti dodržet smluvené rozhraní, tedy neschopnost na základě vstupu poskytnout definovaný výstup. Jedná se o objekt reprezentující tento výjimečný stav který je předáván zpětně skrze již uskutečněnou hiearchii volání ("probublávání na povrch"). Umožňuje ostatním objektům na výjimečnou situaci reagovat. V praxi reakce znamená zamezení šíření nekonzistentního stavu na další části aplikace. Mechanismus probublávání umožňuje na příhodném místě na vzniklou situaci reagovat - reagovat tedy může až objekt, který má zdroje potřebné k "napravení chyby".
Co výjimka není
Nezávisí na četnosti výskytu jakéhokoliv stavu. Konzistentní a nekonzistentní stav se nedefinuje dle pravidelnosti, ale právě dle schopnosti vybrané části splnit "podepsanou smlouvu".
Neslouží k řízení program flow - už to tu kolegové programátoři zmínili hodněkrát a nemá cenu to obhajovat. S každou další výjimkou použitou pro větvení zdvojnásobíte počet stavů, do kterých se může program dostat a jeho chování v té době bude nepředvídatelné. Nikdy totiž nevíte, na jakou kombinaci výjimek narazíte.
EDIT: Jen bych dodal, že se tu řešila otázka toho, zda-li je nevalidní hodnota na vstupu důvodem k vzniku výjimky. Odpověď se skrývá právě v tom, zda-li způsobuje nemožnost splnit smlouvu. Jak tu ovšem uživatel rarouš zmínil, opravdu je to důvod k vyhození ? Já k tomu dodávám následující otázky:
- Nelze vzniklou chybu opravit na místě, případně ji delegovat jinam ?
- Je návratový typ metody správný ? Pokud lze změnou návratového typu či návrhu zamezit způsobení výjimky, bavme se o možných chybách v návrhu.
Považte následující kód (omlouvám se za formátování, není psán v IDE)
public class MatematickyProcesor{ public static float vypoctiPodil(float delenec, float delitel) throws { if(delitel == 0) throw new IllegalArgumentException("Nelze dělit nulou"); return x / y; } }
Jak lze v tomto případě spravit výstup ? Dělení nulou je pro nás problém. Neexistuje nic co bychom mohli udělat pro to, abychom chybu na místě opravili. Nicméně pokud bychom vrátili jakékoliv číslo, volající metoda by jej zcela určitě interpretovala jako správný výsledek. Proto musíme předat zprávu jinak - pomocí výjimky.
Následuje další ukázka
public class NejakeOkno extends JFrame { private JButton tlacitkoOdeslat; private JTextField jmeno, prijmeni; List<Clovek> lide; private void inicializujUkazkovyListener(){ tlacitkoOdeslat.addActionListener(new ActionListener(){ @Override public void actionPerformed(ActionEvent e){ try{ Clovek novyClovek = new Clovek(jmeno.getText(), prijmeni.getText()); lide.add(novyClovek); } catch(MarihuanaDetectedException e){ //Něco dělej a předstírej, že jsi zachytil výjimku, která není součástí kontroverze a že se příklad v Javě moc nepovedl ;) } } }); } } public class Clovek{ String jmeno, prijmeni; public Clovek(String jmeno, String prijmeni) throws MarihuanaDetectedException{ if(prijmeni.equals("German")){ throw new MarihuanaDetectedException("Drogy netolerujeme, promiňte"); } this.jmeno = jmeno; this.prijmeni = prijmeni; } }
V konstruktoru třídy Člověk můžete vidět špatné použití výjimky mimo kontext, pro který byla určena, tedy pro řízení toku programu. Otázka zní, kdy chceme testovat, že někoho nechceme ? A jak se zachovat, když nechceme naši kolekci znesvětit uživateli drog ? Výjimku použít z výše zmíněných důvodů nemůžeme, ale vytvořený objekt také vrátit nechceme. Řešení je následující a mnoha lidem se nebude líbit, nicméně je to jeden z mých patternů, který používám velmi rád. Zkrátka omezíme vstup ! Jak to uděláme levně bez výjimek za cenu ztráty trochu popisnosti ? Trochu upravíme stávající kód, přidáme jednu další třídu, která bude sloužit jako validační schránka.
public class NejakeOkno extends JFrame { private JButton tlacitkoOdeslat; private JTextField jmeno, prijmeni; List<Clovek> lide; private void inicializujUkazkovyListener(){ tlacitkoOdeslat.addActionListener(new ActionListener(){ @Override public void actionPerformed(ActionEvent e){ Prijmeni vlozenePrijmeni = Prijmeni.vytvorPrijmeni(prijmeni.getText()); if(vlozenePrijmeni == null){ //něco dělej, neodešli data, vyhoď varovné okno o nutnosti vyhození počítače z okna } Clovek novyClovek = new Clovek(jmeno.getText(), prijmeni.getText()); lide.add(novyClovek); } }); } } public class Clovek{ String jmeno; //Zde může být dokonce po zbytek aplikace pouze String, třída příjmení slouží jako validační schránka Prijmeni prijmeni; public Clovek(String jmeno, Prijmeni prijmeni){ this.jmeno = jmeno; this.prijmeni = prijmeni; } } public class Prijmeni{ String prijmeni; private Prijmeni(String prijmeni){ this.prijmeni = prijmeni; } public Prijmeni vytvorPrijmeni(String prijmeni){ if(prijmeni.equals("German")) return null; return new Prijmeni(prijmeni); } }
Je tu ale ještě třetí věc - KONTROVERZE - tedy výjimky které symbolizují stav, kdy nedošlo k porušení rozhraní, nicméně stav vráceného objektu není žádoucí. Nežádoucí stav není věc, kterou by syntaxe používaná k vytváření signatur metod/tříd dokázala zachytit. Tyto výjimky můžeme, ale nemáme povinnost zachytávat.
public class NejakeOkno extends JFrame { private JButton tlacitkoOdeslat; private JTextField jmeno, prijmeni; List<Clovek> lide; private void inicializujUkazkovyListener(){ tlacitkoOdeslat.addActionListener(new ActionListener(){ @Override public void actionPerformed(ActionEvent e){ try{ Prijmeni vlozenePrijmeni = Prijmeni.vytvorPrijmeni(prijmeni.getText()); Clovek novyClovek = new Clovek(jmeno.getText(), prijmeni.getText()); lide.add(novyClovek); } catch(SpatnePrijmeniException e){ e.printStackTrace(); //Zobraz JOptionPane varovný dialog o této závažné chybě } }); } } public class Clovek{ String jmeno; //Zde může být dokonce po zbytek aplikace pouze String, třída příjmení slouží jako validační schránka Prijmeni prijmeni; public Clovek(String jmeno, Prijmeni prijmeni){ this.jmeno = jmeno; this.prijmeni = prijmeni; } } public class Prijmeni{ String prijmeni; public Prijmeni(String prijmeni)throws SpatnePrijmeniException{ if(prijmeni.equals("German")) throw new SpatnePrijmeniException("Drogy netolerujeme, promiňte"); this.prijmeni = prijmeni; } }
O správnosti tohoto posledního řešení můžeme vést diskusi, samozřejmě existují další možnosti, sami s nimi přijďte. Už tak je tento post moc dlouhý.
Komentáře
-
joinmax
:
Až na to, že výjimky jsou součástí oné smlouvy a mohou být vyhozeny nejen v důsledku jejího nedodržení (chybný vstup), ale i v důsledku vnějších okolností. Vyhazované výjimky jsou stejně tak součástí rozhraní/smlouvy jako typ a vlastnosti návratové hodnoty, metody, parametry...
—
3.5.2013
-
Anonym
:
Dobrá připomínka. A co takový nápad, že výjimky jsou smlouva pro případ nedodržení původní smlouvy ? Jinak se omlouvám, svůj post jsem po této reakci značně rožšířil ;)
—
3.5.2013
-
Anonym
:
@joinmax To je dost ujetý, viď. Já v žádným rozhraní nevidím žádnou Exception, tudíž není součástí kontraktu. To, že je slušnost vyhazované výjimky zdokumentvat, je věc jiná. Ale není to kontrakt. (Omlouvám se všem, co píší v Haskellu, pro ně tento komentář neplatí.)
—
3.5.2013
-
Petr Voneš
:
V pripade SOAP webovych sluzeb je "vyjimka" (SOAP fault) soucasti kontraktu. To ze je skoro nikdo ve skutecnosti nedefinuje a spoleha na to, ze dana serverova platforma (typicky .NET nebo Java) sama nakonec zkonvertuje neocekavanou/neosetrenou vyjimku v kodu na nejaky genericky SOAP fault (ktery je stejne klientem nevyuzitelny na nic chytrejsiho nez OK/ERROR) neni kontrakt.
—
3.5.2013
-
Anonym
:
@Petr Faulty jsou určený i k vyjadřování stavů - proto jsou součástí kontraktu. To výjimky nejsou.
—
3.5.2013
-
Anonym
:
Vidím že tohle téma bude velmi přínosné a spoustu si toho ujasníme. Trefa do černého.
—
3.5.2013
-
Petr Voneš
:
@rarous: V tom je mozna to nedorozumeni. Metoda vraci navratovou hodnotu, ta muze a casto i obsahuje (ocekavany) chybovy stav operace. Pokud vraci jiny nez navratovy typ (v tomto pripade SOAP fault), nejedna se podle mne o ocekavany stav, ale o vyjimku. Soucasti kontraktu je proto, aby z ni bylo mozne pripadne nacist dalsi informace a podle toho se na klientu zachovat (smulou je, ze 'detail' element SOAP fault paketu nema presnou specifikaci a muze byt zavisly na serverove platforme). Ostatne proto je i SOAP fault typicky vytvaren specifickou vyjimkou na strane serveru.
—
3.5.2013
-
joinmax
:
Pro Rarouše: vyspělejší jazyky podporují kontrolované výjimky (tzn. explicitně deklarované a dokonce ti program ani nedovolí přeložit, pokud je neošetřuješ) a taky ta smlouva/kontrakt nemusí být jen to, co lze popsat prostředky daného jazyka.
—
3.5.2013
-
joinmax
:
BTW: je to zajímavé téma a chci se o tom rozepsat víc... ale je mi líto si s tím dávat tu práci a psát pro malou uzavřenou skupinu lidí, tak to asi někdy hodím spíš do blogu.
—
3.5.2013
-
Johny Patera
:
@joinmax - predem diky pak za link na post.
—
6.5.2013
-
v6ak
:
joinmax: Vyspělé jazyky: je tím myšleno i něco dalšího než Java? Protože jinde jsem to (bohužel) neviděl. Ve Scale bych byl za ně rád, ale bohužel nejsou. (Anotace @throws e jen kvůli kompatibilitě s Javou.)
—
10.5.2013
-
v6ak
:
@rarouš: Součástí rozhraní nejsou jen názvy metod, počty jejich parametrů, typy parametrů a typy návratových hodnot. (Dynamické jazyky toho mimochodem vyžadují ještě méně.) Součástí kontraktu je i nějaká logika. Pokud budu třídit seznam, bude zeznam podle daného komparátoru setříděný. Zdaleka ne vše poskytují obvyklé jazyky (a asi ani vše nemohou, asi bychom se rychle dostali k problému zastavení...), ale součástí kontraktu to je. I pokud použiju jazyk, který neumí checked exceptions, je součástí kontraktu možnost vrhnout výjimku. Operace dělení u integerů vrhne výjimku <=> dělitel je roven nule. Na některých datových typech se může přidat možnost vrhnout výjimku, pokud výsledek nelze vyjádřit přesně (např. pokud vznikne perioda).
A jsou tu i checked exceptions, které říkají "něco se pokazilo, ale s tím se musí počítat". Typicky to "něco" pochází z okolí, jde zejména o IOException. Programátor použije DataInputStream zcela v souladu s kontraktem, ale myš přehlodá síťový kabel a data nedojdou. Nastane IOException. To je též součástí kontraktu (zvlášť patrné to je v jazyce Java, který vám nedovolí ji nechat jen tak být).
—
10.5.2013
-
Anonym
:
Checked exceptions jsou jedním z největších přešlapů Javy, není vhodné to uvádět jako dobrý příklad. Dynamické jazyky většinou nemají formální kontrakty, takže tam si můžeme představit ledasco. Jako návrhář nějakého kotraktu nemůžu a nesmím vědět, jaké výjimky může konkrétní implementace vyhazovat. Právě proto nejsou součástí kontraktu.
—
10.5.2013
-
joinmax
:
Tady už snad jen: DNFTT.
—
10.5.2013
-
joinmax
:
BTW: kam mj. vedou nekontrolované výjimky (a nevhodné technologie) se momentálně můžeš podívat na svém webu - moc pěkná chybová stránka s NullReferenceException :-)
—
11.5.2013
-
Anonym
:
Hmm, ani Javička, ani jiný mně známý jazyk, nemá checked NPE/NRE. Ale existují jiné postupy prevence NRE, které jsem pravda před pěti lety neznal, viz třeba tady http://rarous.net/weblog/447-zatocime-s-null.aspx.
—
12.5.2013
-
joinmax
:
To je komické, tak chyba na rarous.net je tam stále: System.NullReferenceException: Object reference not set to an instance of an object. Microsoft .NET Framework Version:2.0.50727.5466; ASP.NET Version:2.0.50727.5456
—
13.5.2013
-
Petr Voneš
:
Prvnim krokem k naprave problemu je umet jej pojmenovat ;-)
—
13.5.2013
-
Honza Břešťan
:
Je tam uz par mesicu, nekde jsem ji tu uz taky zminoval :) Na blogu, kde se resi "jak zatocit s nullem", je to obzvlast hezka "red flag".
—
13.5.2013
-
Anonym
:
@joinmax Byla, je a bude.
—
13.5.2013
Pro zobrazení všech 4 odpovědí se prosím přihlaste:
Nebo se přihlaste jménem a heslem:
Komentáře