Zpracování chyb a výjimek
2. Obsluha výjimek
V kódu je vhodné předcházet chybám jiným způsobem, než pouhým konstatováním „Nastala chyba“. Můžeme do kódu přidávat vlastní kód pro detekci chyb kolem každého příkazu, což je ale nešikovné, zdlouhavé a může to vést k vyšší náchylnosti k chybám.
Takový případ jsme již zažili v různých modifikacích algoritmu Kalkulacka. Vraťme se ke zdrojovému kódu KalkulackaVicenasobneVetveni, který řešil základní aritmetické operace podle zadaného číselného kódu (1 … sčítání, 2 … odečítání, 3 … násobení a 4 … dělení) u dvou uživatelem zadaných čísel a a b. Rozhodování o provedení aritmetické operace bylo provedeno pomocí vícenásobného větvení proměnné kod a výsledek aritmetické operace vypište v hlavním programu. Celý kód statické metody Main naleznete v následujícím odkazu:
-
Řešení: KalkulackaVicenasobneVetveni_Main
Podívejme se na jeden z potenciálně nebezpečných příkazů ve zdrojovém kódu, který by mohl vyvolat problémy - viz Kód 1. Představme si, že namísto očekávaného zadání celočíselného kódu aritmetické operace uživatel zadá text "bflm".
//Deklarace proměnných: 'kod' ... celočíselný kód aritmetické operace int kod; // ... //Načtení kódu operace kod = int.Parse(Console.ReadLine()); // ...
Kód 1: Výřez zdrojového kódu - program KalkulackaVicenasobneVetveni_Main - potenciálně nebezpečný kód konverze textu na celé číslo
Pokud jste provedli zadání textu "bflm" namísto celého čísla, byla vyvolána tzv. neošetřená výjimka - viz Obrázek 1.

Obrázek 1: Neošetřená výjimka vstupního řetězce
Výjimka tedy představuje specifický typ chyby, který může být vyvolán (nastat) při provádění programu. V kódu je tedy na snaze zpracovat ošetření možné chyby, tzv. obsluhy výjimky (anglicky „Exception“). Ošetření potenciálních chyb je možné pomocí příkazů try a catch.
Obecná syntaxe takových příkazů je:
try
{
nebezpečnýPříkaz
}
catch
{
těloHandleru
}
kde:
- nebezpečnýPříkaz - představuje příkaz nebo složený příkaz, který obsahuje potenciální nebezpečí chyby. Když dojde ke spuštění tohoto bloku a nedojde k vyvolání výjimky (nedojde k žádné chybě), proběhnou všechny příkazy postupně
- těloHandleru - pokud dojde k chybě při provádění nebezpečného příkazu, provádění kódu v tomto nebezpečném příkazu se opustí a skočí se do bloku umístěného za klíčové slovo catch, tzn., že bude provedeno tělo tzv. handleru. Handler (v překladu obsluha) představuje část kódu, který může ošetřit výjimku.
U handleru lze postupovat následujícími způsoby:
- Uvést parametr handleru:
catch(typVýjimky identifikátorVýjimky) {těloHandleru}
- Neuvádět parametr handleru:
catch{těloHandleru}
kde:
- typVýjimky - je výjimka vyjádřena jako parametr handleru, tzn. je uveden typVýjimky a za ním je uveden objekt třídy výjimky – identifikátorVýjimky. Tento objekt v sobě nese podrobné informace o výjimce
- těloHandleru - je kód, kterým bude ošetřen daný typ výjimky. Můžeme mít specifikovány různé kódy pro obsluhu různých typů výjimek, ke kterým může dojít v bloku za příkazem try. Pokud tedy některý z příkazů v bloku za try způsobí chybu, běhové prostředí (anglicky „Runtime“ – když je prováděn program) .NET Framework vygeneruje a vyvolá výjimku. Toto běhové prostředí prozkoumá typ vyvolané výjimky a je proveden kód obsluhující danou výjimku.
Druhou možností je neuvádět parametr handleru, tzn. neuvádět kulatou závorku a v ní typ a identifikátor výjimky. Tato možnost představuje univerzální handler, který zachytí všechny výjimky bez rozdílu. Tento způsob obsluhy se však nedoporučuje, poněvadž absencí objektu generovaného podle určité třídy výjimky ztrácíme informace o příslušné chybě.
Když obslužný kód výjimky skončí, program pokračuje prvním příkazem za tímto obslužným.
Existují různé typy výjimek. Jednotlivé typy výjimek (rozdělené podle třídy) např. objekt vygenerovaný podle třídy FormatException má v sobě uloženy různé informace, pomocí nichž můžeme prozkoumat přesnou příčinu výjimky. Mnoho z těchto vlastností je společných u všech typů výjimek např. vlastnost Message obsahuje textový popis chyby, která vyvolala výjimku. Tato vlastnost může být využita např. pro zápis do souboru se záznamem (s příponou *.log) činnosti aplikace, nebo ji lze zobrazit uživateli.
Běží-li program v režimu ladění ve Visual Studio můžeme (anglicky „Debugging“) výjimky generované naší aplikací zkoumat. Ladící program převezme řízení a pomocí dialogového okna lze zjistit příčinu vzniku výjimky. Lze i měnit hodnoty proměnných, krokovat kód atd.
Ilustrativní příklad KalkulackaVyjimka
Program KalkulackaVyjimka
- Popis: Ošetření výjimky chyby na vstupu
- Zadání: Zkuste implementovat v hlavní statické metodě Main pomocí příkazů try, catch ošetření výjimky vyvolané nesprávným zadáním celočíselného kódu aritmetické operace v programu KalkulackaVicenasobneVetveni_Main. Namísto celého čísla bude zadán text "bflm". V případě, že nebude zadán validní kód aritmetické operace, hlavní metodu ukončete
- Řešení:
KalkulackaVyjimka_Main
Ve výřezu našeho zdrojového kódu týkajícího se programu KalkulackaVyjimka vidíme, že jsme do kódu před potenciálně nebezpečný příkaz načtení celočíselného kódu aritmetické operace umístli příkaz try včetně začátku složené závorky – viz Kód 2. Konec složené závorky umístíme za příkazy kod = int.Parse(Console.ReadLine()); ,tj příkaz který načte vstupní údaj zadaný uživatelem, dále ho konvertuje na celé číslo a následně ho uloží do proměnné kod. Za složené závorky příkazu try umístíme handler catch, tzn., jaké nápravné opatření (jaké příkazy) se mají v případě chyby provést. Handler ošetřuje výjimku typu FormatException - hodnota není ve vhodném formátu, který má být převeden z řetězce pomocí konverze, jako je např. příkaz Parse. Při výjimce je také vygenerován objekt exFormat, který v sobě informace o dané chybě. Tento objekt následně využijeme pro výpis chyby exFormat.ToString(), která nastala. Za příkaz výpisu umístíme příkaz ReadKey, abychom si mohli informace o chybě přečíst. Příkazem return ukončíme metodu, protože nastala chyba a nelze určit aritmetickou operaci.
// ... //Potenciálně nebezpečný kód try { //Načtení kódu operace kod = int.Parse(Console.ReadLine()); } //Handler chyby při konverzi s parametrem handleru catch (FormatException exFormat) { //Výpis parametru handleru Console.WriteLine("Chyba při konverzi: {0}", exFormat.ToString()); //Čekání na stisk klávesy -> Ukončení programu Console.ReadKey(); return; } // ...
Kód 2: Výřez kódu ošetření výjimky chyby na vstupu u programu KalkulackaVicenasobneVetveni
Další možností, jak zjistit zda je možné číslice konvertovat do podoby celého čísla je využít příkazu int.TryParse. Tento test konverze je obvykle proveden za pomoci příkazu if, do jehož podmínky umístíme booleanovský výstup příkazu int.TryParse. Tento příkaz navrací informaci ve formě booleanovské hodnoty (true nebo false), zda je možné číslice načtené v textovém řetězci konvertovat na celé číslo. Pokud ano, výstupem tohoto příkazu je hodnota true a zároveň je celé číslo uloženo do výstupního parametru. Pokud je výstupem metody false, nelze číslice konvertovat.
V následujícím výstupu programu můžeme vidět, jak se program zachová, pokud uživatel zadal špatný vstupní údaj týkající se číselného kódu aritmetické operace. Nastane výjimka a provede se výpis o chybě, která nastala - viz Výstup programu - Kód KalkulackaVyjimka_Main.
Zadejte kód operace (1 ... sčítání, 2 ... odečítání, 3 ... násobení, 4 ... dělení): bflm
Chyba při konverzi: System.FormatException: Vstupní řetězec nemá správný formát.
v System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
v System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo info)
v System.Int32.Parse(String s)
v KalkulackaVyjimka.Program.Main(String[] args) v C:\Users\fox\OneDrive\TI\TI_Nove\Prednasky\Podklady\Programy\Kalkulacka_nove\KalkulackaVyjimka\KalkulackaVyjimka\KalkulackaVyjimka\Program.cs:řádek 25
Výstup programu - Kód KalkulackaVyjimka_Main