A propos des
exceptions dans DELPHI
Une
exception est déclenchée quand une erreur ou un autre événement interrompt le
déroulement normal d'un programme. L'exception transfère le contrôle à un
gestionnaire d'exception, ce qui vous permet de séparer la logique normale
d'exécution du programme de la gestion des erreurs. Comme les exceptions sont
des objets, elles peuvent être regroupées en hiérarchies en utilisant
l'héritage et de nouvelles exceptions peuvent être ajoutées sans affecter le
code existant. Une exception peut véhiculer des informations, par exemple un
message d'erreur, depuis le point où elle est déclenchée jusqu'au point où elle
est gérée.
Quand une
application utilise l'unité SysUtils, la majorité des erreurs d'exécution sont automatiquement
converties en exceptions. Nombre d'erreurs qui, autrement, provoqueraient
l'arrêt d'une application (mémoire insuffisante, division par zéro, erreurs de
protection générales) peuvent ainsi être interceptées et gérées.
Quand utiliser des exceptions
Les
exceptions offrent un moyen élégant d'intercepter les erreurs d'exécution sans
arrêter le programme et sans utiliser d'encombrantes instructions conditionnelles.
Les exigences imposées par la sémantique de la gestion des exceptions se
traduisent par une pénalisation au niveau de la taille du code ou des données
et au niveau des performances à l'exécution. Il est possible de déclencher des
exceptions pour presque toutes les raisons et de protéger pratiquement
n'importe quel bloc de code en l'intégrant dans une instruction try...except ou
try...finally, mais, en pratique, il vaut mieux réserver ces outils à des
situations particulières.
La gestion
des exceptions convient aux erreurs qui ont peu de chances de se produire, mais
dont les conséquences sont quasiment catastrophiques (le crash d'une
application, par exemple) ; aux conditions d'erreurs difficiles à tester
dans des instructions if...then ; et quand vous avez besoin de répondre
aux exceptions déclenchées par le système d'exploitation ou par des routines
dont le code source n'est pas sous votre contrôle. Les exceptions sont
couramment utilisées pour les erreurs matérielles, de mémoire, d'entrée/sortie
et du système d'exploitation.
Les
instructions conditionnelles sont souvent le meilleur moyen de tester les
erreurs. Par exemple, supposons que vous vouliez vous assurer de l'existence
d'un fichier avant d'essayer de l'ouvrir. Vous pourriez le faire comme ceci :
try
AssignFile(F, FileName);
Reset(F); // déclenche une
exception EInOutError si le fichier est introuvable
except
on Exception
do ...
end;
Mais, vous
pourriez aussi éviter la lourdeur de gestion d'exception en utilisant :
if FileExists(FileName) then // renvoie False si le fichier est
introuvable ; ne déclenche aucune exception
begin
AssignFile(F, FileName);
Reset(F);
end;
Les assertions
fournissent un autre moyen de tester une condition booléenne à n'importe quel
endroit du code. Quand une instruction Assert échoue, le programme s'arrête
avec une erreur d'exécution ou (s'il utilise l'unité SysUtils) déclenche une
exception EAssertionFailed. Les assertions devraient n'être utilisées que pour
tester les conditions que vous ne souhaitez pas voir se produire.
Déclaration des types exception
Les types
exception sont déclarés comme les autres classes. En fait, il est possible
d'utiliser comme exception une instance de toute classe. Il est néanmoins
préférable de dériver les exceptions de la classe Exception définie dans SysUtils.
Vous pouvez
grouper les exceptions en familles en utilisant l'héritage. Par exemple, les
déclarations suivantes de SysUtils définissent une famille de types exception
pour les erreurs mathématiques :
type
EMathError =
class(Exception);
EInvalidOp =
class(EMathError);
EZeroDivide =
class(EMathError);
EOverflow =
class(EMathError);
EUnderflow =
class(EMathError);
Etant donné
ces déclarations, vous pouvez définir un seul gestionnaire d'exceptions
EMathError qui gère également EInvalidOp, EZeroDivide, EOverflow et
EUnderflow.
Les classes
d'exceptions définissent parfois des champs, des méthodes ou des propriétés qui
contiennent des informations supplémentaires sur l'erreur. Par exemple,
type EInOutError = class(Exception)
ErrorCode: Integer;
end;
Déclenchement et gestion des
exceptions
Pour
déclencher un objet exception, utilisez une instance de la classe d'exception
avec une instruction raise. Par
exemple,
raise EMathError.Create;
En général,
une instruction raise a la
forme : Raise objet at adresse
Où
"objet" et "at adresse" sont tous deux facultatifs.
Lorsqu'une adresse est spécifiée, cela peut être n'importe quelle expression
dont le résultat est un type pointeur, mais c'est habituellement un pointeur
sur une procédure ou une fonction. Par exemple :
raise Exception.Create('Paramètre manquant') at
@MyFunction;
Utilisez
cette option pour déclencher l'exception depuis un emplacement de la pile
antérieur à celui où l'erreur s'est effectivement produite.
Quand une
exception est déclenchée (c'est-à-dire qu'elle est référencée dans une
instruction raise), elle est régie par
la logique particulière de gestion des exceptions. Une instruction raise ne renvoie jamais le contrôle d'une
manière normale. Elle transfère à la place le contrôle au gestionnaire
d'exception le plus proche capable de gérer les exceptions de la classe donnée.
Le gestionnaire le plus proche correspond au dernier bloc try...except dans
lequel le flux d'exécution est entré sans en être encore sorti.
Par exemple,
la fonction suivante convertit une chaîne en entier et déclenche une exception
ERangeError si la valeur résultante est hors de l'intervalle spécifié :
function StrToIntRange(const S: string; Min, Max:
Longint): Longint;
begin
Result :=
StrToInt(S); // StrToInt est déclarée
dans SysUtils
if (Result
< Min) or (Result > Max) then
raise
ERangeError.CreateFmt('%d n''est pas dans l''intervalle spécifié %d..%d',
[Result, Min, Max]);
end;
Remarquez la
méthode CreateFmt appelée dans l'instruction raise.
Exception et ses descendants ont des constructeurs spéciaux qui proposent
d'autres moyens de créer des messages d'exception et des identificateurs de
contexte.
Une
exception déclenchée est automatiquement détruite une fois qu'elle a été gérée.
N'essayez jamais de détruire manuellement une exception déclenchée.
Remarque: Le déclenchement d'une exception
dans la section initialisation d'une unité peut ne pas donner le résultat
attendu. La gestion normale des exceptions provient de l'unité SysUtils qui
doit d'abord être initialisée pour que cette gestion soit utilisable. Si une
exception se produit lors de l'initialisation, toutes les unités initialisées
(dont SysUtils) sont finalisées et l'exception est redéclenchée. Alors,
l'exception est interceptée et gérée, généralement par l'interruption du
programme. De la même façon, le déclenchement d'une exception dans la section
finalisation d'une unité risque de ne pas produire le résultat escompté si SysUtils
a déjà été finalisé lorsque l'exception a été déclenchée.
Instructions
Try...except
Les
exceptions sont gérées dans des instructions try...except. Par exemple,
try
X := Y/Z;
except
on
EZeroDivide do HandleZeroDivide;
end;
Cette
instruction tente de diviser Y par Z mais appelle la routine HandleZeroDivide
si une exception EZeroDivide est déclenchée.
L'instruction
try...except a la syntaxe suivante :
try instructions
except exceptionBlock
end
où
instructions est une suite d'instructions (délimitée par des points-virgule) et
exceptionBlock est soit
- une autre suite d'instructions,
soit
- une suite de gestionnaires
d'exceptions, éventuellement suivie par
des
instructions else
Un
gestionnaire d'exception a la forme : On identificateur:
type do instruction
où identificateur:
est facultatif (si identificateur est précisé, ce doit être un identificateur
valide), type est le type utilisé pour représenter les exceptions et instruction
est une instruction quelconque.
Une
instruction try...except exécute les instructions dans la liste initiale
instructions. Si aucune exception n'est déclenchée, le bloc exception (exceptionBlock)
n'est pas pris en compte et le contrôle passe à l'instruction suivante du
programme.
Si une
exception est déclenchée lors de l'exécution de la liste instructions initiale,
que ce soit par une instruction raise
dans la liste instructions ou par une procédure ou une fonction appelée dans la
liste instructions, il va y avoir une tentative de "gestion" de
l'exception :
- Si un des gestionnaires du bloc
exception ne correspond à l'exception, le contrôle passe au premier
d'entre eux. Un gestionnaire d'exceptions "correspond" à une
exception si le type du gestionnaire est la classe de l'exception ou un
ancêtre de cette classe.
- Si aucun gestionnaire
correspondant n'est trouvé, le contrôle passe à l'instruction de la clause
else si elle est définie.
- Si le bloc d'exception est simplement
une suite d'instructions sans gestionnaire d'exception, le contrôle passe
à la première instruction de la liste.
Si aucune de
ces conditions n'est respectée, la recherche continue dans le bloc exception de
l'avant-dernière instruction try...except dans laquelle le flux du programme
est entré et n'est pas encore sorti. S'il n'y a là aucun gestionnaire
approprié, aucune clause else, ni
aucune liste d'instructions, la recherche se propage à l'instruction try...except
précédente, et ainsi de suite. Si l'instruction try...except la plus externe
est atteinte et que l'exception n'est toujours pas gérée, le programme
s'interrompt.
Quand
l'exception est gérée, le pointeur de la pile est ramené en arrière jusqu'à la
procédure ou la fonction contenant l'instruction try...except où la gestion a
lieu et le contrôle d'exécution passe au gestionnaire d'exception exécuté, à la
clause else ou à la liste
d'instructions. Ce processus efface tous les appels de procédure ou de fonction
effectués à partir de l'entrée dans l'instruction try...except où l'exception
est gérée. L'objet exception est alors automatiquement détruit par un appel de
son destructeur Destroy et le contrôle revient à l'instruction suivant
l'instruction try...except. Si un appel d'une procédure standard Exit, Break ou
Continue force la sortie du gestionnaire d'exception, l'objet exception est
quand même détruit automatiquement.
Dans
l'exemple suivant, le premier gestionnaire d'exception gère les exceptions
division-par-zéro, le second gère les exceptions de débordement et le dernier
gère toutes les autres exceptions mathématiques. EMathError apparaît en dernier
dans le bloc exception car c'est l'ancêtre des deux autres classes
d'exception ; s'il apparaît en premier, les deux autres gestionnaires ne
sont jamais utilisés.
try
...
except
on EZeroDivide
do HandleZeroDivide;
on EOverflow
do HandleOverflow;
on EMathError
do HandleMathError;
end;
Un
gestionnaire d'exception peut spécifier un identificateur avant le nom de la
classe exception. Cela déclare l'identificateur représentant l'objet exception
pendant l'exécution de l'instruction suivant on...do. La portée de
l'identificateur est limitée à celle de l'instruction. Par exemple,
try
...
except
on E:
Exception do ErrorDialog(E.Message, E.HelpContext);
end;
Si le bloc
exception spécifie une clause else, la
clause else gère toutes les exceptions
qui ne sont pas gérées par les gestionnaires du bloc. Par exemple,
try
...
except
on EZeroDivide
do HandleZeroDivide;
on EOverflow
do HandleOverflow;
on EMathError
do HandleMathError;
else
HandleAllOthers;
end;
Ici, la
clause else gère toutes les exceptions
qui ne sont pas des erreurs mathématiques (EMathError).
Si le bloc
exception ne contient pas de gestionnaires d'exceptions mais une liste
d'instructions, cette liste gère toutes les exceptions. Par exemple,
try
...
except
HandleException;
end;
Ici la
routine HandleException gère toutes les exceptions se produisant lors de
l'exécution des instructions comprises entre try
et except.
Redéclenchement
d'exceptions
Quand le mot
réservé raise apparaît dans un bloc
exception sans être suivi d'une référence d'objet, il déclenche l'exception qui
était gérée par le bloc. Cela permet à un gestionnaire d'exception de répondre
à une erreur d'une manière partielle, puis de redéclencher l'exception. Cela
est pratique quand une procédure ou une fonction doit "faire le
ménage" après le déclenchement d'une exception sans pouvoir gérer
complètement l'exception.
Par exemple,
la fonction GetFileList alloue un objet TStringList et le remplit avec les noms
de fichiers correspondant au chemin de recherche spécifié :
function GetFileList(const Path: string): TStringList;
var
I: Integer;
SearchRec:
TSearchRec;
begin
Result :=
TStringList.Create;
try
I :=
FindFirst(Path, 0, SearchRec);
while I = 0
do
begin
Result.Add(SearchRec.Name);
I :=
FindNext(SearchRec);
end;
except
Result.Free;
raise;
end;
end;
GetFileList
crée un objet TStringList puis utilise les fonctions FindFirst et FindNext
(définies dans SysUtils) pour l'initialiser. Si l'initialisation échoue (car le
chemin d'initialisation est incorrect ou parce qu'il n'y a pas assez de mémoire
pour remplir la liste de chaînes), c'est GetFileList qui doit libérer la
nouvelle liste de chaînes car l'appelant ne connaît même pas son existence.
C'est pour cela que l'initialisation de la liste de chaînes se fait dans une
instruction try...except. Si une exception a lieu, le bloc exception de
l'instruction libère la liste de chaînes puis redéclenche l'exception.
Exceptions
imbriquées
Le code exécuté
dans un gestionnaire d'exception peut lui aussi déclencher et gérer des
exceptions. Tant que ces exceptions sont également gérées dans le gestionnaire
d'exception, elles n'affectent pas l'exception initiale. Par contre, si une
exception déclenchée dans un gestionnaire d'exception commence à se propager
au-delà du gestionnaire, l'exception d'origine est perdue. Ce phénomène est
illustré par la fonction Tan suivante.
type
ETrigError =
class(EMathError);
function
Tan(X: Extended): Extended;
begin
try
Result
:= Sin(X) / Cos(X);
except
on
EMathError do
raise
ETrigError.Create('Argument incorrect pour Tan');
end;
end;
Si une
exception EMathError se produit lors de l'exécution de Tan, le gestionnaire
d'exception déclenche une exception ETrigError. Comme Tan ne dispose pas de
gestionnaire pour ETrigError, l'exception se propage au-delà du gestionnaire
d'exception initial, ce qui provoque la destruction de l'objet exception
EMathError. Ainsi, pour l'appelant, tout se passe comme si la fonction Tan
avait déclenché une exception ETrigError.
Instructions
try...finally
Dans
certains cas, il est indispensable que certaines parties d'une opération
s'effectuent, que l'opération soit ou non interrompue par une exception. Si,
par exemple, une routine prend le contrôle d'une ressource, il est souvent
important que cette ressource soit libérée quelle que soit la manière dont la
routine s'achève. Vous pouvez, dans ce genre de situations, utiliser une
instruction try...finally.
L'exemple
suivant illustre comment du code qui ouvre et traite un fichier peut garantir
que le fichier est fermé, même s'il y a une erreur à l'exécution.
Reset(F);
try
... //
traiter le fichier F
finally
CloseFile(F);
end;
Une
instruction try...finally a la syntaxe suivante :
Try listeInstructions1
finally listeInstructions2
end
où chaque listeInstructions
est une suite d'instructions délimitées par des points-virgule. L'instruction try...finally
exécute les instructions de listeInstructions1 (la clause try). Si listeInstructions1 se termine
sans déclencher d'exception, listeInstructions2 (la clause finally) est exécutée. Si une exception est
déclenchée lors de l'exécution de listeInstructions1, le contrôle est
transféré à listeInstructions2 ; quand listeInstructions2 a
fini de s'exécuter, l'exception est redéclenchée. Si un appel d'une procédure Exit,
Break ou Continue force la sortie de listeInstructions1, listeInstructions2
est exécutée automatiquement. Ainsi, la clause finally
est toujours exécutée quelle que soit la manière dont se termine l'exécution de
la clause try.
Si une
exception est déclenchée sans être gérée par la clause finally, cette exception se propage hors de
l'instruction try...finally et toute exception déjà déclenchée dans la clause try est perdue. La clause finally doit donc gérer toutes les exceptions
déclenchées localement afin de ne pas perturber la propagation des autres
exceptions.
Classes et routines standard des
exceptions
Les unités SysUtils
et System déclarent plusieurs routines standard de gestion d'exceptions, dont
ExceptObject, ExceptAddr et ShowException. SysUtils, System et d'autres unités contiennent
également de nombreuses classes d'exceptions qui dérivent toutes (sauf OutlineError)
de Exception.
La classe
Exception contient les propriétés Message et HelpContext qui peuvent être
utilisées pour transmettre une description de l'erreur et un identificateur de
contexte pour une aide contextuelle. Elle définit également divers
constructeurs qui permettent de spécifier la description et l'identificateur de
contexte de différentes manières.
Aucun commentaire:
Enregistrer un commentaire