Programovanie

Sú kontrolované výnimky dobré alebo zlé?

Java podporuje kontrolované výnimky. Túto kontroverznú jazykovú vlastnosť niektorí milujú a iní nenávidia, až do tej miery, že väčšina programovacích jazykov sa vyhýba kontrolovaným výnimkám a podporuje iba ich nekontrolované náprotivky.

V tomto príspevku skúmam kontroverziu okolo kontrolovaných výnimiek. Najprv predstavím koncept výnimiek a stručne popíšem jazykovú podporu jazyka Java pre výnimky, aby som začiatočníkom pomohol lepšie pochopiť kontroverziu.

Čo sú výnimky?

V ideálnom svete by sa počítačové programy nikdy nestretli so žiadnymi problémami: súbory by existovali, keď by mali existovať, sieťové pripojenia by sa nikdy neočakávane nezatvorili, nikdy by nedošlo k pokusu o vyvolanie metódy pomocou nulového odkazu integer-division-by - nulové pokusy by sa nevyskytli atď. Náš svet však nie je ani zďaleka ideálny; tieto a ďalšie výnimky ideálne vykonávanie programu sú veľmi rozšírené.

Prvé pokusy rozpoznať výnimky zahŕňali vrátenie špeciálnych hodnôt, ktoré naznačujú zlyhanie. Napríklad jazyk C. fopen () funkcia sa vráti NULOVÝ keď nemôže otvoriť súbor. Tiež PHP mysql_query () funkcia sa vráti NEPRAVDA keď dôjde k zlyhaniu SQL. Skutočný poruchový kód musíte hľadať inde. Aj keď je ľahké ho implementovať, s rozpoznávaním výnimiek sú dva problémy s týmto prístupom „vrátenia špeciálnej hodnoty“:

  • Špeciálne hodnoty nepopisujú výnimku. Čo robí NULOVÝ alebo NEPRAVDA naozaj? Všetko závisí od autora funkcie, ktorá vráti špeciálnu hodnotu. Ako ďalej vzťahujete špeciálnu hodnotu k kontextu programu, keď došlo k výnimke, aby ste mohli používateľovi podať zmysluplnú správu?
  • Je príliš ľahké ignorovať špeciálnu hodnotu. Napríklad, int c; SÚBOR * fp = fopen ("data.txt", "r"); c = fgetc (fp); je problematické, pretože tento fragment kódu C sa vykonáva fgetc () prečítať znak zo súboru, aj keď fopen () vracia NULOVÝ. V tomto prípade, fgetc () nebude mať úspech: máme chybu, ktorú možno ťažko nájsť.

Prvý problém je vyriešený použitím tried na opísanie výnimiek. Názov triedy identifikuje druh výnimky a jej polia agregujú vhodný kontext programu na určenie (prostredníctvom volaní metód), čo sa stalo. Druhý problém je vyriešený tak, že kompilátor prinúti programátora buď priamo odpovedať na výnimku, alebo naznačuje, že s výnimkou sa má zaobchádzať inde.

Niektoré výnimky sú veľmi vážne. Program sa môže napríklad pokúsiť prideliť časť pamäte, keď nie je k dispozícii voľná pamäť. Ďalším príkladom je bezhraničná rekurzia, ktorá vyčerpá zásobník. Takéto výnimky sú známe ako chyby.

Výnimky a Java

Java používa triedy na opis výnimiek a chýb. Tieto triedy sú usporiadané do hierarchie zakorenenej v java.lang. Hoditeľné trieda. (Dôvod prečo Hoditeľné bol vybraný na pomenovanie tejto špeciálnej triedy, bude zrejmé čoskoro.) Priamo pod ním Hoditeľnéjava.lang.Výnimka a java.lang.Error triedy, ktoré popisujú výnimky, respektíve chyby.

Napríklad knižnica Java obsahuje java.net.URISyntaxException, ktorá sa rozširuje Výnimka a označuje, že reťazec nebolo možné analyzovať ako referenciu Uniform Resource Identifier. Poznač si to URISyntaxException sa riadi konvenciou pomenovania, v ktorej názov triedy výnimiek končí slovom Výnimka. Podobná konvencia platí aj pre názvy tried chýb, ako napr java.lang.OutOfMemoryError.

Výnimka je podtrieda podľa java.lang.RuntimeException, čo je nadtrieda tých výnimiek, ktoré môžu byť vyvolané počas normálnej prevádzky Java Virtual Machine (JVM). Napríklad, java.lang.ArithmeticException popisuje aritmetické zlyhania, ako sú pokusy o rozdelenie celých čísel na celé číslo 0. Tiež java.lang.NullPointerException popisuje pokusy o prístup k členom objektu pomocou nulovej referencie.

Iný spôsob pohľadu RuntimeException

Oddiel 11.1.1 špecifikácie jazyka Java 8 uvádza: RuntimeException je nadtrieda všetkých výnimiek, ktoré môžu byť vyhodené z mnohých dôvodov počas vyhodnocovania výrazu, ale z ktorých je stále možné zotavenie.

Ak dôjde k výnimke alebo chybe, ide o objekt z príslušnej oblasti Výnimka alebo Chyba vytvorí sa podtrieda, ktorá sa odovzdá JVM. Akt prechodu okolo objektu je známy ako hádzať výnimku. Java poskytuje hodiť vyhlásenie na tento účel. Napríklad, hodiť novú IOException ("nemôžem prečítať súbor"); vytvára nový java.io.IOException objekt, ktorý je inicializovaný na zadaný text. Tento objekt je následne hodený na JVM.

Java poskytuje skús príkaz na vymedzenie kódu, z ktorého môže byť vyvolaná výnimka. Toto vyhlásenie sa skladá z kľúčového slova skús nasledovaný blokom oddeleným zátvorkami. Nasledujúci fragment kódu demonštruje skús a hodiť:

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

V tomto fragmente kódu vykonávanie vstupuje do kódu skús blokovať a vyvolať metóda (), ktorý hodí inštanciu NullPointerException.

JVM prijíma hádzateľná a vyhľadá v zásobníku volaní metód a psovod vybaviť výnimku. Výnimky, ktoré nie sú odvodené z RuntimeException sú často vybavované; runtime výnimky a chyby sú spracovávané zriedka.

Prečo sa chyby riešia zriedka

Chyby sa riešia zriedka, pretože často neexistuje nič, čo by program Java mohol urobiť, aby sa po chybe zotavil. Napríklad keď je vyčerpaná voľná pamäť, program nemôže prideliť ďalšiu pamäť. Ak je však zlyhanie pridelenia spôsobené zadržaním veľkého množstva pamäte, ktoré by sa malo uvoľniť, môže sa pracovník handeru pokúsiť uvoľniť pamäť pomocou JVM. Aj keď sa obslužná rutina môže javiť ako užitočná v tomto kontexte chyby, pokus nemusí byť úspešný.

Handlera popisuje a chytiť blok, ktorý nasleduje za skús blokovať. The chytiť blok poskytuje hlavičku so zoznamom typov výnimiek, ktoré je pripravený spracovať. Ak je typ vrhača zahrnutý v zozname, vrhač je odovzdaný chytiť blok, ktorého kód sa vykoná. Kód reaguje na príčinu zlyhania tak, že spôsobí pokračovanie programu alebo jeho možné ukončenie:

skus {metoda (); } catch (NullPointerException npe) {System.out.println ("pokus o prístup k členovi objektu prostredníctvom nulovej referencie"); } // ... void method () {throw new NullPointerException ("some text"); }

V tomto fragmente kódu som pripojil a chytiť blok do skús blokovať. Keď NullPointerException predmet je vyhodený z metóda (), JVM vyhľadá a prevedie vykonanie na chytiť blok, ktorý odošle správu.

Nakoniec bloky

A skús blok alebo jeho konečná chytiť za blokom môže nasledovať a konečne blok, ktorý sa používa na vykonávanie úloh čistenia, napríklad na uvoľnenie získaných prostriedkov. Už k tomu nemám čo povedať konečne pretože to nie je relevantné pre diskusiu.

Výnimky opísané v Výnimka a jeho podtriedy okrem RuntimeException a jeho podtriedy sú známe ako skontrolované výnimky. Pre každý hodiť príkaz, kompilátor skúma typ objektu výnimky. Ak typ označuje začiarknuté, kompilátor skontroluje zdrojový kód, aby sa zabezpečilo, že výnimka sa spracuje v metóde, kde je vyhodená, alebo je deklarovaná na ďalšie spracovanie v zásobníku volaní metód. Všetky ostatné výnimky sú známe ako nekontrolované výnimky.

Java vám umožňuje deklarovať, že skontrolovaná výnimka sa spracuje vyššie v zásobníku volaní metódou, a to pripojením a hodí doložka (kľúčové slovo hodí nasledovaný zoznamom kontrolovaných názvov tried výnimiek oddelených čiarkami) do hlavičky metódy:

skus {metoda (); } catch (IOException ioe) {System.out.println ("zlyhanie I / O"); } // ... void method () hodí IOException {hodí novú IOException ("nejaký text"); }

Pretože Výnimka IO je typ kontrolovanej výnimky, vyhodené inštancie tejto výnimky musia byť spracované v metóde, kde sú vyhodené, alebo musia byť deklarované ako spracované ďalej v zásobníku metódového pripojenia pripojením hodí doložka k hlavičke každej ovplyvnenej metódy. V takom prípade a hodí IOException doložka je pripojená k metóda ()hlavička. Vyhodené Výnimka IO objekt je odovzdaný do JVM, ktorý vyhľadá a prevedie vykonanie do chytiť psovod.

Argument za a proti kontrolovaným výnimkám

Skontrolované výnimky sa ukázali ako veľmi kontroverzné. Sú to dobré jazykové vlastnosti alebo sú zlé? V tejto časti uvádzam prípady pre a proti kontrolovaným výnimkám.

Skontrolované výnimky sú dobré

James Gosling vytvoril jazyk Java. Zaradil kontrolované výnimky, aby podporil vytvorenie robustnejšieho softvéru. V rozhovore s Billom Vennersom v roku 2003 Gosling poukázal na to, aké ľahké je generovať buggy kód v jazyku C ignorovaním špeciálnych hodnôt, ktoré sa vracajú z funkcií súboru C orientovaných na súbory. Program sa napríklad pokúša čítať zo súboru, ktorý nebol úspešne otvorený na čítanie.

Závažnosť nekontrolovania návratových hodnôt

Nekontrolovanie návratových hodnôt sa nemusí javiť ako nič veľké, ale táto nedbalosť môže mať následky na život alebo na smrť. Zamyslite sa napríklad nad takýmto buggovým softvérom riadiacim systémy raketového navádzania a autami bez vodiča.

Gosling tiež poukázal na to, že vysokoškolské programovacie kurzy nedostatočne diskutujú o riešení chýb (aj keď sa to od roku 2003 mohlo zmeniť). Keď idete na univerzitu a robíte úlohy, požiadajú vás, aby ste si naprogramovali jednu skutočnú cestu [vykonania, kde zlyhanie nie je úvahou]. Určite som nikdy nezažil vysokoškolský kurz, kde sa vôbec diskutovalo o riešení chýb. Vychádzate z vysokej školy a jediné, s čím ste sa museli vyrovnať, je jediná skutočná cesta.

Zameranie sa iba na jednu skutočnú cestu, lenivosť alebo iný faktor viedlo k tomu, že bol napísaný veľa buggy kódu. Skontrolované výnimky vyžadujú, aby programátor zvážil návrh zdrojového kódu a dúfajme, že dosiahne robustnejší softvér.

Skontrolované výnimky sú zlé

Mnoho programátorov nenávidí kontrolované výnimky, pretože sú nútení zaoberať sa API, ktoré ich nadmerne používajú alebo nesprávne označujú kontrolované výnimky namiesto nekontrolovaných výnimiek ako súčasť svojich zmlúv. Napríklad metóde, ktorá nastavuje hodnotu senzora, sa odovzdá neplatné číslo a namiesto inštancie nezačiarknutej kontroly sa vyhodí kontrolovaná výnimka. java.lang.IllegalArgumentException trieda.

Tu uvádzame niekoľko ďalších dôvodov, prečo sa vám nepáčia zaškrtnuté výnimky; Vyňal som ich z rozhovorov Slashdot: Spýtajte sa Jamesa Goslinga na diskusiu o Java a Ocean Exploring Robots:

  • Začiarknuté výnimky ľahko ignorujete tak, že ich znova vytvoríte ako RuntimeException inštancie, tak aký má zmysel ich mať? Stratil som počet opakovaní tohto bloku kódu:
    try {// do stuff} catch (AnnoyingcheckedException e) {throw new RuntimeException (e); }

    99% času s tým nemôžem nič urobiť. Nakoniec bloky vykonajú potrebné čistenie (alebo aspoň by mali).

  • Začiarknuté výnimky je možné ignorovať ich prehltnutím, tak aký má zmysel ich mať? Stratil som tiež počet opakovaní:
    skúste {// urobiť veci} chytiť (AnnoyingCheckedException e) {// neurobiť nič}

    Prečo? Pretože niekto sa s tým musel vyrovnať a bol lenivý. Bolo to nesprávne? Samozrejme. Stáva sa to? Absolútne. Čo keby to bola namiesto toho nekontrolovaná výnimka? Aplikácia by práve zomrela (čo je lepšie ako prehltnúť výnimku).

  • Výsledkom kontrolovaných výnimiek je viac hodí prehlásenia o doložkách. Problém so zaškrtnutými výnimkami je, že povzbudzujú ľudí, aby prehltli dôležité podrobnosti (menovite triedu výnimiek). Ak sa rozhodnete neprehltnúť tento detail, musíte neustále pridávať hodí vyhlásenia v celej svojej aplikácii. To znamená 1) že nový typ výnimky ovplyvní veľa podpisov funkcií a 2) môžete premeškať konkrétnu inštanciu výnimky, ktorú skutočne chcete chytiť (napríklad otvoríte sekundárny súbor pre funkciu, ktorá zapisuje údaje do Sekundárny súbor je voliteľný, takže jeho chyby môžete ignorovať, ale kvôli podpisu Výnimka IO, je ľahké to prehliadnuť).
  • Skontrolované výnimky v skutočnosti nie sú výnimkami. Zaškrtnuté výnimky spočívajú v tom, že to nie sú skutočne výnimky zvyčajného chápania konceptu. Namiesto toho sú to alternatívne návratové hodnoty API.

    Celá myšlienka výnimiek spočíva v tom, že chyba hodená niekam dolu v telefónnom reťazci môže prebublávať a spracovať ju kód niekde ďalej, bez toho, aby sa s tým musel intervenujúci kód obávať. Skontrolované výnimky na druhej strane vyžadujú, aby každá úroveň kódu medzi vyhadzovačom a lapačom deklarovala, že vie o všetkých formách výnimiek, ktoré nimi môžu prechádzať. To sa v praxi skutočne trochu líši od toho, či by kontrolované výnimky boli iba špeciálne návratové hodnoty, ktoré musel volajúci skontrolovať.

Ďalej som sa stretol s argumentom, že aplikácie musia spracovávať veľké množstvo kontrolovaných výnimiek, ktoré sú generované z viacerých knižníc, ku ktorým majú prístup. Tento problém je však možné prekonať pomocou šikovne navrhnutej fasády, ktorá využíva zreťazené zariadenie Java na výnimky a opätovné generovanie výnimiek na výrazné zníženie počtu výnimiek, ktoré je potrebné spracovať, pri zachovaní pôvodnej výnimky, ktorá bola vyhodená.

Záver

Sú kontrolované výnimky dobré alebo zlé? Inými slovami, mali by byť programátori nútení zaobchádzať so skontrolovanými výnimkami alebo im byť poskytnutá príležitosť ich ignorovať? Páči sa mi myšlienka presadiť robustnejší softvér. Tiež si však myslím, že mechanizmus Java na spracovanie výnimiek sa musí vyvinúť, aby bol programátorsky prívetivejší. Tu je niekoľko spôsobov, ako tento mechanizmus vylepšiť:

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