Tvorba spolehlivých aplikací

Delphi poskytuje mechanismus, který dokáže značnou měrou přispět k větší robustnosti aplikací, což znamená, že aplikace reaguje na chyby konzistentním způsobem, dokáže podle možností zajistit zotavení ze vzniklé chyby a případně uzavřít rozpracovanou činnost bez ztráty dat nebo systémových prostředků Windows.

Vznik chyby je v Delphi je obsloužen vytvořením výjimky. Výjimka je objekt, který obsahuje informaci o tom, jaká chyba se stala a kde vznikla.

Chráněné programové bloky

Programový kód robustní aplikace musí rozpoznat vznik výjimky a musí na ní reagovat. Pokud nespecifikujeme reakci, aplikace bude ukončena zprávou identifikující chybu. Úkolem programátora je tedy identifikovat místa, kde může chyba vzniknout a definovat odpovídající reakci, především v těch částech programu, kde může chyba způsobit ztrátu dat nebo systémových prostředků.

Reakce na výjimku je vždy vázána na programový blok. Potřebujeme-li tedy pro posloupnost programových příkazů určitou reakci na chybu, uzavřeme tyto příkazy do bloku a definujeme reakci na chybu pro tento blok.

Bloky s definovanou reakcí na výjimky se nazývají chráněné bloky, protože jsou částečně chráněny proti chybám, které jinak mohou způsobit havárii aplikace nebo poškodit data. Chráněný blok začíná klíčovým slovem try a končí klíčovým slovem end.

Reakce na výjimky

Dojde-li k chybě, aplikace vytvoří výjimku, což znamená, že vytvoří objekt pro tuto výjimku. Po vytvoření výjimky může aplikace zpracovat ukončující kód, obsloužit chybu nebo obojí.

Zpracování ukončujícího kódu

Nejjednodušší reakcí na výjimku je provedení ukončujícího kódu. Tato reakce nenapraví chybu, ale alespoň zajistí, aby aplikace nenechala své prostředí v nestabilním stavu a aby běh aplikace pokračoval programovým kódem umístěným za chráněným blokem.

Ukončující kód používáme většinou k tomu, aby aplikace i přes výskyt chyby uvolnila přidělené systémové prostředky. Bližší vysvětlení je v následující části "Ochrana přidělených zdrojů".

Obsluha výjimky

Obsluhou výjimky rozumíme konkrétní reakci na konkrétní typ výjimky. Obsluha odstraní chybu a zruší vytvořený objekt výjimky, díky čemuž lze pokračovat ve zpracovávání aplikace.

Většinou definujeme obsluhu výjimek tak, abychom umožnili zotavení aplikace ze vzniklé chyby a její pokračování. Výjimky, které lze takto obsloužit, jsou pokusy otevřít neexistující soubor, zápis na plný disk nebo výpočty, které vybočily ze stanovených mezí. Některé z nich (např. nenalezení souboru) lze odstranit snadno, zatímco odstranění jiných (např. vyčerpání volné paměti) může být pro aplikaci nebo pro uživatele podstatně komplikovanější. Další vysvětlení jsou v částech "Zpracování výjimek od knihovních funkcí", "Zpracování výjimek od ovládacích prvků" a "Definice vlastních výjimek".

Výjimky a pořadí zpracování příkazů

Hlavní výhodou použití výjimek je skutečnost, že výjimky nekolidují s bezchybným průběhem aplikace. Přesun kontroly a zpracování chyb mimo hlavní linii algoritmu značně zjednodušuje vytvářený programový kód.

Deklarací chráněného bloku definujeme konkrétní reakci na výjimky, které mohou vzniknout v průběhu zpracování tohoto bloku. Vznikne-li při zpracování bloku výjimka, přeruší se provádění chráněného bloku, zpracuje se definovaná reakce a potom dojde k opuštění bloku.

Uveďme příklad programu s chráněným blokem. Vznikne-li při zpracování bloku výjimka, začne se zpracovávat část pro zpracování výjimky a zazní zvukový signál. Provádění programu pokračuje za koncem bloku.

try                                          {začátek chráněného bloku}
   a:=0;                                     {vznikne-li výjimka v některém příkazu}
   b:=100/a;
   writeln('Tohle nikdo neuvidí');
except                                       {... skočí se sem}
on Exception do MessageBeep(0);              {při každé chybě se ozve zvukový signál}
                                              {a pak se pokračuje za koncem chráněného}
                                              {bloku                                  }

Vnořování reakcí na výjimky

Programový kód za klíčovým slovem except definuje reakci na výjimky vzniklé uvnitř bloku. Protože Pascal umožňuje vnořování bloků do jiných bloků, lze přizpůsobit reakci na výjimky dokonce i uvnitř bloků, které již reakci na výjimku obsahují.

V nejjednodušším případě lze například chránit přidělení systémových prostředků a uvnitř chráněného bloku definovat blok, který přiděluje a chrání další systémové prostředky. Uveďme zde základní schéma této definice:

{přidělení prvního zdroje }
try
   {přidělení druhého zdroje }
   try
   {kód, který používá oba zdroje }
   finally
   {uvolní druhý zdroj }
   end;
finally
{uvolní první zdroj }
end;

Vnořené bloky lze použít i k lokálnímu zpracování konkrétních výjimek, které předefinovává zpracování definované ve vnějším bloku. Základní schéma této definice vypadá následovně:

try
   {chráněný  kód }
   try
   {speciálně chráněný kód }
   except
   {lokální zpracování výjimky }
   end;
except
{globální zpracování výjimky }
end;

Lze také kombinovat bloky pro různé druhy reakcí na výjimky, vnořovat ochranu systémových prostředků do bloků pro konkrétní výjimky a naopak.

Ochrana přidělených systémových prostředků

Klíčem k vytváření robustních aplikací je zajištění toho, že přidělené systémové prostředky aplikace vždy také uvolní, a to i v případě chyby. Pokud si například aplikace nechá přidělit paměť, musí zajistit, aby ji také vrátila zpátky. Pokud otevře soubor, je třeba zajistit, aby ho zase za sebou zavřela.

Je třeba si uvědomit, že výjimky nevznikají v kódu programu. Nastavit výjimku může například knihovní procedura nebo nějaký ovládací prvek. Programový kód musí zajistit, aby byly systémové prostředky uvolněny i v tomto případě.

Které systémové prostředky potřebují ochranu?

Běžně se zajišťuje ochrana systémových prostředků tak, že aplikace obsahuje programový kód pro jejich přidělení i uvolnění. Provedení kódu, který systémový prostředek uvolňuje, je třeba zajistit i při vzniku výjimky.

Mezi běžné systémové prostředky, které je třeba vždy uvolnit, patří soubory, paměť, systémové prostředky Windows a objekty.

Následující zpracování události například přiděluje paměť a potom generuje chybu, takže kód pro uvolnění paměti se neprovede:

procedure TForm1.Button1Click(Sender:TComponent);
var APointer: pointer;
    AnInteger, ADividend: integer;
begin
   ADividend:=0;
   GetMem(APointer, 1024);                  {přidělí 1K paměti}
   AnInteger:=10 div ADividend;             {dělí nulou}
   FreeMem(APointer,1024);                  {nikdy se neprovede}
end;

Ačkoliv většina chyb není takhle evidentních, příklad ilustruje jeden podstatný moment: Když nastane chyba při dělení nulou, dojde k ukončení bloku, takže se příkazu FreeMem nikdy nepodaří paměť uvolnit.

Abychom zajistili, že procedura FreeMem uvolní paměť přidělenou procedurou GetMem, je třeba umístit programový kód do chráněného bloku.

Vytváření bloků pro ochranu systémových prostředků

Abychom zajistili uvolnění systémových prostředků i při vzniku výjimky, zařadíme programový kód používající prostředek do chráněného bloku a kód pro uvolnění prostředku do speciální části bloku. Zde je nástin typického chráněného přidělení systémového prostředku:

{přidělení prostředku}
try
   {příkazy používající prostředek}
finally
   {uvolnění prostředku}
end;

Klíčem k použití bloku try...finally je skutečnost, že aplikace vždy provede příkazy v části bloku začínající klíčovým slovem finally, i když při provádění chráněného bloku vznikne chyba. Kdykoliv programový kód v části try (nebo kterákoliv procedura volaná z této části) vytvoří výjimku, provádění příkazů pokračuje částí finally, kterou nazýváme ukončující kód. Pokud k výjimce nedojde, ukončující kód je normálně proveden za všemi příkazy v části try. Zde je příklad zpracování události, která přidělí paměť a generuje chybu, přesto však přidělenou paměť uvolní:

procedure TForm1.Button1Click(Sender:TComponent);
var APointer:pointer;
    AnInteger, ADividend: integer;
begin
ADividend:=0;
GetMem(APointer, 1024);                        {přidělí 1K paměti}
try AnInteger := 10 div ADividend;             {dělí nulou}
finally FreeMem(Apointer, 1024);               {provede se po chybě}
end;
end;

Příkazy v ukončujícím kódu nezávisí na typu výjimky ani na tom, zda výjimka nastala či nikoliv.

Poznámka: Blok pro ochranu systémových prostředků neobsluhuje výjimku. Ukončující kód ani nemá informaci o tom, zda vůbec výjimka nastala, a nemůže proto rozhodnout, zda a jak má výjimku obsloužit. Dojde-li k výjimce uvnitř bloku pro ochranu systémového prostředku, provede se ukončující kód a pak ukončí blok, přičemž výjimka zůstane nahozena. Výjimku může případně obsloužit nadřazený blok.

Zpracování výjimek od knihovních funkcí

Programový kód, který používá systémovou knihovnu (RTL - Run Time Library) obsahující např. matematické funkce nebo funkce pro práci se soubory, dostává informaci o chybách v těchto procedurách ve formě výjimek. Implicitně generují výjimky od knihovních funkcí zprávu, kterou pak aplikace zobrazí uživateli. Lze definovat i vlastní, jinou obsluhu těchto výjimek.

Existují rovněž němé výjimky, které implicitně žádnou zprávu nezobrazují. Princip němých výjimek je obsloužen samostatně.

Co jsou výjimky od knihovních funkcí

Výjimky od knihovních funkcí jsou definovány v modulu SysUtils a jsou všechny potomky obecného objektu typu Exception (výjimka). Objekt typu Exception obsahuje řetězec pro zprávu, která je implicitně výjimkami od knihovních funkcí zobrazována.

Knihovní funkce vytváří těchto sedm typů výjimek:

Vstup/výstupní výjimky

Vstup/výstupní (V/V) výjimky se mohou vyskytnout, pokud se knihovní funkce snaží otevřít soubor nebo V/V zařízení. Většina V/V výjimek je spojena s chybovými kódy vracenými operačními systémy DOS nebo Windows při přístupu k souboru.

Modul SysUtils definuje obecnou V/V výjimku typu EInOutError, která obsahuje atribut se jménem ErrorPre indikující výskyt chyby. Tento atribut je dostupný v instanci objektu výjimky postupem uvedeným v části "Práce s instancemi výjimek".

Výjimky při přidělování paměti

Výjimky při přidělování paměti se mohou objevit, pokud se snažíme přidělit dynamickou paměť nebo s ní pracovat. Modul SysUtils definuje dvě výjimky při přidělování paměti zvané EOutOfMemory a EInvalidPointer, které jsou odvozené bezprostředně z výjimky Exception.

Výjimka Význam
EOutOfMemory V hromadě není volný prostor pro přidělení požadované paměti.
EInvalidPointerAplikace se pokouší vrátit přidělenou paměť, ukazatel však směřuje mimo hromadu. Obvykle to znamená, že vrácení paměti již bylo provedeno.

Výjimky celočíselné aritmetiky

Výjimky celočíselné aritmetiky se mohou vyskytnout, pokud zpracováváme celočíselné výrazy. Modul SysUtils definuje obecnou výjimku pro celočíselnou aritmetiku s názvem EIntError. Knihovní funkce nevytváří přímo instanci výjimky EIntError, ale vytváří instance konkrétních typů výjimek z třídy EIntError odvozených.

Následující tabulka uvádí výjimky pro celočíselnou aritmetiku odvozené od EIntError.

Výjimka Význam
EDivByZero Pokus o dělení nulou
ERangeError Hodnota nebo výraz mimo povolený rozsah
EIntOverflow Přetečení při celočíselné operaci

Výjimky aritmetiky v pohyblivé řádové čárce

Výjimky aritmetiky v pohyblivé řádové čárce se mohou vyskytnou při provádění operací v pohyblivé řádové čárce. Modul SysUtils definuje obecnou výjimku pro pohyblivou řádovou čárku s názvem EMathError. Knihovní funkce nevytváří přímo instanci výjimky EMathError, ale vytváří instance konkrétních typů výjimek z třídy EMathError odvozených.

Následující tabulka uvádí výjimky pro aritmetiku v pohyblivé řádová čárce odvozené od EMathError.

Výjimka Význam
EInvalidOp Procesoru byla předložena neznámá instrukce
EZeroDividePokus o dělení nulou
EOverflow Přetečení v pohyblivé řádové čárce
EUnderflow Podtečení v pohyblivé řádové čárce

Výjimky při přetypování

Výjimky při přetypování se vyskytnou při pokusu o přetypování objektu pomocí operátoru as. Modul SysUtils definuje výjimku EInvalidCast, která je vytvořena při nepovoleném přetypování.

Výjimky při konverzi dat

Výjimky při konverzi dat se vyskytují při konverzi dat z jednoho tvaru na jiný tvar pomocí funkcí jako je IntToStr, StrToInt, StrToFloat apod. Modul SysUtils definuje výjimku typu EConvertError, kterou knihovní funkce vytvářejí, když nemohou provést konverzi předkládaných dat.

Hardwarové výjimky

Hardwarové výjimky se mohou vyskytnout ve dvou typech situací: když procesor detekuje chybu, se kterou se nedokáže vyrovnat, nebo když aplikace sama generuje přerušení pro ukončení činnosti.

Modul SysUtil definuje obecnou hardwarovou výjimku typu EProcessorException. Knihovní funkce nevytvářejí instance výjimky EProcessorException, ale vytvářejí výjimky z této třídy odvozené.

Hardwarové výjimky odvozené od EProcessorException jsou uvedeny v následující tabulce.

Výjimka Význam
EFault Základní objekt, od něhož jsou odvozeny následující tři výjimky
EGPFault Obecné porušení ochrany paměti, typicky způsobené použitím neinicializovaného ukazatele nebo objektu
EStackFault Chybný přístup do zásobníkového segmentu
EPageFault Chyba při použití souboru výměny
EInvalidOpPreZpracování neznámé instrukce. Typicky vzniká, když se procesor pokouší provádět data nebo neinicializovanou paměť.
EBreakPoint Ladicí přerušení nastavované v prostředí Delphi.
ESingleStep Ladicí přerušení při provedení jednoho kroku programu.

S hardwarovými výjimkami snad s výjimkou porušení ochrany paměti byste se měli potkávat pouze zřídka, neboť představují závažné chyby v prostředí operačního systému. Výjimky pro bod přerušení a jeden krok programu jsou používány uvnitř integrovaného prostředí Delphi.

Vytvoření obsluhy výjimky

Obsluha výjimky je programový kód, který zpracovává reakci na konkrétní výjimku nebo výjimku uvnitř chráněného bloku.

Při definici obsluhy výjimky uzavřeme programový kód, kterým chceme výjimku obsloužit, do části except bloku pro zpracování výjimky. Typický blok pro obsluhu výjimky vypadá následovně:

try
    {chráněné příkazy}
except
    {obsluha výjimky}
end;

Aplikace provádí příkazy uvedené v části except pouze tehdy, když se vyskytne výjimka v průběhu zpracování části try. Provedení příkazů v části {\bf try} zahrnuje i provedení procedur volaných z části try. To znamená, že pokud je z části try volána procedura nebo funkce, která nedefinuje svojí obsluhu výjimky, provádění programu se vrací do bloku pro obsluhu výjimky.

Pokud některý příkaz v části try vytvoří výjimku, provádění programu se přesune na začátek části except a prochází se zde uvedené příkazy pro obsluhu výjimky až po nalezení té obsluhy, která se vztahuje k právě nastalé výjimce.

Jakmile je nalezena odpovídající obsluha výjimky, příkazy v ní uvedené se provedou a objekt výjimky je automaticky zrušen. Výpočet pokračuje za koncem chráněného bloku.

Příkazy pro obsluhu výjimky

Každý z příkazů v části except příslušného bloku try..except obsahuje programový kód pro obsluhu konkrétního třídy výjimky. Tyto příkazy mají následující tvar:

     on < typ výjimky > do < příkaz >;

Můžeme například definovat obsluhu pro dělení nulou, která dosadí implicitní hodnotu:

function SpoctiPrumer(Soucet, Pocet:integer):integer;
begin
try Vysledek:=Soucet div Pocet;
except
   on EDivByZero do Vysledek:=0;
end;
end;

Toto řešení je čistší než testování jmenovatele před každým voláním funkce. Ekvivalentní funkce zapsaná bez použití výjimky vypadá následovně:

function SpoctiPrumer(Soucet, Pocet:integer):integer;
begin
if Pocet <> 0 then Vysledek := Soucet div} Pocet
else Vysledek := 0;
end;

Rozdíl mezi těmito dvěma funkcemi je v podstatě rozdíl mezi programováním s výjimkami a bez použití výjimek. Uvedený příklad je zcela jednoduchý, ale snadno si lze představit komplikovanější výpočty zahrnující stovky kroků, z nichž každý může selhat, pokud je jeden nebo více vstupů chybných.

Při použití výjimek máme možnost uvést "normální" tvar algoritmu a potom obsloužit výjimečné případy, pro který tento tvar neplatí. Bez použití výjimek je třeba vždy opakovaně testovat, zda je možno pokračovat v provádění následujícího kroku.

Práce s instancemi výjimek

Obsluha výjimky obvykle nepotřebuje jinou informaci o výjimce než její typ, takže příkazy za frází on..do jsou vázány pouze na typ výjimky. Někdy je však potřeba i informace obsažená v instanci výjimky.

Pokud je třeba v obsluze výjimky získat i informaci vztaženou k instanci výjimky, použijeme speciální tvar fráze on..do, který umožňuje přístup k instanci výjimky. Tento tvar vyžaduje použití pomocné proměnné, do níž se uloží odkaz na instanci.

Vytvořme například nový projekt s jediným formulářem obsahujícím rolovací lištu a tlačítko. Obsluha tlačítka bude obsahovat jediný příkaz:

     ScrollBar1.Max := ScrollBar1.Min - 1;

Tento příkaz vede k chybě, protože maximální pozice rolovací lišty musí být vždy větší než minimální pozice. Implicitní obsluha výjimky zobrazí dialog obsahující zprávu o výskytu výjimky. Tuto obsluhu lze předefinovat a vytvořit vlastní dialog používající zprávu o výjimce:

try ScrollBar1 := ScrollBar1 - 1;
except
   on E:EInvalidOperation do
      MessageDlg('Ignoruji výjimku: ' + E.Message, mtInformation, [mbOK], 0);
end;

Typ pomocné proměnné (v tomto příkladu E) je uveden za dvojtečkou (v tomto příkladu EInvalidOperation). K přetypování výjimky na požadovaný typ lze případně použít operátor as. Poznámka: Instanci výjimky nikdy nerušíme. Zrušení instance je zajištěno automatickým zpracováním výjimky. Pokud zrušíme instanci sami, aplikace se jí pokusí zrušit znovu a způsobí tak havárii aplikace.

Platnost obsluhy výjimky

Není třeba uvádět v každém bloku obsluhu pro každý typ výjimky. Obsluhu výjimky vytváříme pouze pro ty výjimky, které potřebujeme v daném bloku obsloužit specifickým způsobem.

Pokud není pro danou výjimku uvedena v bloku specifická obsluha, provádění opouští blok a vrací se do nadřazeného bloku (nebo do části programu, odkud byl blok vyvolán), přičemž výjimka zůstává nahozena. Tento postup se opakuje až po návrat do bloku, v němž je výjimka obsloužena, případně až po návrat na úroveň aplikace (tj. prakticky k ukončení reakce programu na poslední akci uživatele).

Definice implicitní obsluhy výjimky

Lze definovat i implicitní obsluhu výjimek, která se vztahuje na všechny výjimky, pro něž nebyla uvedena specifická obsluha, a to tak, že přidáme do fráze except část else:

try {příkazy}
except
   on E_neco do {obsluha specifické výjimky};
   else {implicitní obsluha výjimek};

Vytvoření implicitní obsluhy výjimky pro daný blok zajistí, že blok nějakým způsobem zpracuje každou výjimku a nedojde tak k přechodu do nadřazeného bloku.

POZOR! Tuto vše zahrnující implicitní obsluhu výjimky bychom měli použít jen velmi opatrně. Fráze else zajišťuj obsluhu všech výjimek, včetně těch, o nichž nevíme nic. Obecně by měly být zpracovány pouze výjimky, které umíme obsloužit. V ostatních případech je lepší provést ukončující kód a ponechat zpracování výjimky té části programu, která má více informací o výjimce a o její obsluze.

Obsluha tříd výjimek

Protože výjimky tvoří hierarchii, lze definovat obsluhu pro celou část této hierarchie zápisem obsluhy k té třídě, z níž je daná část hierarchie odvozena.

Následující příklad naznačuje řešení, které zpracovává všechny celočíselné matematické výjimky:

try                       {příkazy provádějící celočíselné matematické operace}
except on EIntError do    {specifická obsluha pro chyby v celočíselné aritmetice}
end;

I tak lze zpracovat konkrétní výjimku specifickým způsobem, obsluhu konkrétní je však třeba uvést před obsluhu obecnou, protože se obsluhy prohledávají v tom pořadí, v jakém byly uvedeny.

Následující blok například zpracovává specificky chyby rozsahu a obecně všechny chyby v celočíselné aritmetice:

try {příkazy provádějící celočíselné matematické operace}
except
   on ERangeError do {zpracování chyby rozsahu};
   on EIntError do   {zpracování ostatních chyb v celočíselné aritmetice};
end;

Pokud by byla obsluha pro EIntError uvedena před obsluhou pro ERangeError, specifická obsluha pro ERangeError by nebyla provedena nikdy.

Opakované vytvoření výjimky

Někdy potřebujeme při lokální obsluze výjimky zpracování výjimky v nadřazeném bloku pouze doplnit, nikoliv nahradit. Při ukončení lokální obsluhy se instance výjimky zruší a obsluha v nadřazeném bloku se tudíž neprovede. Tomuto zrušení výjimky lze však zabránit a umožnit tak provedení obsluhy z nadřazeného bloku.

Můžeme například chtít při výskytu výjimky zobrazit uživateli nějakou zprávu a potom pokračovat standardní obsluhou výjimky. Deklarujeme tedy lokální obsluhu výjimky, která zobrazí zprávu a potom provede příkaz raise. Toto tzv. znovuvytvoření výjimky ukazuje následující příklad:

try {příkazy}
   try {speciální příkazy}
   except
      on E_neco do begin
         {obsluha pro speciální příkazy}
         raise;                         {znovuvytvoření výjimky}
      end;
   end;
   except}
      on E_neco do ...;                 {obecné zpracování}
end;

Pokud programový kód v části {příkazy} vytvoří výjimku E_neco, je provedena pouze obsluha z vnější fráze except. Pokud však výjimku E_neco vytvoří programový kód v části {speciální příkazy}, je provedena obsluha z vnitřní fráze except následovaná obecnější obsluhou z vnější fráze except.

Opakovaným vytvořením výjimky lze snadno přidat speciální obsluhu výjimky pro speciální případy bez ztráty existující obsluhy nebo nutnosti ji duplikovat.

Zpracování výjimek od ovládacích prvků

Rovněž ovládací prvky Delphi mohou vytvářet výjimky označující výskyt chybových stavů. Většina těchto výjimek je způsobena programovými chybami, které by jinak vedly k ukončení výpočtu. Mechanismus pro obsluhu výjimek od ovládacích prvků se neliší od obsluhy výjimek od knihovních funkcí.

Běžným zdrojem chyb v ovládacích prvcích je chyba rozsahu intervalu u vlastností obsahujících index. Pokud seznam například obsahuje tři položky (0..2) a aplikace se pokouší zpracovat položku s číslem 3, seznam vytváří výjimku "Index mimo povolený rozsah".

Následující příklad obsahuje obsluhu výjimky, která upozorňuje uživatele na chybný index položky seznamu:

procedure TForm1.Button1Click(Sender:TObject);
begin
ListBox1.Items.Add('první položka');
ListBox1.Items.Add('druhá položka');
ListBox1.Items.Add('třetí položka');
try Caption :=  ListBox1.Items[3];  {nastavuje titulek formuláře podle čtvrté
                                     položky seznamu}
except
   on EListError do
      MessageDlg('Seznam obsahuje méně položek než čtyři', mtWarning, [mbOK],0);
end;
end;

Při prvním odeslání tlačítka má seznam jenom tři položky, tudíž přístup ke čtvrté položce (Items[3]) vede k chybě. Druhé odeslání tlačítka přidává k seznamu další řetězce a k výjimce tedy již nedojde.

Němé výjimky

Aplikace v Delphi zpracovává většinu výjimek neobsloužených v programu tak, že zobrazí dialog se zprávou o typu výjimky. Lze definovat i "němé" výjimky, které implicitně žádnou zprávu nezobrazují.

Němé výjimky se uplatní v případě, kdy nechceme výjimku obsloužit, ale chceme zrušit prováděnou operaci. Zrušení operace má blízko k opuštění bloku pomocí procedur Break a Exit, tímto způsobem je však možno opustit i několik vnořených bloků.

Všechny němé výjimky jsou odvozeny od standardní výjimky EAbort. Standardní implicitní obsluha výjimek zobrazuje dialog se zprávou pro všechny výjimky kromě výjimek odvozených od typu EAbort.

Vytvoření němé výjimky je možno provést i zkráceným způsobem, voláním procedury Abort. Procedura Abort automaticky vytváří výjimku typu EAbort, která ukončí prováděnou operaci bez toho, že by zobrazovala chybovou zprávu.

Následující příklad obsahuje jednoduché ukončení operace. Ve formuláři obsahujícím prázdný seznam a tlačítko doplníme do události OnClick tohoto tlačítka následující programový kód:

procedure TForm1.Button1Click(Sender:TObject);
var  i:integer;
begin
for i:=1 to 10 do begin
   ListBox1.Items.Add(IntToStr(i));           {zařazuj do seznamu čísla}
   if i=7 then Abort;                         {po sedmé položce skonči}
end;
end;

Definice vlastních výjimek

Kromě ochrany programového kódu pomocí výjimek od knihovních funkcí a ovládacích prvků lze stejný mechanismus použít i pro obsluhu výjimečných situací ve vlastním programovém kódu. K tomu je třeba deklarovat třídu pro daný typ výjimky a v programu tuto výjimku vytvořit.

Deklarace třídy výjimky

Protože výjimky jsou objekty, definice nové třídy výjimky je stejně jednoduchá jako deklarace jiné nové třídy objektů.

Standardní obsluha výjimek zpracovává výjimky odvozené od třídy Exception. Proto je vhodné odvozovat nové třídy výjimek od této třídy nebo od některé ze standardních odvozených tříd. Pokud je potom v některém bloku vytvořena výjimka nově deklarovaného typu a není pro ní definována specifická obsluha, bude obsloužena standardním způsobem.

Vezměme například následující deklaraci:

type EMojeVyjimka = class(Exception);

Standardní obsluha pro typ Exception je provedena i v případě, že je vytvořena EMojeVyjimka, ale není pro ní definována specifická obsluha. Protože standardní obsluha pro typ Exception zobrazí jméno vytvořené výjimky, lze minimálně ověřit, že k vytvoření této výjimky skutečně došlo.

Vytvoření vlastní výjimky

K indikaci chybového stavu aplikace použijeme vytvoření výjimky, což představuje vytvoření instance výjimky a volání příkazu raise. Tento příkaz je tvořen klíčovým slovem raise následovaným instancí objektu výjimky. Při vlastní obsluze výjimky je instance objektu výjimky zrušena, takže toto rušení nikdy neprovádíme explicitně.

Nastavení adresy výjimky

Při vytvoření výjimky je nastavena proměnná ErrorAddr v modulu System na adresu, na které došlo k vytvoření výjimky. V obsluze výjimky je možno se odvolat na hodnotu této proměnné a informovat tak například uživatele o tom, kde k nahození výjimky došlo.

Při vytváření výjimky lze rovněž specifikovat hodnotu pro uložení do proměnné ErrorAddr. Příkaz raise doplníme o klíčové slovo at následované adresovým výrazem. Při deklaraci tvaru

type EChybneHeslo = class(Exception);

lze například vytvořit výjimku "chybné heslo" uvedením příkazu raise s uvedením instance výjimky EChybneHeslo podobně jako v následujícím příkladu:

if Heslo <> SpravneHeslo then
   raise EChybneHeslo.Create('Zadáno chybné heslo');

Souhrn

Výjimky jsou flexibilním nástrojem pro oznámení a obsloužení chybových stavů aplikace. Knihovna systému Delphi i standardní ovládací prvky s výjimkami pracují a využívají je.

Pomocí obsluhy výjimek v aplikaci lze zajistit, že nedojde ke ztrátě systémových prostředků při nenadálém ukončením aplikace, případně umožní aplikaci nebo uživateli chybový stav opravit a zkusit operaci znovu. Minimálně poskytují výjimky aplikaci potřebné informace a mechanismus nutný k obsloužení chybových stavů.