Programování

Jsou kontrolované výjimky dobré nebo špatné?

Java podporuje zaškrtnuté výjimky. Tuto kontroverzní jazykovou funkci někteří milují a ostatní ji nenávidí, a to až do bodu, kdy se většina programovacích jazyků vyhýbá kontrolovaným výjimkám a podporuje pouze jejich nezaškrtnuté protějšky.

V tomto příspěvku zkoumám kontroverze kolem kontrolovaných výjimek. Nejprve představím koncept výjimek a stručně popíšu jazykovou podporu jazyka Java pro výjimky, abych začátečníkům pomohl lépe porozumět kontroverzi.

Co jsou výjimky?

V ideálním světě by se počítačové programy nikdy nesetkaly s žádnými problémy: soubory by existovaly, když by měly existovat, síťová připojení by se nikdy neočekávaně nezavřela, nikdy by nedošlo k pokusu o vyvolání metody pomocí nulové reference, integer-division-by - nulové pokusy by se neobjevily atd. Náš svět však není zdaleka ideální; tyto a další výjimky k ideálnímu provedení programu jsou velmi rozšířené.

První pokusy rozpoznat výjimky zahrnovaly vrácení speciálních hodnot, které označují selhání. Například jazyk C. fopen () funkce se vrací NULA když nemůže otevřít soubor. Také PHP mysql_query () funkce se vrací NEPRAVDIVÉ když dojde k selhání SQL. Skutečný kód poruchy musíte hledat jinde. Ačkoli je snadné jej implementovat, existují dva problémy s tímto přístupem k „rozpoznávání výjimek“ s „speciální návratovou hodnotou“:

  • Speciální hodnoty nepopisují výjimku. Co dělá NULA nebo NEPRAVDIVÉ opravdu myslí? Vše záleží na autorovi funkce, která vrací speciální hodnotu. Jak dále vztahujete zvláštní hodnotu k kontextu programu, když došlo k výjimce, abyste uživateli mohli předat smysluplnou zprávu?
  • Je příliš snadné ignorovat speciální hodnotu. Například, int c; SOUBOR * fp = fopen ("data.txt", "r"); c = fgetc (fp); je problematické, protože tento fragment kódu C je spuštěn fgetc () číst znak ze souboru, i když fopen () se vrací NULA. V tomto případě, fgetc () neuspěje: máme chybu, kterou může být těžké najít.

První problém je vyřešen pomocí tříd k popisu výjimek. Název třídy identifikuje druh výjimky a její pole agregují vhodný kontext programu pro určení (prostřednictvím volání metod), co se stalo. Druhý problém je vyřešen tím, že kompilátor donutí programátor buď přímo reagovat na výjimku, nebo indikovat, že výjimku je třeba zpracovat jinde.

Některé výjimky jsou velmi závažné. Program se může například pokusit přidělit část paměti, pokud není k dispozici žádná volná paměť. Dalším příkladem je neomezená rekurze, která vyčerpá zásobník. Takové výjimky jsou známé jako chyby.

Výjimky a Java

Java používá třídy k popisu výjimek a chyb. Tyto třídy jsou uspořádány do hierarchie zakořeněné v java.lang. Hoditelné třída. (Důvod proč Vrhací byl vybrán k pojmenování této speciální třídy, se brzy ukáže.) Přímo pod ním Vrhací jsou java.lang.Exception a java.lang.Error třídy, které popisují výjimky a chyby.

Například knihovna Java obsahuje java.net.URISyntaxException, který se rozšiřuje Výjimka a označuje, že řetězec nelze analyzovat jako odkaz na Uniform Resource Identifier. Všimněte si, že URISyntaxException následuje konvenci pojmenování, ve které název třídy výjimky končí slovem Výjimka. Podobná konvence platí pro názvy tříd chyb, například java.lang.OutOfMemoryError.

Výjimka je podtřída od java.lang.RuntimeException, což je nadtřída těchto výjimek, které lze vyvolat během normálního provozu Java Virtual Machine (JVM). Například, java.lang.ArithmeticException popisuje aritmetické chyby, jako jsou pokusy rozdělit celá čísla na celé číslo 0. Také, java.lang.NullPointerException popisuje pokusy o přístup k členům objektu pomocí nulové reference.

Další způsob, jak se na to podívat RuntimeException

Oddíl 11.1.1 Specifikace jazyka Java 8 uvádí: RuntimeException je nadtřída všech výjimek, které mohou být během vyhodnocení výrazu vyvolány z mnoha důvodů, ale ze kterých může být stále možné zotavení.

Když dojde k výjimce nebo chybě, objekt z příslušného Výjimka nebo Chyba je vytvořena podtřída a předána JVM. Akt předání objektu je znám jako vyvolání výjimky. Java poskytuje házet prohlášení za tímto účelem. Například, vyvolá novou IOException ("nelze číst soubor"); vytváří nový java.io.IOException objekt, který je inicializován na zadaný text. Tento objekt je následně hoden na JVM.

Java poskytuje Snaž se příkaz pro vymezení kódu, ze kterého může být vyvolána výjimka. Toto prohlášení se skládá z klíčového slova Snaž se následovaný blokem odděleným závorkami. Následující fragment kódu ukazuje Snaž se a házet:

zkuste {method (); } // ... void method () {throw new NullPointerException ("some text"); }

V tomto fragmentu kódu provádění vstupuje do Snaž se blokovat a vyvolat metoda(), který hodí instanci NullPointerException.

JVM přijímá házet a vyhledá v zásobníku volání metody a psovod zpracovat výjimku. Výjimky neodvozené z RuntimeException jsou často zpracovávány; runtime výjimky a chyby jsou zpracovávány zřídka.

Proč se chyby zpracovávají jen zřídka

Chyby se řeší jen zřídka, protože program Java nemusí často udělat nic, aby se z chyby zotavilo. Když je například vyčerpána volná paměť, program nemůže přidělit další paměť. Pokud je však selhání přidělení způsobeno zadržením velkého množství paměti, která by měla být uvolněna, mohl by se obslužný program pokusit uvolnit paměť pomocí JVM. Ačkoli se obslužná rutina může v tomto chybovém kontextu jevit jako užitečná, pokus nemusí být úspěšný.

Psovod je popsán a chytit blok, který následuje po Snaž se blok. The chytit block provides a header that lists the types of exceptions that it's ready to handle. Pokud je typ vrhače zahrnut v seznamu, je vrhač předán chytit blok, jehož kód se spustí. Kód reaguje na příčinu selhání takovým způsobem, že způsobí pokračování programu nebo jeho ukončení:

zkuste {method (); } catch (NullPointerException npe) {System.out.println ("pokus o přístup k členu objektu pomocí nulové reference"); } // ... void method () {throw new NullPointerException ("some text"); }

V tomto fragmentu kódu jsem připojil a chytit blok do Snaž se blok. Když NullPointerException objekt je vyhozen z metoda(), JVM vyhledá a předá provedení do chytit blok, který odešle zprávu.

Nakonec bloky

A Snaž se blok nebo jeho finále chytit po bloku může následovat a Konečně blok, který se používá k provádění úkolů čištění, jako je uvolňování získaných prostředků. Už k tomu nemám co říct Konečně protože to není relevantní pro diskusi.

Výjimky popsané Výjimka a jeho podtřídy kromě RuntimeException a jeho podtřídy jsou známé jako kontrolované výjimky. Pro každého házet příkaz, kompilátor zkoumá typ objektu výjimky. Pokud typ označuje zaškrtnuto, kompilátor zkontroluje zdrojový kód, aby zajistil, že výjimka je zpracována v metodě, kde je vyvolána nebo je deklarována k dalšímu zpracování v zásobníku volání metody. Všechny ostatní výjimky jsou známé jako nezaškrtnuté výjimky.

Java umožňuje deklarovat, že se kontrolovaná výjimka dále zpracovává v zásobníku volání metod připojením a hodí doložka (klíčové slovo hodí následovaný čárkami oddělený seznam kontrolovaných názvů tříd výjimek) do záhlaví metody:

zkuste {method (); } catch (IOException ioe) {System.out.println ("selhání I / O"); } // ... void method () vyvolá IOException {hodit novou IOException ("nějaký text"); }

Protože IOException je typ kontrolované výjimky, vyvolané instance této výjimky musí být zpracovány v metodě, kde jsou vyvolány, nebo musí být deklarovány k dalšímu zpracování v zásobníku volání metod připojením a hodí klauzule do záhlaví každé ovlivněné metody. V tomto případě a vyvolá IOException klauzule je připojena k metoda()záhlaví. Vržený IOException objekt je předán JVM, který vyhledá a přenese provedení do chytit psovod.

Hádání pro a proti kontrolovaným výjimkám

Zkontrolované výjimky se ukázaly jako velmi kontroverzní. Jsou dobrou jazykovou funkcí nebo jsou špatní? V této části uvádím případy pro a proti kontrolovaným výjimkám.

Zkontrolované výjimky jsou dobré

James Gosling vytvořil jazyk Java. Zahrnoval kontrolované výjimky, aby podpořil vytvoření robustnějšího softwaru. V rozhovoru z roku 2003 s Billem Vennersem Gosling poukázal na to, jak snadné je generovat buggy kód v jazyce C tím, že ignoruje speciální hodnoty, které se vracejí z funkcí C orientovaných na soubory. Například program se pokusí číst ze souboru, který nebyl úspěšně otevřen pro čtení.

Závažnost nekontrolování návratových hodnot

Nezkontrolovat návratové hodnoty se může zdát jako velký problém, ale tato nedbalost může mít následky na život nebo na smrt. Zamyslete se například nad takovým buggy softwarem, který ovládá systémy navádění raket a auta bez řidiče.

Gosling také poukázal na to, že kurzy programování na vysokých školách dostatečně nediskutují o zpracování chyb (i když se to od roku 2003 mohlo změnit). Když procházíte vysokou školou a děláte úkoly, požádají vás, abyste naprogramovali jednu pravou cestu [provádění, kde selhání není úvahou]. Určitě jsem nikdy nezažil vysokoškolský kurz, kde by se vůbec diskutovalo o zpracování chyb. Vyjdete ze školy a jediné, s čím jste se museli vypořádat, je jedna opravdová cesta.

Zaměření pouze na jednu skutečnou cestu, lenost nebo jiný faktor vedlo k tomu, že byl napsán hodně buggy kódu. Zaškrtnuté výjimky vyžadují, aby programátor zvážil návrh zdrojového kódu a doufejme dosáhl robustnějšího softwaru.

Zkontrolované výjimky jsou špatné

Mnoho programátorů nenávidí kontrolované výjimky, protože jsou nuceni vypořádat se s API, která je nadužívají, nebo nesprávně specifikují kontrolované výjimky místo nekontrolovaných výjimek jako součást svých smluv. Například metoda, která nastaví hodnotu senzoru, předá neplatné číslo a vyvolá kontrolovanou výjimku namísto instance nekontrolovaného java.lang.IllegalArgumentException třída.

Zde je několik dalších důvodů, proč se vám nelíbí zaškrtnuté výjimky; Vyňal jsem je z rozhovorů Slashdota: Zeptejte se Jamese Goslinga na diskusi o Javě a oceánských průzkumných robotech:

  • Zaškrtnuté výjimky lze snadno ignorovat tak, že je znovu vytvoříte jako RuntimeException instance, tak jaký to má smysl mít je? Ztratil jsem počet, kolikrát jsem napsal tento blok kódu:
    try {// do things} catch (AnnoyingcheckedException e) {throw new RuntimeException (e); }

    99% času s tím nemohu nic dělat. Nakonec bloky provedou veškerá nezbytná vyčištění (nebo alespoň by měla).

  • Zaškrtnuté výjimky lze ignorovat tím, že je spolknete, tak jaký má smysl je mít? Také jsem ztratil počet případů, kdy jsem to viděl:
    zkuste {// dělat věci} chytit (AnnoyingCheckedException e) {// nedělat nic}

    Proč? Protože se s tím někdo musel vypořádat a byl líný. Bylo to špatně? Tak určitě. Stává se to? Absolutně. Co kdyby se jednalo o nekontrolovanou výjimku? Aplikace by právě zemřela (což je lepší než spolknout výjimku).

  • Výsledkem kontrolovaných výjimek je více hodí prohlášení o klauzuli. Problém kontrolovaných výjimek spočívá v tom, že povzbuzují lidi, aby spolkli důležité podrobnosti (jmenovitě třídu výjimek). Pokud se rozhodnete nepohltit tento detail, musíte neustále přidávat hodí prohlášení v celé vaší aplikaci. To znamená 1), že nový typ výjimky ovlivní mnoho podpisů funkcí, a 2) můžete zmeškat konkrétní instanci výjimky, kterou skutečně chcete chytit (řekněme, že otevřete sekundární soubor pro funkci, která zapisuje data do soubor. Sekundární soubor je volitelný, takže jeho chyby můžete ignorovat, ale proto, že podpis vyvolá IOException, je snadné to přehlédnout).
  • Zaškrtnuté výjimky ve skutečnosti nejsou výjimkami. Věc o kontrolovaných výjimkách spočívá v tom, že to ve skutečnosti nejsou výjimky obvyklým chápáním konceptu. Místo toho se jedná o alternativní návratové hodnoty API.

    Celá myšlenka výjimek spočívá v tom, že chyba způsobená někde dolů v řetězci volání může probublávat a být zpracována kódem někde dále, aniž by se o to musel zasahující kód starat. Kontrolované výjimky naopak vyžadují, aby každá úroveň kódu mezi vrhačem a chytačem deklarovala, že ví o všech formách výjimek, které je mohou procházet. V praxi se to opravdu trochu liší, pokud kontrolované výjimky byly jednoduše speciální návratové hodnoty, které musel volající zkontrolovat.

Kromě toho jsem narazil na argument o tom, že aplikace musí zpracovávat velké množství kontrolovaných výjimek, které jsou generovány z více knihoven, ke kterým přistupují. Tento problém však lze překonat pomocí chytře navržené fasády, která využívá zařízení řetězových výjimek a přetváření výjimek Java k výraznému snížení počtu výjimek, které je třeba zpracovat při zachování původní výjimky, která byla vyvolána.

Závěr

Jsou kontrolované výjimky dobré nebo špatné? Jinými slovy, měli by být programátoři nuceni zpracovávat kontrolované výjimky nebo jim být dána příležitost je ignorovat? Líbí se mi myšlenka prosazování robustnějšího softwaru. Také si však myslím, že mechanismus zpracování výjimek v Javě se musí vyvinout, aby byl programátorštější. Tady je několik způsobů, jak tento mechanismus vylepšit:

$config[zx-auto] not found$config[zx-overlay] not found