Fórum Root.cz
Hlavní témata => Vývoj => Téma založeno: D 24. 01. 2015, 18:07:09
-
Kdy použít jistě aserci a kdy vyhodit výjimku?
Vím, že aserce slouží pro zachycení stavu, který nesmí nikdy nastat. Podle mne však, pokud mám funkci, která dělí dvě čísla předaná parametrem, pak dělení nulou by nemělo nastat... nicméně raději vyhodím výjimku -- protože ta nemůže být vypnuta direktivou kompilátoru a klient ji může zachytit -- je to tak správné?
Jak sem četl, aserce by měla být aplikována také jen na soukromé a chráněné části kódu a výjimky především na veřejné metody. V mnoha zdrojácích jsem se však s asercí v soukromím kódu nesetkal, většinou je tam také výjimka.
-
Nevim, v jakem jazyce to resis, tak to popisu, jak to je v Jave, ale jinde to bude podobne. A predpokladam, ze nechavame stranou asserce v testech.
Asserce jsou poor man's design by contract. Muzes v nich popsat jake jsou predpoklady danych kusu kodu (tenhle parametr neni nula...) a co se jsou vlastnosti jeho vysledku (vraci se neprazdny seznam...). V Jave jsou udelane tak, ze pokud neni asserce splnena (a asserce jsou povolene), tak vylitne vyjimka. Takze ani s assercemi se vyjimek nezbavis, jen
- mas hezci zapis
- mas urcene, co ti vyleti
Casto se asserce pouzivaji pri vyvoji a testovani, na produkci jsou vypnute (t.j. pokracuje se v krasojizde i kdyz nekdo porusil kontrakt, bozi at nas chrani pred nasledky). Ze to je dobra metoda, to bych si netroufal tvrdit.
V poslednim odstavci mas vicemene pravdu - vstup od cizich mas kontrolovat a v definovanych stavech hazet vhodnou vyjimku (IllegalArgumentException...), asserce by mely "dozajistovat" ze uvnitr tvych kusu kodu je vsechno OK.
Ja uz leta asserci nenapsal, vsechny kontoroly pouzivaji jine vyjimky. Ale protoze neni uplne pekne mit vsude if ... throw, neni od veci misto toho pouzit nejakou knihovnu, ktera dela to same v citelnejsi podobe (assertThatNotNull(variable, "comment"), assertNotEmpty(list, SpecializedExceptionClass)....), cimz ziskas pekny zapis i jistotu, ze na produkci i pri testovani je vse stejne.
-
Assert se používá pro případy, které nikdy nemůžou nastat - a pokud přesto nastanou, jedná se o chybu uvnitř TVÉHO kódu, nikoli cizího, nebo dokonce jen špatným vstupem.
Tedy pokud ta knihovna, která dělí dvě čísla, je použita pouze uvnitř tvého programu a daná funkce není exportována ven, dal bych tam assert. Máš totiž zajistit už předem, aby ten assert nevylítl, a za to jsi zodpovědný pouze ty.
Pokud je ta knihovna nějak veřejná a tedy by ji mohl použít někdo zvenčí, pak rozhodně dej výjimku. Pokud bych zavolal knihovnu a ta by někde uvnitř spadla na assert, myslel bych si, že je špatně napsaná (a hlavně bych nevěděl, co s tím mám udělat). Pokud vylítne výjimka, alespoň s tím něco můžu udělat.
Přiznám se ale, že občas pro ryze soukromé programy (které spouštím jen já) z lenosti používám assert i tam, kde by měla být výjimka (například „otevři soubor“, „assert(soubor je otevřený)“); je to nejrychlejší způsob a stejně při každém běhu programu koukám na výstup já (takže tomu rozumím) a zároveň vím, že nemám asserty vypínat.
-
@Ondra Satai Nekola "Nevim, v jakem jazyce to resis (...)" To je dobrá připomínka, moje zmatení souvisí také s rozdíly v jazycích, které jsem načítal toto téma (C++, Java, Python, Ada). Například Ada má už _pre-conditions_ a _post-condition_ -- tedy DbC.
@Aleš Janda "Pokud je ta knihovna nějak veřejná" Tím myslíte rozhraní knihovny, tedy public třídy a metody, nebo obecně, pokud jde někam do produkce?
Díky za vaše odpovědi!
-
Assert v Javě označuje předpoklady, které musí být splněné, aby kód fungoval správně. Když nejsou splněné, neznamená to nutně, že kód bude fungovat špatně - proto je kontrola assercí při vývoji zapnutá (aby se odhalila místa, kde se něco používá špatně), ale při provozu zapnuté být nemusí. Výjimka naproti tomu znamená, že tohle určitě nemůže dopadnout dobře.
Třeba píšu funkci, u které vím, že teď ji nikdy nebudu volat s null jako parametrem. V funkci je na to assert a při psaní neřeším případ, že by parametr byl null. Pokud ji ale s null někdo zavolá a má vypnuté asserty, může se stát, že ta funkce proběhne bez chyby. A nebo někde spadne na NullPointerException (v Javě). Pokud ale vím, že by ta funkce s null parametrem fungovala špatně, dám tam if a vyhození NPE.
-
@Aleš Janda "Pokud je ta knihovna nějak veřejná" Tím myslíte rozhraní knihovny, tedy public třídy a metody, nebo obecně, pokud jde někam do produkce?
Tím myslím rozhraní knihovny. Ale třeba i v rámci jednoho týmu. Pokud může někdo jiný než TY způsobit assert, dej tam výjimku. Ať už to je kolega, který bude volat tvoji funkci knihovny (ale ta knihovna jinak veřejná nebude), anebo se tam ta čísla dostanou nějak ze vstupu, patří tam výjimka. Assert je pro místa, která můžeš způsobit jen ty, a používá se typicky pro elementární kontrolu, že kód ještě pořád funguje (například po refaktoringu).
-
Obecně v mnoha zdrojích jsem našel: use asserts for things that should never happen, což ale podle platí i pro spoustu výjimek. Rozdíl vidím v tom, že aserce neslouží k řešení situace, oproti výjimce, která toto umožní. Také use exceptions when checking parameters passed to public or protected methods and constructors, je ve shodě s odpověďmi. Nicméně to je asi pořád více méně ještě závislé na jazyku, ve kterém to řeším.
-
Assert v Javě označuje předpoklady, které musí být splněné, aby kód fungoval správně. Když nejsou splněné, neznamená to nutně, že kód bude fungovat špatně - proto je kontrola assercí při vývoji zapnutá (aby se odhalila místa, kde se něco používá špatně), ale při provozu zapnuté být nemusí. Výjimka naproti tomu znamená, že tohle určitě nemůže dopadnout dobře.
Třeba píšu funkci, u které vím, že teď ji nikdy nebudu volat s null jako parametrem. V funkci je na to assert a při psaní neřeším případ, že by parametr byl null. Pokud ji ale s null někdo zavolá a má vypnuté asserty, může se stát, že ta funkce proběhne bez chyby. A nebo někde spadne na NullPointerException (v Javě). Pokud ale vím, že by ta funkce s null parametrem fungovala špatně, dám tam if a vyhození NPE.
Chybí tu popis jedné zásadní vlastnosti assert v Javě: pokud podmínka selže, je vyhozena výjimka java.lang.AssertionError což je potomek java.lang.Error a stejně jako všichni ostatní potomci java.lang.Error nemá být tato výjimka nikdy chytána a jakkoliv zpracovávána - znamená totiž že došlo k situaci která je neopravitelná (podobně jako například java.lang.OutOfMemoryError). Toto je zásadní rozdíl proti tomu kdy si výjimku hážu sám, to si totiž můžu vybrat potomka java.lang.Exception kde se počítá s tím že je aplikace může chytat a pokoušet se zpracovat.
-
Chybí tu popis jedné zásadní vlastnosti assert v Javě: pokud podmínka selže, je vyhozena výjimka java.lang.AssertionError což je potomek java.lang.Error a stejně jako všichni ostatní potomci java.lang.Error nemá být tato výjimka nikdy chytána a jakkoliv zpracovávána - znamená totiž že došlo k situaci která je neopravitelná (podobně jako například java.lang.OutOfMemoryError). Toto je zásadní rozdíl proti tomu kdy si výjimku hážu sám, to si totiž můžu vybrat potomka java.lang.Exception kde se počítá s tím že je aplikace může chytat a pokoušet se zpracovat.
To je správně, výjimka z assertu se nemá zachytávat - pokud selže assert, je to stav aplikace, se kterým se nepočítá. Pokud s danou situací počítám, musím upravit aplikaci tak, aby assert neselhával.
-
Jestli se nepletu, tak např. v Pythonu se právě `assert` může zachytávat a to pomocí `AssertionError`. Toť rozdíl oproti Javě. Při optimalizaci Pythonu se ale `assert` odstraní, podobně jako při použití direktivy kompilátoru.
-
Selhani assertu je takovej stav, kdyz se algoritmus dostane do stavu, ve kterym bejt nesmi
takze assert pouzit pro kontrolu vnitrniho stavu algoritmu, nikdo z vnejsku to nesmi byt schopen vyvolat!
na osetreni vstupu vzdy vyhodit vyjimku (obvykle NPE, IAE), NPE se taky vyhazuje i automaticky, pro ohlidani null se i daji pouzit @NonNull a @Nullable anotace (napriklad je pro to podopra v Eclipse), ale pak je treba toto pouzivat vsude, ale daji se timto zpusobem ohlidat NPE (prekladac ziska informace o mistech, kde muze dochazet k NPE)
-
Ten příklad s dělením je hezký. Jak už tu zaznělo, jde-li vstup od uživatele, nesmí se to řešit asertem. Asert odchytává situace, kdy je chyba v kódu, a měl by mít za následek ukončení aplikace.
Jinak od výjimek se v některých jazycích/knihovnách upouští. Někdo tvrdí, že výjimky jsou jen převlečené goto. Já bych spíš řekl zamaskovaný long jump. Většinou je lepší vracet chybu jinak (například výstupním parametrem nebo vláknově lokální proměnnou jako v C), výjimky mají už podle názvu řešit výjimečné situace, ne chybu na vstupu.
-
výjimky mají už podle názvu řešit výjimečné situace, ne chybu na vstupu.
Název může být chybný. Kdy je vhodné použít výjimky, záleží na konkrétním jazyku a jeho konkrétní implementaci.
-
ja bych rekl, ze vyjimka pro chybu na vstupu dava ladici informaci o tom, ze kdyz ji nezachytime, program spadne a vypise, k cemu doslo (pripadne muzeme vyjimku odchytit, ale pak jsme vedomi toho, ze k chybe na vstupu dojit muze) Vono v jave stejne muzes budto vratit normalni hodnotu, vratit null nebo vyhodit vyjimku. Null muze mi i nekdy svuj vyznam ho vracet, navic muze posunou problem o patro vejs (a zkreslit ho), takze je opravdu nejlepsi vratit vyjimku (ktera presne popise co se stalo). Vyjimka je zpusob, jak metoda muze oznamit, ze neni schopna skoncit "standardnim" zpusobem
-
@zboj Třeba Google v určitých aplikací prý výjimky zakazuje (myslím že to je nějaké style-guide pro chrome?). Zajímalo by máte názor na výjimky v C++, ve světle C++11/14. Díky
-
@zboj -- pardon ... zajímal by mne váš názor na výjimky v C++ -- ta nemožnost opravit příspěvek je otravná!
-
Výjimky jsou v C++ dobré v situaci, kdy máte dobře navrženou aplikaci, rozdělené akce atp. Dokážete tak zajistit smysluplnou recovery i z věcí, o kterých ani netušíte, že mohou nastat. Nicméně mají i řadu nevýhod, z toho největší jest asi ztráta kontextu, co se vlastně rozbilo (aby se to mohlo opravit) a boj s realitou, že v C++ existují pravděpodobně až miliardy řádek různých knihoven, které použití výjimek moc nenapomáhají a člověk dojde k názoru, že mu výjimky jen komplikují vlastní řešení (které je co do rozsahu významně menší než API, které používá).
Assert zase funguje jen v debug konfiguraci a jak mnozí uvádějí, jde o kontrakt, že daná situace nemá (nesmí) nastat. Osobně jej rozšiřuji i na situace druhu invalid_argument, ale souhlasím s tím, že takový argument nesmí pocházet od uživatele. Assert, na rozdíl od výjimky, jasně sděluje, že situace nastat nesmí a je třeba ji řešit, a navíc poskytuje vývojáři kompletní dump včetně stacku a lokálních proměnných, takže je nesrovnatelně snazší opravit assert než výjimku std::out_of_range.
Zjednodušeně, asserty dávám tam, kam nechci, aby se kód nikdy dostal, výjimky tam, kde doufám v recovery. A často kombinuji obojí, tedy dám assert před výjimku. Ale diskuse, kdy házet výjimku a kdy vrátit chybu pomocí return value, to je asi na delší povídání.
-
@zboj Třeba Google v určitých aplikací prý výjimky zakazuje (myslím že to je nějaké style-guide pro chrome?). Zajímalo by máte názor na výjimky v C++, ve světle C++11/14. Díky
C++ není úplně stavěné na programování bez výjimek. Největší problém je v tom, že z konstruktoru, kopírovacího konstruktoru a nebo z operátoru přiřazení nemůžete vrátit nějaký návratový kód, podle kterého se dá poznat úspěch/chybový kód. Samozřejmě toto se dá obejít - nepoužíváním kopírovacích konstruktorů atp., pokud potřebujete při kopírování provést operaci, která může selhat (alokaci paměti) - ale to je fakt otrava.
Google C++ coding style výjimky zakazuje, zjednodušeně, proto, protože:
- vypnuté výjimky prodlužují kompilaci a zvětšují paměťové nároky
- výjimky dělají kód trochu méně čitelný, protože čtenář musí myslet na "schované" control-flow cesty z bloku způsobené možností vyhození výjimky
http://google-styleguide.googlecode.com/svn/trunk/cppguide.html#Exceptions
-
Díky za opovědi.
-
Google C++ coding style výjimky zakazuje, zjednodušeně, proto, protože:
- vypnuté výjimky prodlužují kompilaci a zvětšují paměťové nároky
- výjimky dělají kód trochu méně čitelný, protože čtenář musí myslet na "schované" control-flow cesty z bloku způsobené možností vyhození výjimky
http://google-styleguide.googlecode.com/svn/trunk/cppguide.html#Exceptions
Řekl bych, že jeden hodně důležitý (možná ten rozhodující) důvod vytáhli až v tom rozhodnutí. Mají kopec kódu, který s výjimkami nepočítá, a museli by ho kompletně překopat.
-
Výjimka je na řešení cyb v hodnotách, asset na hledání chyb v kódu.
Pokud mám kus kódu, který požaduje existující adresář, napíšu, co má dělat. Pak řeším, že není platná cesta, nebo že adresář neexistuje. Buďto dám na začátek test s return DIR_NOT_EXIST, nebo výjimku. Je to vstup zvenčí, kde může být hodnota, která se liší od toho, s čím můžu pracovat - výjímečná situace, která by správně neměla nastat. A může nastat v závislosti na použití, takže musí být i v release. Můžu to chytnout, hodit chybovou hlášku nbebo ten adresář bytvořit a prubnout to znovu,...
Oproti tomu aserce hlídá, že kód dělá to, co má. Pokud napíšu funkci pro odmocninu, tak z definice je jasný, že na výstupu bude kladný číslo menší než argument. To dám do assertu. V produkční verzi by to jenom zabíralo místo a brzdilo, ale během ladění mě to může praštit, že co počítám není odmocnina, ale nesmysl. A samozřejmě si musím hlídat i vstup, pokud jsem na reálné množině, tak musí být >=0. Tam je to na výjimku u knihovní funkce (volání odkudkoliv kdykoliv), ale pokud je to soukromá metoda, můžu tam dát assert a dostanu kopanec "pozor, rveš tam něco, co tam nepatří". A vím, že se na to mám zaměřit a fixnout to, než začne řvát zákanzík...
Mimo to existuje i compile time asserce, která nedovolí zkompilovat kód, pokud je v něm měco blbě (třeba se liší velikost pole od počtu hodnot v enumu, který je indexuje,...). V nejjednoduší formě
#ifndef KONSTANTA
#error KONSTANTA nedefinována
#endif
A vůbec nejhloupější řešení chyby je to, že programátor řeší blbý hodnoty na vstupu stylem "hodnota neodpovídá, padělám ji". To se pak chyba prakticky nedá najít.
-
Pokud napíšu funkci pro odmocninu, tak z definice je jasný, že na výstupu bude kladný číslo menší než argument. To dám do assertu. V produkční verzi by to jenom zabíralo místo a brzdilo, ale během ladění mě to může praštit, že co počítám není odmocnina, ale nesmysl.
Je dobré si uvědomit, že jsi napsal nesmysl. Odmocnina může být i větší než argument.
-
Když jste tak tady všichni tak chytří, tak mi poraďte jak byste nejlépe řešili následující situaci v Javě:
Definujete v jednom balíku interface, které někdo bude implementovat, třeba com.company.interfaces.DataReader který v konkrétní implementaci bude číst data buď z databáze, nebo XML nebo třeba z CSV, bude mít nějakou metodu na čtení, třeba String readData() throws Exception, a otázkou je, jestli je dobré aby každá metoda nějakého rozhraní u nějž dopředu nebudu vědět konkrétní implementaci měla možnost házet výjimku, což je nutné pokud chceme dát implementátorovi možnost nějakou vyvolat, navíc Exception není moc obecná a bude ji nutné zkoumat přes reflexi.
Jak to nejlépe řešit?
Jednou z možností je že rozhraní nikdy nemůže vyhodit výjimku a v případě že nějaká nastane tak ji implementátor přepošle nahoru jako RuntimException.
Nejlepší řešení asi by bylo že každá třída rozhraní by měla umožnit zaregistrovat vlastní exception handler a jinak její metody nevyhazují nikdy žádnou výjimku, implementátor pak může všechny svoje výjimky odchytit a předat do případného handleru.
Jaký je váš názor na výjimky a rozhraní v Javě?
-
throws DataReaderException
-
Jak to nejlépe řešit?
Záleží na tom, co všechno může ten volající předpokládat. Pokud o vnitřku neví nic, tak ho stejně zajímá jen vyletěla/nevyletěla, protože může maximálně tak vypsat zprávu a možná to zkusit znova (což pravděpodobně zdechne zase).
Rozumné řešení je IMO poslat ji dál a doufat, že někde výš je něco, co ví, jaká implementace je použitá, a dokáže to chytit a zpracovat. Nebo musí být součástí rozhraní i trochu lepší specifikace toho, co může lítat ven.
Nejlepší řešení asi by bylo že každá třída rozhraní by měla umožnit zaregistrovat vlastní exception handler a jinak její metody nevyhazují nikdy žádnou výjimku, implementátor pak může všechny svoje výjimky odchytit a předat do případného handleru.
Aneb proč se střelit do nohy, když můžu do hlavy? Jaký je rozdíl v tom, jestli vyletí neznámá výjimka, nebo se mi zavolá handler se stejně neznámým parametrem?
Akorát musím navíc z toho handleru předat informaci ven, aby se ta volající funkce dověděla o to, že něco selhalo. Ještě by se ten handler mohl volat z nějakého pracovního vlákna, aby byla obzvlášť sranda. :)
-
Nepisane vynimky (RuntimeException, Throwable) slusny programator sam nehadze*, lebo nevie, ako to kto osetri.
Hadze sa vynimka na urovni modulu - proste throws DataReaderException. A ked sa nieco nepodari, tak sa hodi ona - nikoho vyssie nemaju co zaujimat detaily. Ked nahodou maju, tak DataReaderException moze implementovat metody napr. getErrorLine() alebo getErrorCol(). Pripadny dovod vynimky odporucam dat do cause, takze ho vrati getCause().
Pouzivat introspection je vacsinou zle.
*moze hodit v pripade, ze je to natolko vazna chyba, ze by mal tento modul sposobit ukoncenie programu. To ale v 99,9% nie je