Guide Pascal et Delphi
Guide Pascal et Delphi
Date de publication : 10/04/2000 , Date de mise à jour : 01/04/2008
XIX. Gestion des exceptions
XIX-A. Introduction
XIX-B. Try..Finally
XIX-C. Try..Except
XIX-C-1. Grammaire
XIX-C-2. Listes non exhaustive de classe d'exception
XIX-D. Exception personnalisée
XIX-E. Raise
XIX-F. Conclusion
XIX. Gestion des exceptions
Le présent tutoriel n'a pas la prétention de tout vous apprendre sur la gestion des
exceptions (comme tous les tutoriaux que je fais mais celui-la plus que les autres).
Mais vous devriez vous sentir à l'aise avec la bête et ne plus rechigner à les utiliser.
Pour ceux qui ont déjà pris l'habitude d'utiliser leurs services, je leur conseille de
le parcourir quand même, il se pourrait qu'ils apprennent quelque chose. Si tel n'était
pas le cas, toutes mes excuses (et mieux encore vous en connaissez plus sur le sujet,
je vous invite à compléter/modifer ce tutorial).
XIX-A. Introduction
Tout d'abord qu'est qu'une exception ? Comme son nom l'indique une exception est un
événement imprévu par le programmeur (ou programme). Vous me direz comment peut-il
gérer quelques chose qu'il n'a pas prévu ? Très simple, le programmeur sachant que
le monde n'est pas parfait aura pris le soin de protéger des blocs d'instructions
sensibles.
Concrètement, lorsque vous développez votre application, vous vous attendez à ce que telle ou telle chose soit disponible (un fichier dll par exemple) ou que l'utilisateur entre tel type de donnée dans telle variable (champ de saisie où il faut entrer des nombres) (par utilisateur, je considère une personne ou le programme lui-même) or il arrive que les choses ne se passent pas tout à fait comme vous les avez souhaitées, fichier absent, caractère vers une variable numérique etc...
Pour protéger votre application et plus précisément un bloc de code, une solution est d'utiliser les exceptions et ainsi éviter à votre application de planter lamentablement. Une autre solution serait de faire des tests mais si pour des cas simples, les tests sont faciles à mettre en place (l'exemple de la division par zéro est assez parlant), il n'en est pas de même pour des tâches plus complexes ou sujettes à différents types d'erreur (comme l'accès à un fichier). D'autres diront que gérer par exception, les cas à problème, allège le code (à vous de voir).
Les exceptions peuvent être de natures différentes suivant l'opération où elles se produisent. Ces différentes exceptions sont appelées type d'exception ou classe d'exception. Par exemple lors d'une tentative de conversion d'un string en float, si le string contient des lettres, on aura une exception de classe conversion (EConverError).
La protection d'un bloc de code se fait par l'utilisation d'un couple de mot clé. Suivant la façon dont l'on veut gérer l'exception, deux couples existent, try..finally et try..except. Les deux couples protégent le code situé entre try et l'autre mot clé, si une exception arrive entre ces deux mots clés , le programme arrête de traiter le bloc protégé et passe tout de suite aux instructions comprises entre le second mot clé du couple et poursuit ensuite les instructions suivant ce second bloc de code. La différence entre le couple try..finally et try..except se situe dans l'exécution du second bloc d'instruction.
Concrètement, lorsque vous développez votre application, vous vous attendez à ce que telle ou telle chose soit disponible (un fichier dll par exemple) ou que l'utilisateur entre tel type de donnée dans telle variable (champ de saisie où il faut entrer des nombres) (par utilisateur, je considère une personne ou le programme lui-même) or il arrive que les choses ne se passent pas tout à fait comme vous les avez souhaitées, fichier absent, caractère vers une variable numérique etc...
Pour protéger votre application et plus précisément un bloc de code, une solution est d'utiliser les exceptions et ainsi éviter à votre application de planter lamentablement. Une autre solution serait de faire des tests mais si pour des cas simples, les tests sont faciles à mettre en place (l'exemple de la division par zéro est assez parlant), il n'en est pas de même pour des tâches plus complexes ou sujettes à différents types d'erreur (comme l'accès à un fichier). D'autres diront que gérer par exception, les cas à problème, allège le code (à vous de voir).
Les exceptions peuvent être de natures différentes suivant l'opération où elles se produisent. Ces différentes exceptions sont appelées type d'exception ou classe d'exception. Par exemple lors d'une tentative de conversion d'un string en float, si le string contient des lettres, on aura une exception de classe conversion (EConverError).
La protection d'un bloc de code se fait par l'utilisation d'un couple de mot clé. Suivant la façon dont l'on veut gérer l'exception, deux couples existent, try..finally et try..except. Les deux couples protégent le code situé entre try et l'autre mot clé, si une exception arrive entre ces deux mots clés , le programme arrête de traiter le bloc protégé et passe tout de suite aux instructions comprises entre le second mot clé du couple et poursuit ensuite les instructions suivant ce second bloc de code. La différence entre le couple try..finally et try..except se situe dans l'exécution du second bloc d'instruction.
-
Avec try..finally, les instructions du second bloc d'instruction sont toujours exécutées.
instructions1
try
//
Bloc
de
code
à
protéger
instructions protégéesfinally
//
Second
bloc
de
code
//
Ce
bloc
sera
exécuté
à
la
fin
des
instructions
protégées
//
ou
dès
qu'une
erreur
survient
dans
le
bloc
de
code
protégé
instructions2end
;//
suite
des
instructions
qui
seront
exécutées
si
elles
ne
provoquent
pas
d'erreur
instructions3 -
Avec try..except, les instructions du second bloc d'instruction ne sont exécutées que s'il y a eut une exception dans le bloc protégé. Le couple try..except propose en plus de traiter, l'exception en fonction de la classe d'exception.
instructions1
try
//
Bloc
de
code
à
protéger
instructions protégéesexcept
//
Second
bloc
de
code
//
Ce
bloc
ne
sera
exécuté
que
si
une
erreur
survient
dans
la
partie
protégée
instructions2end
;//
suite
des
instructions
qui
seront
exécutées
si
elles
ne
provoquent
pas
d'erreur
instructions3
Vous l'aurez remarqué, le bloc dit protégé est celui qui le paraît le moins. En
fait par bloc protégé, je pense que l'on désigne la protection du programme contre
un code sensible. Votre programme ne va plus planter lamentablement lorsqu'il
rencontrera une erreur dans un bloc protégé. Par contre vous continuerez d'avoir
les messages d'erreur en l'exécutant depuis Delphi, cela est destiné à vous aider
à vérifier que vous interceptez bien la bonne classe d'exception.
XIX-B. Try..Finally
La syntaxe de try..finally est :
|
Instruction1 est une partie sensible du code (manipulation d'un fichier, création
d'un objet, etc...) et instruction2 une partie du code qui doit être exécuté
quoiqu'il arrive (libération de ressource, fermeture d'une connexion etc...).
Si une erreur survient dans les instructions du bloc instruction1, l'exécution
passe immédiatement à l'exécution d'instruction2 sinon l'exécution termine les
instructions et passe ensuite au instruction2.
Vous l'aurez compris, son utilisation est fortement recommandée pour libérer une ressource même si le programme rencontre une erreur. Mais attention, l'instruction demandant la ressource doit se trouver à l'extérieur du try..finally. Dans le cas contraire, s'il arrivait que le programme ne puisse pas allouer la ressource, tenter de la libérer peut provoquer une erreur.
Exemple : Création d'un objet
Vous l'aurez compris, son utilisation est fortement recommandée pour libérer une ressource même si le programme rencontre une erreur. Mais attention, l'instruction demandant la ressource doit se trouver à l'extérieur du try..finally. Dans le cas contraire, s'il arrivait que le programme ne puisse pas allouer la ressource, tenter de la libérer peut provoquer une erreur.
Exemple : Création d'un objet
|
Exemple : assignation d'un fichier
|
Notez la position de demande d'allocation de ressource par rapport au
try..finally.
Attention : Tant que vous n'avez pas appelé CloseFile(F), vous ne pouvez pas manipuler le fichier (renommer, détruire, déplacer etc...). Ne l'oubliez pas ! Ceci est valable pour les fichiers mais aussi pour d'autres ressources (base de donnée, périphérique...). |
Précision : La protection d'un bloc de code permet d'éviter la propagation du message d'erreur mais dans certain cas, il peut être nécessaire de relancer sa diffusion. La commande raise peut être utilisée à cet effet voir son chapitre pour plus de précision. |
XIX-C. Try..Except
XIX-C-1. Grammaire
Examinons une application fournissant un champ de saisie n'attendant que des
nombres comme saisie. Deux solutions s'offrent à vous, la première empêcher
l'utilisateur d'entrer autre chose que des caractères numériques (assez compliqué
à mettre en place mais mieux au niveau de l'ergonomie) et la seconde utiliser
les exceptions. Laissons la première de côté et intéressons-nous à la seconde.
Ce que nous devons protéger est le moment où la saisie de l'utilisateur doit
être affectée à une variable de type numérique. Vous pouvez tester en réalisant
une application faisant une telle opération. Lors de l'exécution, un message
d'erreur se produira dès que vous affecterez des lettres à la variables,
plantant le plus souvent votre application. Par contre en protègeant votre
bloc de code, non seulement, vous limitez l'erreur à cette portion de code et
vous pouvez en plus réaliser un traitement spécifique au problème (réinitialiser
des variables, informer l'utilisateur...).
La syntaxe est la suivante :
La syntaxe est la suivante :
|
Instruction1 est comme pour le try..finally la partie sensible
du code tandis qu'instruction2 le code qui sera exécuté si instruction1 provoque
une erreur. Si une erreur survient dans les instructions du bloc instruction1,
l'exécution passe immédiatement à l'exécution d'instruction2 sinon l'exécution
termine les instructions et passe ensuite au instruction3.
Exemple : Gestion des erreurs liées à une conversion de Type.
Exemple : Gestion des erreurs liées à une conversion de Type.
|
Essayez cet exemple, en cas d'erreur de saisie vous aurez droit à un message
d'erreur un peu plus clair que ceux distillés par Windows. Pour le vérifier,
tapez votre chiffre en vous trompant dans le séparateur décimal (le point au
lieu de la virgule et vice versa), Sans la gestion d'erreur vous saurez
seulement que votre saisie n'est pas valide sans comprendre pourquoi car
vous avez bien entré un nombre alors que grâce à la gestion des exceptions,
vous aurez le droit à :
En plus, vous pouvez ajouter des instructions remettant votre programme dans un
état stable (réinitialisation de variable par exemple).
L'exemple ci dessous est une des façons d'écrire la gestion des exceptions par try..except. Dans ce cas précis, nous savions ce qui pouvait provoquer une erreur dans le code protégé (une erreur de conversion) et nous n'avons traité que ce cas.
D'une manière plus générale, on peut considérer que la gestion d'exception peut intercepter des erreurs prévisibles et d'autre plus aléatoires (non prévues) et que l'on peut soit traiter les erreurs prévisibles soit les autres ou les deux (ce qui quand même préférable).
Quand on veut traiter une erreur prévisible, il faut savoir à quelle classe elle appartient, par exemple une erreur de conversion appartient à la classe EConvertError (on peut savoir ceci en consultant dans l'aide, le type d'erreur soulevée par une fonction particulière).
Le try..except pourra se présenter ainsi :
L'exemple ci dessous est une des façons d'écrire la gestion des exceptions par try..except. Dans ce cas précis, nous savions ce qui pouvait provoquer une erreur dans le code protégé (une erreur de conversion) et nous n'avons traité que ce cas.
D'une manière plus générale, on peut considérer que la gestion d'exception peut intercepter des erreurs prévisibles et d'autre plus aléatoires (non prévues) et que l'on peut soit traiter les erreurs prévisibles soit les autres ou les deux (ce qui quand même préférable).
Quand on veut traiter une erreur prévisible, il faut savoir à quelle classe elle appartient, par exemple une erreur de conversion appartient à la classe EConvertError (on peut savoir ceci en consultant dans l'aide, le type d'erreur soulevée par une fonction particulière).
Le try..except pourra se présenter ainsi :
|
XIX-C-2. Listes non exhaustive de classe d'exception
Ceci est une liste incomplète des classes d'exception que vous pouvez être amené
à utiliser.
- EconvertError : Erreur de conversion, vous essayez de convertir un
type en un autre alors qu'ils sont incompatibles.
Exemple : un string en integer (StrToInt) avec un string ne correspondant pas à un nombre entier. - EDivByZero : Le programme a tenté de faire une division par zéro (pas Cool).
- EFOpenError : Le programme ne peut ouvrir un fichier spécifié (le fichier n'existe pas par exemple)
- EInOutError : Erreur d'entrée-sortie, sur le fichier spécifié
- EReadError : le programme tente de lire des données dans un flux mais ne peut lire le nombre spécifié d'octets.
- ERangeError : Débordement de taille. Le programme dépasse les borne d'un type entier ou les limites d'un tableau.
- EAbort : Exception spéciale car elle n'affiche pas de message d'erreur. On peut s'en servir pour annuler une tache en cours si une condition arrive (une erreur par exemple). Pour la déclencher un simple appel à Abort; suffit, une exception EAbort est alors générée. C'est un moyen simple de créer une exception personnalisée, il suffit alors de traiter l'exception on EAbort do.
Si vous n'arrivez pas à trouver la classe d'exception correspondant à votre code,
tenter de provoquer l'erreur. Dans le message d'erreur, Delphi vous indiquera la
classe d'exception (si elle existe), ce message permet aussi de tester si on a
intercepté la bonne classe d'exception.
XIX-D. Exception personnalisée
Toutes les exceptions dérivent de la Class exception, vous pouvez donc créer vos
propres classes d'exception en héritant de cette classe. Ceci peut vous permettre
de gérer votre programme par exception en supprimant tous les test des cas qui ne
vous intéresse pas (exemple : la vérification qu'un diviseur est non nul).
En disant que toutes les exceptions dérivent de la classe Exception, je ne suis pas tout à fait exact. En fait n'importe quel objet peut être déclenché en tant qu'exception. Cependant, les gestionnaires d'exception standard ne gèrent que les exceptions dérivant de la classe exception.
Dans votre programme, si vous voulez déclarer une nouvelle classe d'exception, vous aurez à entrer le code suivant :
En disant que toutes les exceptions dérivent de la classe Exception, je ne suis pas tout à fait exact. En fait n'importe quel objet peut être déclenché en tant qu'exception. Cependant, les gestionnaires d'exception standard ne gèrent que les exceptions dérivant de la classe exception.
Dans votre programme, si vous voulez déclarer une nouvelle classe d'exception, vous aurez à entrer le code suivant :
|
Seule la première ligne est obligatoire. Si vous ne précisez pas le reste, la seule
information disponible lors du déclenchement de votre exception sera son nom.
FonctionGerantErreur est le nouveau gestionnaire de l'exception, dans cette fonction
vous mettrez le code assurant la stabilité de votre application.
Remarque : Pour accéder au message ou à la Méthode d'une Exception (FonctionGerantErreur) tapez E.Message ou E.MaFonction. Dans ce cas le try..except doit s'écrire ainsi : |
|
Exemple : Exception personnalisée
|
et dans le code
|
La propriété Message de la classe Exception (y compris les classes dérivées) et
l'affichage de message personnalisé dans le bloc except/end sont équivalents. Pour
les classes d'exception déjà existantes, on préférera sans doute rendre le message
plus explicite tandis que pour les classes personnalisées on aura recours à la
propriété Message plutôt que d'indiquer à chaque fois le texte.
Il en est de même pour les instructions gérant l'erreur. On n'utilisera la Méthode de la classe que pour ceux personnalisées, évitant d'utiliser une fonction orpheline ou pire de réécrire à chaque fois le code.
Reportez-vous sur les classes pour plus de renseignement sur l'héritage.
Il en est de même pour les instructions gérant l'erreur. On n'utilisera la Méthode de la classe que pour ceux personnalisées, évitant d'utiliser une fonction orpheline ou pire de réécrire à chaque fois le code.
Reportez-vous sur les classes pour plus de renseignement sur l'héritage.
XIX-E. Raise
Protéger ainsi votre code, vous permet d'intercepter les messages d'erreur. Toutefois
dans certaine situation, vous souhaiterez que le message d'erreur soit propagé pour
qu'il soit intercepté par une autre gestion des exceptions.
Prenons l'exemple de l'assignation de fichier, plutôt que d'utiliser une variable de retour pour indiquer le résultat de l'opération, on pourrait transmettre le message d'erreur éventuel. A cet effet, la commande raise est à votre disposition.
Raise ne sert pas uniquement à propager un message d'erreur, on peut aussi s'en servir pour déclencher une exception (en générale pour déclencher une exception personnalisée).
Ci-dessous, un exemple de déclenchement d'exception personnalisée.
Prenons l'exemple de l'assignation de fichier, plutôt que d'utiliser une variable de retour pour indiquer le résultat de l'opération, on pourrait transmettre le message d'erreur éventuel. A cet effet, la commande raise est à votre disposition.
Raise ne sert pas uniquement à propager un message d'erreur, on peut aussi s'en servir pour déclencher une exception (en générale pour déclencher une exception personnalisée).
Ci-dessous, un exemple de déclenchement d'exception personnalisée.
|
Exemple complet de l'utilisation de raise :
|
XIX-F. Conclusion
Grâce aux exceptions apparues avec la programmation objet, le développeur a maintenant
à sa disposition un outil efficace pour protéger son programme des aléas de l'informatique.
J'espère que le présent tutorial a été pour vous une mine d'information et que désormais
vous aborderez la gestion des exceptions avec sérénité.