Zpracování výjimek - Exception handling

Ve výpočetní a programování , zpracování výjimek je proces reagovat na výskytu výjimek - anomální nebo výjimečných podmínek, které vyžadují speciální zpracování - při provádění jednoho programu . Výjimka obecně přeruší běžný tok provádění a provede předregistrovanou obslužnou rutinu výjimek ; podrobnosti o tom, jak se to provádí, závisí na tom, zda se jedná o hardwarovou nebo softwarovou výjimku a jak je softwarová výjimka implementována. Zpracování výjimek, je-li k dispozici, usnadňují specializované konstrukce programovacích jazyků , hardwarové mechanismy, jako jsou přerušení , nebo zařízení pro meziprocesovou komunikaci (IPC) operačního systému (OS), jako jsou signály . Některé výjimky, zejména hardwarové, mohou být zpracovány tak elegantně, že provádění může pokračovat tam, kde bylo přerušeno.

Alternativním přístupem k manipulaci s softwarově výjimkou je kontrola chyb, která udržuje normální tok programu s pozdějším explicitní kontroly pro nepředvídané hlásil pomocí speciálních návratové hodnoty, pomocné globální proměnné , jako je C ' s errno nebo plovoucí stav vlajky bodu. Přístupem je také validace vstupu , která preventivně filtruje výjimečné případy.

V hardwaru

Mechanismy výjimek hardwaru zpracovává CPU. Je určen k podpoře např. Detekce chyb a přesměruje tok programu na rutiny služeb zpracování chyb. Stav před uložením výjimky, např. Do zásobníku.

Zpracování/pasti hardwarových výjimek: IEEE 754 s plovoucí desetinnou čárkou

Zpracování výjimek v hardwarovém standardu IEEE 754 s plovoucí desetinnou čárkou obecně odkazuje na výjimečné podmínky a definuje výjimku jako „událost, ke které dochází, když operace na některých konkrétních operandech nemá žádný výsledek vhodný pro každou rozumnou aplikaci. Tato operace může signalizovat jednu nebo více výjimek vyvoláním výchozího nebo, pokud je to výslovně požadováno, alternativního zpracování definovaného jazykem. "

Ve výchozím nastavení lze výjimku IEEE 754 obnovit a je řešena nahrazením předdefinované hodnoty pro různé výjimky, např. Nekonečno pro výjimku dělenou nulou, a poskytnutím stavových příznaků pro pozdější kontrolu, zda k výjimce došlo ( typický typ viz programovací jazyk C99 příklad zpracování výjimek IEEE 754). Styl zpracování výjimek povolený použitím stavových příznaků zahrnuje: první výpočet výrazu pomocí rychlé, přímé implementace; kontrola, zda selhala, testováním stavových příznaků; a potom v případě potřeby volání pomalejší, numericky robustnější implementace.

Standard IEEE 754 používá termín „odchyt“ k označení volání rutiny zpracování výjimek dodaných uživatelem za výjimečných podmínek a je volitelnou funkcí standardu. Standard za tímto účelem doporučuje několik scénářů použití, včetně implementace přednastavení jiné než výchozí hodnoty následované obnovením, aby bylo možné stručně zpracovat odstranitelné singularity .

Výchozí chování při zpracování výjimek IEEE 754 při obnovení po předběžné substituci výchozí hodnoty předchází rizikům spojeným se změnou toku řízení programu na numerických výjimkách. Například v roce 1996 první let Ariane 5 (Flight 501) skončil katastrofickým výbuchem, částečně kvůli zásadám zpracování výjimek programovacího jazyka Ada pro přerušení výpočtu aritmetické chyby, což byla v tomto případě 64bitová plovoucí desetinná čárka přetečení převodu na 16bitové celé číslo . V případě Ariane Flight 501 programátoři chránili pouze čtyři ze sedmi kritických proměnných proti přetečení kvůli obavám z výpočetních omezení palubního počítače a spoléhali se na to, co se ukázalo jako nesprávné předpoklady o možném rozsahu hodnot pro tři nechráněné proměnné, protože znovu použili kód z Ariane 4, u kterého byly jejich předpoklady správné. Podle Williama Kahana by bylo ztrátě letu 501 zabráněno, kdyby byla použita zásada zpracování výjimek IEEE 754 výchozí substituce, protože přetékající 64bitová až 16bitová konverze, která způsobila přerušení softwaru, nastala v části kód, který se ukázal být na Ariane 5 zcela zbytečný. Oficiální zpráva o havárii (vedená vyšetřovací komisí vedenou Jacquesem-Louisem Lions ) poznamenala, že „Základním tématem vývoje Ariane 5 je zaujatost vůči zmírňování náhodného selhání . Dodavatel setrvačného navigačního systému (SRI) se řídil pouze specifikací, která mu byla dána a která stanovila, že v případě jakékoli detekované výjimky má být procesor zastaven. Výjimka, ke které došlo, nebyla způsobena náhodným selháním ale chyba návrhu. Výjimka byla zjištěna, ale byla zpracována nevhodně, protože byl přijat názor, že software by měl být považován za správný, dokud se neukáže, že je na vině. [...] Althoug h selhání bylo způsobeno chybou systematického návrhu softwaru, lze zavést mechanismy ke zmírnění tohoto typu problému. Například počítače v SRI mohly nadále poskytovat své nejlepší odhady požadovaných informací o postoji . Existuje důvod k obavám, že by měla být povolena nebo dokonce vyžadována softwarová výjimka, která způsobí zastavení procesoru při manipulaci s kritickým vybavením. Ztráta správné softwarové funkce je skutečně nebezpečná, protože v obou jednotkách SRI běží stejný software. V případě Ariane 501 to mělo za následek vypnutí dvou stále zdravých kritických jednotek vybavení. “

Z hlediska zpracování jsou hardwarová přerušení podobná obnovitelným výjimkám, ačkoli obvykle nesouvisejí s řídicím tokem uživatelského programu .

Možnosti zpracování výjimek poskytované operačním systémem

Unixové operační systémy poskytují zařízení pro zpracování výjimek v programech prostřednictvím IPC . Přerušení způsobená prováděním procesu jsou obvykle zpracovávána rutinami služby přerušení operačního systému a operační systém pak může do tohoto procesu odeslat signál , který může požádat operační systém o registraci obsluhy signálu, která má být volána když je signál zvýšen, nebo nechte operační systém provést výchozí akci (jako ukončení programu). Typickými příklady jsou SIGSEGV , SIGBUS , SIGILL a SIGFPE .

Jiné operační systémy, např. OS/360 a následníci , mohou místo IPC nebo navíc používat různé přístupy.

V softwaru

Zpracování výjimek softwaru a podpora poskytovaná softwarovými nástroji se poněkud liší od toho, co je chápáno zpracováním výjimek v hardwaru, ale jsou zahrnuty podobné koncepty. V mechanismech programovacího jazyka pro zpracování výjimek se termín výjimka obvykle používá v konkrétním smyslu k označení datové struktury ukládající informace o výjimečných podmínkách. Jeden mechanismus přenosu kontroly nebo vyvolání výjimky se nazývá hod . Výjimka se prý hodí . Provedení je přeneseno na „úlovek“.

Z pohledu autora rutiny je vyvolání výjimky užitečným způsobem, jak signalizovat, že rutina nemůže běžet normálně - například když je vstupní argument neplatný (např. Hodnota je mimo doménu funkce ) nebo když zdroj, na který se spoléhá, ​​není k dispozici (jako chybějící soubor, chyba na pevném disku nebo chyby v nedostatku paměti) nebo rutina zjistila normální stav, který vyžaduje zvláštní zacházení, např. pozornost, konec souboru . V systémech bez výjimek by rutiny musely vrátit nějaký speciální chybový kód . To je však někdy komplikováno problémem semipredikátu , ve kterém uživatelé rutiny potřebují napsat kód navíc, aby odlišili normální návratové hodnoty od chybných.

Programovací jazyky se podstatně liší v pojetí výjimky. Současné jazyky lze zhruba rozdělit do dvou skupin:

  • Do této kategorie patří jazyky, kde jsou jako struktury pro řízení toku navrženy výjimky: Ada, Modula-3, ML, OCaml, PL/I, Python a Ruby.
  • Jazyky, kde se výjimky používají pouze ke zpracování abnormálních, nepředvídatelných a chybných situací: C ++, Java, C#, Common Lisp, Eiffel a Modula-2.

Kiniry také poznamenává, že „Jazykový design pouze částečně ovlivňuje používání výjimek, a tedy i způsob, jakým se člověk zabývá částečnými a úplnými poruchami během provádění systému. Dalším významným vlivem jsou příklady použití, typicky v základních knihovnách a příklady kódu v technické oblasti. knihy, články z časopisů a online diskusní fóra a v normách kodexu organizace. “

Při zvažování strategií zpracování výjimek čelí současné aplikace mnoha problémům s designem. Zejména v moderních aplikacích na podnikové úrovni musí výjimky často překračovat hranice procesů a hranice strojů. Součástí návrhu solidní strategie zpracování výjimek je rozpoznání, kdy proces selhal do té míry, že jej nelze ekonomicky zvládnout softwarovou částí procesu.

Dějiny

Zpracování výjimek softwaru vyvinutých v Lispu v 60. a 70. letech minulého století. To pochází z LISP 1.5 (1962), kde byly výjimky chycen do ERRSETklíčového slova, který se vrátil NILv případě chyby, namísto ukončení programu, nebo vstup do ladicího programu. Zvyšování chyb bylo v systému MacLisp zavedeno koncem 60. let 20. století prostřednictvím ERRklíčového slova. To bylo rychle použito nejen pro zvyšování chyb, ale i pro nelokální řídicí tok, a proto bylo rozšířeno o dvě nová klíčová slova, CATCHa THROW(MacLisp, červen 1972), rezervace ERRSETa ERRpro zpracování chyb. Chování vyčištění, kterému se dnes obecně říká „konečně“, bylo představeno v NIL (New Implementation of LISP) v polovině až koncem sedmdesátých let minulého století jako UNWIND-PROTECT. To pak přijal Common Lisp . Současné to bylo dynamic-windve Scheme, které řešilo výjimky při uzavírání. První dokumenty o zpracování strukturovaných výjimek byly Goodenough (1975a) a Goodenough (1975b) . Zpracování výjimek bylo následně od 80. let široce přijato mnoha programovacími jazyky.

PL/Použil jsem výjimky s dynamickým rozsahem, avšak novější jazyky používají výjimky s lexikálním rozsahem. Zpracování výjimek PL/I zahrnovalo události, které nejsou chybami, např. Pozornost, konec souboru, úprava uvedených proměnných. Zatímco některé novější jazyky podporují výjimky bez chyb, jejich použití není běžné.

Původně zpracování výjimek softwaru zahrnovalo jak obnovitelné výjimky (sémantika obnovení), jako většina hardwarových výjimek, tak i neobnovitelné výjimky (sémantika ukončení). Sémantika obnovení však byla v 70. a 80. letech 20. století považována v praxi za neúčinnou (viz diskuse o standardizaci C ++, citovaná níže) a již se běžně nepoužívají, přestože ji poskytují programovací jazyky jako Common Lisp, Dylan a PL/I.

Sémantika ukončení

Mechanismy zpracování výjimek v současných jazycích se obvykle nedají obnovit („sémantika ukončení“) na rozdíl od hardwarových výjimek, které lze obvykle obnovit. To je založeno na zkušenostech s používáním obou, protože existují teoretické a konstrukční argumenty ve prospěch obou rozhodnutí; ty byly rozsáhle diskutovány během diskusí o standardizaci C ++ v letech 1989–1991, které vyústily v definitivní rozhodnutí pro sémantiku ukončení. K odůvodnění takového návrhu mechanismu C ++ Stroustrup poznamenává:

[A] t Palo Alto [C ++ standardization] meeting in November 1991, uslyšeli jsme brilantní shrnutí argumentů pro sémantiku ukončení podložené osobními zkušenostmi a daty od Jima Mitchella (od Sun, dříve od Xerox PARC). Jim používal zpracování výjimek v půl tuctu jazyků po dobu 20 let a byl jedním z prvních zastánců obnovovací sémantiky jako jeden z hlavních návrhářů a implementátorů systému Xerox Cedar/Mesa . Jeho zpráva byla

„Ukončení má přednost před obnovením; to není věc názoru, ale otázka let zkušeností. Obnovení je svůdné, ale neplatné. “

Toto prohlášení podpořil zkušenostmi z několika operačních systémů. Klíčovým příkladem byl Cedar/Mesa: Napsali to lidé, kteří měli rádi a používali obnovení, ale po deseti letech používání zbývalo v systému půl milionu řádků jen jedno použití obnovení - a to bylo kontextové zkoumání. Protože obnovení nebylo pro takovéto kontextové šetření vlastně nutné, odstranili ho a zjistili v této části systému výrazné zvýšení rychlosti. V každém případě, kdy bylo použito obnovení, se během deseti let stal problémem a nahradil jej vhodnější design. V zásadě každé použití obnovení představovalo selhání udržet oddělené úrovně abstrakce.

Kritika

Kontrastní pohled na bezpečnost zpracování výjimek poskytl Tony Hoare v roce 1980 a popsal programovací jazyk Ada jako „... spoustu funkcí a notačních konvencí, z nichž mnohé jsou zbytečné a některé z nich, jako je zpracování výjimek, dokonce nebezpečné. [...] Nedovolte, aby byl tento jazyk v jeho současném stavu používán v aplikacích, kde je kritická spolehlivost [...]. Další raketa, která by zabloudila v důsledku chyby programovacího jazyka, nemusí být průzkumná vesmírná raketa na neškodném výletu na Venuši: Může to být jaderná hlavice explodující nad jedním z našich vlastních měst. “

Zpracování výjimek často není v softwaru zpracováno správně, zvláště když existuje více zdrojů výjimek; analýza toku dat 5 milionů řádků kódu Java zjistila více než 1300 vad zpracování výjimek. Weimer a Necula s odvoláním na několik předchozích studií jiných (1999–2004) a jejich vlastních výsledků napsali, že významným problémem s výjimkami je, že „vytvářejí skryté cesty toku řízení, o nichž je pro programátory obtížné uvažovat“.

Go byl původně vydán s výslovně vynechaným zpracováním výjimek, přičemž vývojáři tvrdili, že zmatl tok řízení . Později výjimku podobné panic/ recovermechanismus byl přidán do jazyka, který autoři Go poradit s použitím pouze neodstranitelné chyby, která by měla zastavit celý proces.

Výjimky, jako nestrukturovaný tok, zvyšují riziko úniku zdrojů (například únik ze sekce uzamčené mutexem nebo dočasně otevřeného souboru) nebo nekonzistentní stav. Existují různé techniky pro správu zdrojů za přítomnosti výjimek, nejčastěji kombinující vzor vyřazení s nějakou formou ochrany proti odvíjení (jako finallyklauzule), která automaticky uvolní prostředek, když ovládací prvek opustí část kódu.

Podpora výjimek v programovacích jazycích

Mnoho počítačových jazyků má vestavěnou podporu pro výjimky a zpracování výjimek. To zahrnuje ActionScript , Ada , BlitzMax , C ++ , C# , Clojure , COBOL , D , ECMAScript , Eiffel , Java , ML , Next Generation Shell , Object Pascal (např. Delphi , Free Pascal a podobně), PowerBuilder , Objective-C , OCaml , PHP (od verze 5), PL/I , PL/SQL , Prolog , Python , REALbasic , Ruby , Scala , Seed7 , Smalltalk , Tcl , Visual Prolog a většina jazyků .NET . Zpracování výjimek se v těchto jazycích běžně nedá obnovit a když je vyvolána výjimka, program prohledává zpět hromadu volání funkcí, dokud není nalezen obslužný program výjimek.

Některé jazyky vyžadují postupné odvíjení zásobníku. To znamená, že pokud funkce f , obsahující obslužnou rutinu H pro výjimku E , volá funkci g , která zase volá funkci h , a v h dojde k výjimce E , pak mohou být funkce h a g ukončeny a H v f zvládne E .

Jazyky zpracovávající výjimky bez tohoto odvíjení jsou Common Lisp s jeho podmíněným systémem , PL/I a Smalltalk . Všichni volají obslužný program výjimek a neodvíjejí zásobník; nicméně v PL/I, pokud "ON jednotka" (obsluha výjimek) udělá GOTO z ON jednotky, tím se odvíjí zásobník. Obsluha výjimky má možnost restartovat výpočet, pokračovat nebo se uvolnit. To umožňuje programu pokračovat ve výpočtu přesně na stejném místě, kde došlo k chybě (například když byl k dispozici dříve chybějící soubor) nebo implementovat oznámení, protokolování, dotazy a proměnné tekutiny nad mechanismus zpracování výjimek (jako hotovo v Smalltalk). Realizace odkládání tohoto Mythryl programovací jazyk podporuje konstantním čase zpracování výjimek, aniž by stack odvíjení.

S výjimkou drobných syntaktických rozdílů se používá pouze několik stylů zpracování výjimek. V nejpopulárnějším stylu je výjimka iniciována zvláštním příkazem ( thrownebo raise) s objektem výjimky (např. S ​​Java nebo Object Pascal) nebo hodnotou speciálního výčtového typu (např. S ​​Ada nebo SML). Rozsah výjimek manipulátory začíná klauzule markerů ( trynebo v jazyce blok spouštěče, jako je begin) a končí na začátku první věty psovoda ( catch, except, rescue). Může následovat několik klauzulí obsluhy a každá může určit, jaké typy výjimek zpracovává a jaký název používá pro objekt výjimky.

Několik jazyků také povoluje klauzuli ( else), která se používá v případě, že před dosažením konce oblasti obsluhy nedošlo k žádné výjimce.

Častější je související klauzule ( finallynebo ensure), která se spouští bez ohledu na to, zda došlo k výjimce, obvykle k uvolnění prostředků získaných v těle bloku zpracování výjimek. Je pozoruhodné, že C ++ tuto konstrukci neposkytuje, protože podporuje techniku získávání zdrojů je inicializace (RAII), která uvolňuje prostředky pomocí destruktorů .

Celkově může kód pro zpracování výjimek vypadat takto (v pseudokódu podobném Javě ):

try {
    line = console.readLine();

    if (line.length() == 0) {
        throw new EmptyLineException("The line read from console was empty!");
    }

    console.printLine("Hello %s!" % line);
    console.printLine("The program ran successfully.");
}
catch (EmptyLineException e) {
    console.printLine("Hello!");
}
catch (Exception e) {
    console.printLine("Error: " + e.message());
}
finally {
    console.printLine("The program is now terminating.");
}

Jako drobnou variaci některé jazyky používají klauzuli jediného psovoda, která se interně zabývá třídou výjimky.

Podle dokumentu Westley Weimera a George Neculy z roku 2008 přispívá syntaxe try... finallybloků v Javě k závadám softwaru. Když metoda potřebuje zvládnout získání a uvolnění 3–5 zdrojů, programátoři zjevně nejsou ochotni vnořit dostatek bloků kvůli problémům s čitelností, i když by to bylo správné řešení. Je možné použít jeden try... finallyblok, i když pracujete s více zdroji, ale to vyžaduje správné použití hodnot sentinel , což je další běžný zdroj chyb pro tento typ problému. Pokud jde o sémantiku try... catch... finallykonstruktu obecně, Weimer a Necula píší, že „Zatímco try-catch-konečne je koncepčně jednoduché, má nejsložitější popis provedení v jazykové specifikaci [Gosling et al. 1996] a ve svém oficiálním anglickém popisu vyžaduje čtyři úrovně vnořených „if“. Stručně řečeno, obsahuje velké množství rohových případů, které programátoři často přehlížejí. "

C podporuje různé způsoby kontroly chyb, ale obecně se nepovažuje za podporu „zpracování výjimek,“ i když je setjmpi longjmpfunkce standardní knihovny může být použit k realizaci výjimek sémantiku.

Perl má volitelnou podporu pro zpracování strukturovaných výjimek.

Podpora Pythonu pro zpracování výjimek je všudypřítomná a konzistentní. Je obtížné psát robustní program Python bez použití svých trya exceptklíčová slova.

Zpracování výjimek v hierarchiích uživatelského rozhraní

Nedávné front-endové webové rámce, jako například React a Vue , zavedly mechanismy zpracování chyb, kde se chyby šíří do hierarchie komponent uživatelského rozhraní, a to způsobem, který je analogický s tím, jak se chyby šíří v zásobníku volání při provádění kódu. Mechanismus hraniční chyby zde slouží jako analogie k typickému mechanismu try-catch. Komponenta tedy může zajistit, že chyby z jejích podřízených komponent budou zachyceny a zpracovány a nebudou šířeny do nadřazených komponent.

Například ve Vue komponenta zachytí chyby implementací errorCaptured

Vue.component('parent', {
    template: '<div><slot></slot></div>',
    errorCaptured: (err, vm, info) => alert('An error occurred');
})
Vue.component('child', {
    template: '<div>{{ cause_error() }}</div>'
})

Při použití takto v označení:

<parent>
    <child></child>
</parent>

Chyba způsobená podřízenou komponentou je zachycena a zpracována nadřazenou komponentou.

Implementace zpracování výjimek

Implementace zpracování výjimek v programovacích jazycích obvykle zahrnuje značné množství podpory jak z generátoru kódu, tak z runtime systému doprovázejícího překladač. (Právě přidáním zpracování výjimek do C ++ skončila užitečná životnost původního kompilátoru C ++, Cfront .) Nejběžnější jsou dvě schémata. První, dynamická registrace , generuje kód, který průběžně aktualizuje struktury o stavu programu z hlediska zpracování výjimek. Obvykle to přidá nový prvek do rozložení rámce zásobníku, který ví, jaké obslužné rutiny jsou k dispozici pro funkci nebo metodu přidruženou k tomuto rámci; pokud je vyvolána výjimka, ukazatel v rozvržení směruje modul runtime na příslušný kód obsluhy. Tento přístup je kompaktní z hlediska prostoru, ale přidává režijní náklady na vstup a výstup rámce. Běžně se používalo například v mnoha implementacích Ada, kde již byla pro mnoho dalších jazykových funkcí potřeba komplexní podpora generování a běhu. Dynamická registrace, kterou lze poměrně snadno definovat, je přístupná k prokázání správnosti .

Druhé schéma a schéma implementované v mnoha kompilátorech C ++ v produkční kvalitě je přístup založený na tabulce . To vytváří statické tabulky v době kompilace a v době propojení, které souvisejí s rozsahy čítače programu se stavem programu s ohledem na zpracování výjimek. Pokud je vyvolána výjimka, runtime systém vyhledá aktuální umístění instrukcí v tabulkách a určí, jaké ovladače jsou ve hře a co je třeba udělat. Tento přístup minimalizuje režijní náklady pro případ, kdy není vyvolána výjimka. To se děje za cenu určitého místa, ale tento prostor lze alokovat do datových sekcí pouze pro čtení, které nejsou načteny nebo přemístěny, dokud není skutečně vyvolána výjimka. Tento druhý přístup je také lepší z hlediska dosažení bezpečnosti závitu .

Byly také navrženy další definiční a implementační schémata. U jazyků, které podporují metaprogramování , byly pokročilé přístupy, které vůbec nezahrnují režijní náklady (nad rámec již existující podpory reflexe ).

Zpracování výjimek na základě návrhu podle smlouvy

Jiný pohled na výjimky vychází ze zásad návrhu podle smlouvy a je podpořen zejména eiffelovým jazykem . Cílem je poskytnout přísnější základ pro zpracování výjimek přesnou definicí toho, co je „normální“ a „abnormální“ chování. Tento přístup je konkrétně založen na dvou konceptech:

  • Selhání : neschopnost operace splnit svou smlouvu. Například sčítání může způsobit aritmetické přetečení (nesplňuje svou smlouvu na výpočet dobré aproximace k matematickému součtu); nebo rutina nemusí splnit své podmínky.
  • Výjimka : abnormální událost, ke které dochází během provádění rutiny (tato rutina je „ příjemcem “ výjimky) během jejího provádění. Taková neobvyklá událost je důsledkem selhání operace vyvolané rutinou.

„Princip bezpečného zpracování výjimek“, jak jej představil Bertrand Meyer v Object-Oriented Software Construction, pak tvrdí, že existují pouze dva smysluplné způsoby, jak může rutina reagovat, když dojde k výjimce:

  • Selhání nebo „organizovaná panika“: Rutina opraví stav objektu opětovným zavedením invariantu (jedná se o „organizovanou“ část) a poté selže (panika), což vyvolá výjimku u jejího volajícího (takže abnormální událost je neignorováno).
  • Opakovat: Rutina zkusí algoritmus znovu, obvykle po změně některých hodnot, takže další pokus bude mít větší šanci uspět.

Zejména pouhé ignorování výjimky není povoleno; blok musí být buď opakován a úspěšně dokončen, nebo šířit výjimku svému volajícímu.

Zde je příklad vyjádřený v Eiffelově syntaxi. Předpokládá, že rutina send_fastje obvykle lepší způsob odeslání zprávy, ale může selhat a vyvolat výjimku; pokud ano, použije algoritmus další send_slow, který selže méně často. Pokud send_slowselže, rutina sendjako celek by měla selhat, což způsobí, že volající získá výjimku.

send (m: MESSAGE) is
  -- Send m through fast link, if possible, otherwise through slow link.
local
  tried_fast, tried_slow: BOOLEAN
do
  if tried_fast then
     tried_slow := True
     send_slow (m)
  else
     tried_fast := True
     send_fast (m)
  end
rescue
  if not tried_slow then
     retry
  end
end

Logické lokální proměnné jsou na začátku inicializovány na hodnotu False. Pokud send_fastselže, tělo ( doklauzule) bude provedeno znovu, což způsobí spuštění send_slow. Pokud se toto provedení send_slownezdaří, rescueklauzule se spustí do konce bez retry( elseve finále žádná klauzule if), což způsobí selhání rutinního spuštění jako celku.

Tento přístup má tu výhodu, že jasně definuje, co jsou „normální“ a „abnormální“ případy: abnormální případ, který způsobuje výjimku, je takový, kdy rutina není schopna splnit svou smlouvu. Definuje jasné rozdělení rolí: dodoložka (normální tělo) má na starosti dosažení nebo pokus o dosažení smlouvy rutiny; rescueklauzule má na starosti obnovovat kontext a restartování procesu, pokud to má šanci na úspěch, ale ne provádět žádné skutečné výpočty.

Ačkoli výjimky v Eiffelově filozofii mají celkem jasnou filozofii, Kiniry (2006) kritizuje jejich implementaci, protože „Výjimky, které jsou součástí definice jazyka, jsou reprezentovány hodnotami INTEGER, výjimky definované vývojářem hodnotami STRING. [...] Navíc, protože jsou základní hodnoty, nikoli objekty, nemají žádnou vlastní sémantiku nad rámec toho, co je vyjádřeno v pomocné rutině, která nutně nemůže být spolehlivá kvůli účinnému přetížení reprezentace (např. nelze rozlišit dvě celá čísla stejné hodnoty). “

Nezachycené výjimky

Pokud je vyvolána výjimka a není chycena (z provozního hlediska je vyvolána výjimka, pokud není zadán žádný použitelný obslužný program), nezachycenou výjimku zpracovává modul runtime; rutina, která to dělá, se nazývá nezachycený obslužný program výjimek . Nejběžnějším výchozím chováním je ukončit program a vytisknout na konzolu chybovou zprávu, obvykle včetně informací o ladění, jako je řetězcová reprezentace výjimky atrasování zásobníku. Tomu se často vyhneme tím, že máme obslužný program nejvyšší úrovně (na úrovni aplikace) (například vesmyčce událostí), který zachytí výjimky, než dosáhnou běhu.

Všimněte si, že i když nezachycená výjimka může mít za následek neobvyklé ukončení programu (program nemusí být správný, pokud není výjimka zachycena, zejména tím, že nevrátí zpět částečně dokončené transakce nebo neuvolní zdroje), proces se ukončí normálně (za předpokladu, že runtime funguje správně), protože runtime (který řídí provádění programu) může zajistit řádné vypnutí procesu.

V programu s více vlákny může místo toho nezachycená výjimka ve vlákně vést k ukončení právě tohoto vlákna, nikoli celého procesu (nezachycené výjimky v obsluze na úrovni vláken jsou zachyceny obsluhou nejvyšší úrovně). To je zvláště důležité pro servery, kde lze například ukončit servlet (běžící ve vlastním vlákně), aniž by to ovlivnilo celkový stav serveru.

Tento výchozí obslužný program nezachycených výjimek může být přepsán, a to globálně nebo za vlákno, například za účelem poskytnutí alternativního protokolování nebo hlášení koncových uživatelů o nezachycených výjimkách nebo restartování vláken, která se ukončí kvůli nezachycené výjimce. Například v Javě se to dělá pro jedno vlákno přes Thread.setUncaughtExceptionHandlera globálně přes Thread.setDefaultUncaughtExceptionHandler; v Pythonu se to provádí úpravou sys.excepthook.

Statická kontrola výjimek

Ověřené výjimky

Návrháři Javy vymysleli zaškrtnuté výjimky, které jsou speciální sadou výjimek. Zkontrolované výjimky, které metoda může vyvolat, jsou součástí podpisu metody . Pokud například metoda může vyvolat IOException, musí tuto skutečnost výslovně deklarovat v podpisu metody. Pokud tak neučiníte, dojde k chybě při kompilaci.

Kiniry (2006) však poznamenává, že knihovny Java (jako tomu bylo v roce 2006) byly často nekonzistentní ve svém přístupu k hlášení chyb, protože „Ne všechny chybné situace v Javě představují výjimky. Mnoho metod vrací speciální hodnoty, které indikují selhání kódované jako konstantní pole souvisejících tříd “.

Zkontrolované výjimky souvisejí s kontrolami výjimek, které existují pro programovací jazyk OCaml . Externí nástroj pro OCaml je jak neviditelný (tj. Nevyžaduje žádné syntaktické anotace), tak volitelný (tj. Je možné kompilovat a spouštět program bez kontroly výjimek, i když to není doporučeno pro produkční kód).

Programovací jazyk CLU měl funkci s rozhraním blíže k čemu Java zavedla později. Funkce by mohla vyvolávat pouze výjimky uvedené v jejím typu, ale všechny unikající výjimky z volaných funkcí by se automaticky změnily na jedinou výjimku za běhu failure, místo aby došlo k chybě při kompilaci. Později měla Modula-3 podobnou funkci. Tyto funkce nezahrnují kontrolu času kompilace, která je ústřední v konceptu kontrolovaných výjimek a nebyla (od roku 2006) začleněna do jiných programovacích jazyků než Java.

Dřívější verze programovacího jazyka C ++ obsahovaly volitelný mechanismus pro kontrolované výjimky, který se nazývá specifikace výjimek . Ve výchozím nastavení může jakákoli funkce vyvolat jakoukoli výjimku, ale může to být omezeno klauzulí přidanou k podpisu funkce, která určuje, které výjimky může funkce vyvolat. Specifikace výjimek nebyly v době kompilace vynuceny. Porušení vedla k volání globální funkce . Mohla by být zadána prázdná specifikace výjimky, která by naznačovala, že funkce nevyvolá žádnou výjimku. To nebylo nastaveno jako výchozí, když bylo do jazyka přidáno zpracování výjimek, protože by to vyžadovalo příliš mnoho úprav stávajícího kódu, znemožnilo by to interakci s kódem napsaným v jiných jazycích a svádělo by to programátory k psaní příliš mnoha obslužných programů na lokálním úroveň. Explicitní použití specifikací prázdných výjimek by však mohlo kompilátorům C ++ umožnit provádět významné optimalizace rozložení kódu a zásobníku, které jsou vyloučeny, když může ve funkci probíhat zpracování výjimek. Někteří analytici považovali správné použití specifikací výjimek v C ++ za obtížně dosažitelné. Toto použití specifikací výjimek bylo zahrnuto v C ++ 03 , zastaralé v jazykovém standardu 2012 C ++ ( C ++ 11 ) a bylo z jazyka v C ++ 17 odebráno . Funkci, která nebude vyvolávat žádné výjimky, lze nyní označit klíčovým slovem. throwstd::unexpectednoexcept

Na rozdíl od Javy jazyky jako C# nevyžadují deklaraci žádného typu výjimky. Podle Hanspetera Mössenböcka, nerozlišování mezi výjimkami, které mají být nazývány (kontrolované), a výjimkami, které nelze nazývat (nekontrolovanými), činí psaný program pohodlnějším, ale méně robustním, protože nezachycená výjimka má za následek přerušení trasování zásobníku . Kiniry (2006) však poznamenává, že Java JDK (verze 1.4.1) vyvolává velké množství nekontrolovaných výjimek: jedna na každých 140 řádků kódu, zatímco Eiffel je používá mnohem střídměji, přičemž jeden vyvolá každých 4600 řádků kódu. Kiniry také píše, že „Jak každý Java programátor ví, objem try catchkódu v typické aplikaci Java je někdy větší než srovnatelný kód nezbytný pro explicitní kontrolu parametrů a návratové hodnoty v jiných jazycích, které nemají zaškrtnuté výjimky. Ve skutečnosti obecná shoda mezi zákopovými Java programátory je, že řešení kontrolovaných výjimek je téměř stejně nepříjemný úkol jako psaní dokumentace. Mnoho programátorů tedy uvádí, že „nesouhlasí“ s kontrolovanými výjimkami. To vede k hojnosti kontrolovaných, ale ignorovaných výjimky “. Kiniry také poznamenává, že vývojáři C# byli zjevně ovlivněni tímto druhem uživatelských zkušeností, přičemž jim byl přičten následující citát (prostřednictvím Erica Gunnersona):

„Zkoumání malých programů vede k závěru, že vyžadování specifikací výjimek by mohlo jak zvýšit produktivitu vývojářů, tak zlepšit kvalitu kódu, ale zkušenosti s velkými softwarovými projekty naznačují jiný výsledek - sníženou produktivitu a malé nebo žádné zvýšení kvality kódu.“

Podle Anderse Hejlsberga existovala v jejich návrhové skupině poměrně široká shoda na tom, že v jazyce C#nebudou kontrolovány výjimky jako jazyková funkce. Hejlsberg to v rozhovoru vysvětlil

"Klauzule throws, přinejmenším způsob, jakým je implementována v Javě, vás nutně nemusí nutně zpracovávat výjimky, ale pokud je nezvládáte, nutí vás přesně uznat, které výjimky by mohly projít." Vyžaduje, abyste buď odchytili deklarované výjimky, nebo je vložili do vlastní klauzule vrhů. Aby tento požadavek vyřešili, dělají lidé směšné věci. Například zdobí každou metodu „hodí výjimku“. Tím se tato funkce zcela porazí a programátor vás donutil psát více hltavých keců. To nikomu nepomůže. "

Pohledy na využití

Zkontrolované výjimky mohou v době kompilace snížit výskyt nevyřízených výjimek, které se v dané aplikaci objevují za běhu . Nekontrolované výjimky (například objekty JavaRuntimeException a Error) zůstávají neošetřené.

Zkontrolované výjimky však mohou buď vyžadovat rozsáhlá throwsprohlášení, odhalení podrobností implementace a omezení zapouzdření , nebo podpořit kódování špatně uvažovaných bloků, které mohou skrývat oprávněné výjimky před jejich příslušnými obslužnými rutinami. Zvažte rostoucí kódovou základnu v průběhu času. Rozhraní může být deklarováno tak, aby vyvolávalo výjimky X a Y. V novější verzi kódu, pokud by někdo chtěl vyvolat výjimku Z, způsobil by, že by byl nový kód nekompatibilní s předchozími způsoby použití. Kromě toho, u vzoru adaptéru , ve kterém jedna část kódu deklaruje rozhraní, které je pak implementováno jiným tělem kódu, takže kód může být zapojen a volán prvním, může mít kód adaptéru bohatou sadu výjimek popisovat problémy, ale je nucen používat typy výjimek deklarované v rozhraní. try/catch

Je možné snížit počet deklarovaných výjimek buď vyhlášením nadtřídy všech potenciálně vyvolaných výjimek, nebo definováním a deklarací typů výjimek, které jsou vhodné pro úroveň abstrakce volané metody a mapování výjimek nižší úrovně na tyto typy, nejlépe zabalené pomocí řetězení výjimek , aby byla zachována hlavní příčina. Kromě toho je velmi možné, že ve výše uvedeném příkladu měnícího se rozhraní bude nutné upravit také volající kód, protože v jistém smyslu jsou výjimky, které metoda může vyvolat, součástí implicitního rozhraní metody.

Použití deklarace nebo obvykle stačí k uspokojení kontroly v Javě. I když to může mít určité využití, v zásadě to obchází mechanismus zaškrtnutých výjimek, což Oracle odrazuje. throws Exceptioncatch (Exception e)

Nekontrolované typy výjimek by obecně neměly být zpracovávány, s výjimkou možných na nejzazších úrovních rozsahu. Ty často představují scénáře, které neumožňují obnovu: s často odrážejí programové vady a s obecně představují neodstranitelné chyby JVM. I v jazyce, který podporuje zaškrtnuté výjimky, existují případy, kdy použití kontrolovaných výjimek není vhodné. RuntimeExceptionError

Dynamická kontrola výjimek

Smyslem rutin zpracování výjimek je zajistit, aby kód zvládl chybové podmínky. Aby se zjistilo, že rutiny zpracování výjimek jsou dostatečně robustní, je nutné předložit kód se širokým spektrem neplatných nebo neočekávaných vstupů, které mohou být vytvořeny pomocí softwarové injekce při poruše softwaru a testování mutací (což je také někdy označováno jako fuzz testování ). Jedním z nejobtížnějších typů softwaru, pro který lze psát rutiny zpracování výjimek, je protokolární software, protože na příjem vstupů, které neodpovídají příslušným specifikacím, musí být připravena robustní implementace protokolu.

Aby bylo zajištěno, že smysluplnou regresní analýzu lze provádět v průběhu životního cyklu vývoje softwaru , jakékoli testování zpracování výjimek by mělo být vysoce automatizované a testovací případy musí být generovány vědeckým, opakovatelným způsobem. Existuje několik komerčně dostupných systémů, které takové testování provádějí.

V prostředích modulu runtime, jako je Java nebo .NET , existují nástroje, které se připojují k modulu runtime, a pokaždé, když dojde k výjimce zájmu, zaznamenají informace o ladění, které existovaly v paměti v době vyvolání výjimky ( zásobník volání a halda hodnoty). Tyto nástroje se nazývají nástroje pro automatizované zpracování výjimek nebo zachycování chyb a pro výjimky poskytují informace o příčině.

Synchronicita výjimek

Poněkud související s konceptem kontrolovaných výjimek je synchronicita výjimek . Synchronní výjimky se vyskytují v konkrétním programovém příkazu, zatímco asynchronní výjimky se mohou zvýšit prakticky kdekoli. Z toho vyplývá, že asynchronní zpracování výjimek kompilátor nevyžaduje. Je také obtížné s nimi programovat. Mezi příklady přirozeně asynchronních událostí patří stisknutí Ctrl-C k přerušení programu a příjem signálu jako „stop“ nebo „pozastavení“ z jiného vlákna provádění .

Programovací jazyky to obvykle řeší omezením asynchronicity, například Java zastarala používání své výjimky ThreadDeath, která byla použita k tomu, aby umožnilo jednomu vláknu zastavit jiné. Místo toho mohou existovat semi-asynchronní výjimky, které se vyvolávají pouze na vhodných místech programu nebo synchronně.

Kondiční systémy

Společné Lisp , Dylan a Smalltalk mají systém podmínek (viz Common Lisp Condition System ), který zahrnuje výše uvedené systémy zpracování výjimek. V těchto jazycích nebo prostředích příchod podmínky („zobecnění chyby“ podle Kenta Pitmana ) znamená volání funkce a pouze pozdě v obsluze výjimek může být přijato rozhodnutí o uvolnění zásobníku.

Podmínky jsou zobecněním výjimek. Když nastane podmínka, vyhledá se příslušná obsluha podmínek a vybere se v pořadí zásobníků, aby podmínku zvládla. Podmínky, které nepředstavují chyby, mohou bezpečně zůstat zcela neošetřené; jejich jediným účelem může být šíření rad nebo varování směrem k uživateli.

Nepřetržité výjimky

To souvisí s takzvaným obnovovacím modelem zpracování výjimek, ve kterém se říká, že některé výjimky jsou kontinuální : je povoleno vrátit se k výrazu, který signalizoval výjimku, poté, co v obslužné rutině provedli nápravná opatření. Systém podmínek je zobecněn takto: v obsluze nezávažného stavu (aka spojitá výjimka ) je možné přeskočit na předdefinované body restartu (aka restarty ), které leží mezi signalizačním výrazem a obsluhou podmínky. Restarty jsou funkce uzavřené v některém lexikálním prostředí, což umožňuje programátorovi opravit toto prostředí před úplným ukončením obslužné rutiny podmínek nebo částečným odvinutím zásobníku.

Příkladem je podmínka ENDPAGE v PL/I; jednotka ON může zapisovat řádky upoutávky stránky a řádky záhlaví pro další stránku, poté propadnout a pokračovat v provádění přerušeného kódu.

Restartuje oddělený mechanismus od zásad

Zpracování podmínek navíc poskytuje oddělení mechanismu od zásad . Restartování poskytuje různé možné mechanismy zotavení z chyby, ale nevybírejte, který mechanismus je v dané situaci vhodný. To je provincie obsluhy podmínek, která (protože se nachází v kódu vyšší úrovně) má přístup k širšímu pohledu.

Příklad: Předpokládejme, že existuje funkce knihovny, jejímž účelem je analyzovat jednu položku souboru syslog . Co by měla tato funkce dělat, pokud je položka poškozená? Neexistuje jedna správná odpověď, protože stejnou knihovnu lze nasadit do programů pro mnoho různých účelů. V interaktivním prohlížeči souborů protokolu by mohlo být správné vrátit položku bez analýzy, aby ji uživatel viděl-ale v automatizovaném programu shrnujícím protokol může být správné zadat nulové hodnoty pro nečitelná pole, ale přeruší se s chybou, pokud bylo poškozeno příliš mnoho položek.

To znamená, že na tuto otázku lze odpovědět pouze z hlediska širších cílů programu, které obecná funkce knihovny nezná. Ukončení s chybovou zprávou je však jen zřídka správnou odpovědí. Takže místo jednoduchého ukončení s chybou může funkce navazovat restarty, které nabízejí různé způsoby, jak pokračovat - například přeskočit záznam do protokolu, zadat výchozí nebo nulové hodnoty pro nečitelná pole, požádat uživatele o chybějící hodnoty nebo k uvolnění zásobníku a přerušení zpracování pomocí chybové zprávy. Nabízené restarty představují dostupné mechanismy pro obnovu z chyby; výběr restartu obsluhou podmínky dodá zásadu .

Viz také

Reference

externí odkazy