"Požadovaný stav"? "Normální běh programu"? WTF?!
Žádný stav přeci není "požadovaný" a celý běh programu je "normální". Když si představím stavový diagram programu, nemám důvod některé uzly malovat nějak jinak. Program prostě reaguje a možné události a "chyba" je jen jednou z možných událostí. Záměrně píšu v uvozovkách, protože to je jen naše označení pro určitý stav. Ale to označení je dost zavádějící - chybou totiž ve skutečnosti je jakékoli neošetření/neuvažování možného stavu. Že mi nedojde paket je úplně stejně "normální" stav, jako že dojde. Pokud mám nabídku z 5 položek označených čísly 1-5, je stisk jakékoli klávesy stejně "normální". Přece není žádnou "chybou" pokud uživatel stiskne L a není důvod takovou situaci obsluhovat nějak zcela odlišně než číslice 1-5. Je to úplně normální situace, protože dotyčná funkce mi může vrátit cokoli, co se dá na klávesnici stisknout, a já s tím musím jako programátor počítat.
To je typický pohled programátora s klapkami na očích. Pro všechny ostatní je samozřejmě nějaký běh programu normální, a to takový, kvůli kterému ten program používají. Třeba když si chce uživatel vytisknout PDF dokument, je normální běh PDF prohlížeče takový, že uživateli nakonec z tiskárny vyleze papír s vytištěným dokumentem. Můžete mít v programu ošetřené úplně všechny chyby, ale pokud se uživateli ten dokument vytisknout nepodaří, bude oprávněně nadávat, že ten program je k ničemu.
Jeden extrém jsou programy, které s chybami vůbec nepočítají a neošetřují je. Druhý extrém jste popsal vy, a spočívá v tom, že se někdo soustředí jenom na ošetření chyb a zapomene, že kromě toho by ten program také měl ještě něco rozumného dělat.
Kde tvrdím, že se soustřeďuji
jenom na ošetření chyb a zapomínám kvůli tomu, že má program taky něco rozumného dělat? Nebo jak jste na to přišel? Protože ošetřuji chyby stejně pečlivě jako kterýkoli jiný stav, mám podle vás klapky na očích? Co to pro boha melete za nesmysly?
Jaký mají výjimky kladný efekt na návrh programu? Co je správného na reagování na určité stavy nějakým úplně jiným kanálem někde v úplně jiné části programu? Postnul jsem dva články, na něž Kit napsal, že autor výjimky použil chybně. A jak je to tedy správně?
Správného je na tom to, že se na každý stav reaguje v té části programu, kde se na něj nějak reagovat dá. Například u toho tisku PDF může dojít k tomu, že v průběhu vykreslování stránky na tiskárnu dojde ke ztrátě spojení s tiskárnou. Vykreslovací kód s tím samozřejmě nic neudělá a ani s tím nic dělat nemá, na druhou stranu velice vhodné místo kde takovou událost řešit je kód pro interakci s uživatelem. Který může uživateli třeba zobrazit zprávu, že tiskárna bohužel přestala komunikovat, a nabídnout možnost vytisknout to na jiné tiskárně.
Vhodné místo, kde řešit takovou událost, je v modulu, který zajišťuje komunikaci s tiskárnou - například formou
if error && attempt_cntr-- => retry. Pokud tiskárna nereaguje, generuje chybu volajícímu, tedy asi vykreslujícímu kódu. Ten se podle toho zařídí po svém - např. uklizením svého kontextu a propagováním chyby ze své úrovně - zatímco komunikační úroveň zajistila konzistenci svého kontextu a hlásila nahoru chybu s komunikací, vykreslovací modul zajistí konzistenci na své úrovni a hlásí nahoru chybu při vykreslování písmenka X. Volající modul uklidí svůj kontext a propaguje výš chybu při pokusu vytisknout 5. písmenko 10. řádku 23. stránky. Teprve tady někde se o tom zpraví uživatel a případně se mu nabídne, co s tím dál. Může se třeba rozhodnout, že dokument počínaje 23. stránkou dotiskne na jiné tiskárně jak sám naznačujete, nebo překontrolovat tiskárnu a pokračovat v tisku stránkou, kde došlo k chybě apod. Ale pokud do této úrovně spadne nějaká low level chyba "printer communication error", tak co se s tím dá na této úrovni rozumného udělat, kromě "milý uživateli, tiskárna nekomunikuje, hroutím se k zemi"? Mám tomu rozmět tak, že kdybyste takový pdf-prohlížeč psal vy, tak při nějakém nahodilém zajiskření v konektoru se tisk zhroutí a musel bych běžet k tiskárně, abych zjistil, kam až a jestli vůbec se něco vytisklo, abych to pak mohl ručně poslat někam jinam nebo to zkusit znova, tipuji, že nejlépe celé od začátku? Děkuji pěkně, to teda píšete opravdu "velmi kvalitní a robustní" software.
Soustředíte se na nepodstatné detaily a ne na to podstatné, co chtěl ten autor říci, tj. že reakce na chybu je přes výjimky izolována od místa jejího vzniku, což tady někteří neustále vyzdvihujete jako přednost, ale podle mě je to zcela špatně, protože dochází k narušení a ztrátě kontextu. Kdybych reagoval v místě vzniku chyby, nepotřebuji balit informace o ní do žádného objektu, protože všechny dostupné informace o ní mám v rámci jejího kontextu k dispozici. A naopak - vyhození nějakého objektu - byť odvozeného speciálně pro danou úroveň - nemůže tuto informaci a ten kontext nahradit. Ono je to problematické i u těch hardwarových výjimek, ale tím spíše nechápu, proč bych si podobným mechanismem měl komplikovat práci i tam, kde to vůbec není nutné.
Svým způsobem ti rozumím, chápu, že objekt může nést více informací než nějaký errorcode, že je to konzistentní oproti vracení errocodu (návratovou hodnotou? referencí? globální proměnnou? někdy tak, jindy onak?), ale to řešení přes try-catch-throw se mi vůbec nelíbí a myslím, že je velmi znepřehledňující a komplikující.
Vždycky jsem se snažil oddělovat testy chybových stavů od zbytku kódu - IMHO je to ospravedlnění pro returny uprostřed funkcí - nejprve otestuji základní podmínky a případně skončím s chybovým kódem. Dále algoritmus pokračuje už s vědomím, že některé situace v daném bodě nastat nemohou - hodnoty jsou smysluplné, prostředky jsou alokované atp. Ale pořád je ještě spousta situací, kdy se testují podmínky v rámci samotného algoritmu - a tam mi try-catche jeho zápis neuvěřitelně zatemňují a komplikují.
Při ignorování/zapomenutí ošetření chyb z bezprostředně nižší úrovně vnímám výjimky spíš jako debugovací nástroj, ale rozhodně nesdílím názor, který tu opět několikrát padl, že se na to v podstatě díky výjimkám můžu vykašlat a chytit ji někde o 3 úrovně výš. Takto programovat je podle mě prasečina. Důsledné používání principu reinterpretace a případné propagace reinterpretované chyby se mi osvědčil do takové míry, že výjimky vnímám takto negativně.