/ janturon.cz / Od kodéra k analytikovi / Zpracování chyb

Zpracování chyb (error processing)

Teorie

Chybou zde není myšlen omyl při rozhodování, ale stav aplikace, který se liší od předpokládaného stavu. Příčinou bývají špatná data (corrupted data): nevyplněná nebo špatně vyplněná nebo také záměrný útok. Špatnými daty jsou myšleny nádsledující kategorie. Máme-li zadat věk dítěte 0-15 let, pak:

Podle odolnosti proti špatným datům klasifikujeme systémy následovně:

Existují tyto vzory řešení zpracování chyb:

Pokud chybu jakýmkoliv výšeuvedeným způsobem odchytíme, měli bychom na ni zareagovat. Možnosti reakce shrnují následující vzory:

Hlášení chyb (error reporting): i pokud systém chybu správně zpracuje, přece jenom je to chyba a neměla by zůstat jen tak bez povšimnutí. Základní podmínkou odstranění chyby je její zopakování (replikace), tedy určení stavu, který vždy vede k té samé chybě. Vývojář by se proto měl dozvědět co nejdetailnější popis chyby. Neměl by se to ale dozvědět koncový uživatel: zaprvé ho detaily nezajímají (stačí ho v případě odmítnutí informovat, že k chybě došlo) a za druhé popis chyby může obsahovat citlivé údaje (třeba heslo), což je vážné ohrožení bezpečnosti. Standardně se proto chyby hlásí:

Ladící programy (debuggers) většiny IDE (vývojových prostředí) umožňují následující funkce:

U kompilovaných programů se pro účely ladění ve (většinou výchozí) volbě kompilace debug do kódu vkládají instrukce pro práci s ladícím programem. Tyto instrukce nejsou v produkční (release) verzi zapotřebí, a pokud zadáme tuto volbu, kompilátor je tam nedoplní.

K odstraňování chyb (běhových, které se projeví až při běhu aplikace, ne syntaktických, na které kompilátor upozorní při překladu) používá tento univerzální postup:

  1. Replikace chyby: pokusíme se chybu zopakovat a určit posloupnost akcí, které vždy vedou k chybě. Některé chyby replikovat nelze, v tom případě je nutno provést detailní rozbor.
  2. Izolace chyby: s pomocí ladících nástrojů a chybových hlášení (nebo postupným vylučováním v případě nereplikované chyby) určíme místo v kódu, kde se chyba vyskytuje. Většinou to bývá neočekávaná hodnota proměnné, nejčastěji null.
  3. Určení příčiny: následuje pátrání po tom, jak je možné, že proměnná nabývá neočekávané hodnoty. Při nedostatku představivosti velmi pomůže krokování ladícího programu.
  4. Ošetření chyby: v místě chyby nebo dříve v běhu programu je místo, kde jsme zapomněli provést ošetření chyby. Chybu ošetříme vhodným z uvedených vzorů.

Praxe

Error state - chybu opravíme aktivním otestováním chybového stavu

// C++ class UserInfo { bool jobEmpty; public: UserInfo() : jobEmpty(false) { } bool JobEmpty() { return jobEmpty; } string getJob() { string input; cout << "Your job: "; getline(cin,input); jobEmpty = input.find_first_not_of(' ')==string::npos; return input; } }; int main() { UserInfo questions; string theJob = questions.getJob(); // error state if (questions.JobEmpty()) theJob = "unknown"; cout << theJob; return 0; } // Javascript function UserInfo() { var that = this; var jobEmpty; this.JobEmpty = function() { return jobEmpty; } this.getJob = function() { var input = prompt("Your job: "); jobEmpty = input.search(/\S/)==-1; return input; } } var questions = new UserInfo(); var theJob = questions.getJob(); if(questions.JobEmpty()) theJob = "unknown"; alert(theJob);

Error handling - nastavíme funkci, která se automaticky zavolá a opraví výstup, pokud byl zadán prázdný (zde zopakuje otázku)

// C++ class UserInfo { function<string()> error; public: UserInfo() { error = []()->string { return ""; }; } void errorHandler(function<string()> h) { error = h; } string getJob() { string input; cout << "Your job: "; getline(cin,input); bool empty = input.find_first_not_of(' ')==string::npos; if(empty) input = error(); return input; } }; int main() { UserInfo questions; questions.errorHandler(bind(&UserInfo::getJob,&questions)); string theJob = questions.getJob(); cout << theJob; return 0; }

Zatímco metoda error je typu function<string()>, obslužná metoda getJob, ktoru ji požadujeme předat, je jiného typu: function<string(UserInfo*)>. Převod provádí funkce bind, viz též Základy funkcionálního programování. Podobné implementační detaily v Javascriptu netřeba řešit:

// Javascript function UserInfo() { var that = this; this.error = function() { return ""; } this.getJob = function() { var input = prompt("Your job: "); var empty = input.search(/\S/)==-1; if(empty) input = that.error(); return input; } } var questions = new UserInfo(); questions.error = questions.getJob; var theJob = questions.getJob(); alert(theJob);

Exceptions - pokud uživatel zadá nekompatibilní data, dojde k chybě při převodu na číslo; tuto chybu zachytíme, a nastavíme věk na 0: následující kód je ukázka no-throw guarantee zabezpečení.

C++ std funkce vyjímky nepoužívají (chyby řeší pomocí chybového stavu), nativní Javascript funkce také ne (běhové chyby jsou zamlčeny). Proto je ukázka v jazyce C#, kde s vyjímkami .NET framework běžně pracuje. Vyjímky jsou stejným způsobem zapisovány i v C++ i v Javascriptu.

// C# class UserInfo { public int getAge() { Console.Write("Your age: "); string result = Console.ReadLine().Trim(); try { int parsed = Int32.Parse(result); Console.WriteLine("Age accepted"); return parsed; } catch (Exception) { Console.WriteLine("Age set to default zero."); return 0; } } } void Main() { UserInfo questions = new UserInfo(); int age = questions.getAge(); }

Proces zpracování vyjímky je náročný, a proto bychom je měli používat jen při psaní znovupoužitelného kódu, kde je reakci vhodné nechat na koncovém uživateli, například při ukončení datového spojení při přenosu souboru.

Typickým zneužitím vyjímek je dělení nulou:

// zneužití int result; // definování mimo blok try try { result = a / b; } catch(DivideByZeroException) { result = 0; } // správně int result = b!=0 ? a / b : 0;

Pozn.: Vyjímky lze řešit i chybovou obsluhou tak, že do bloku catch{} doplníme funkci pro obsluhu chyby. Blok catch{} by měl být ponechán prázdný jen v odůvodněných případech: způsobí to ignorování chyby, viz teorie vlevo.