Le présent tutorial 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).
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.
instructions1 try // Bloc de code à protéger instructions protégées finally // 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é instructions2 end; // suite des instructions qui seront exécutées si elles ne provoquent pas d'erreur instructions3 |
instructions1 try // Bloc de code à protéger instructions protégées except // Second bloc de code // Ce bloc ne sera exécuté que si une erreur survient dans la partie protégée instructions2 end; // 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.
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
procedure TForm1.Button1Click(Sender: TObject);
var
Ob : TObjetExemple;
begin
Ob := TObjetExemple.Create; // Création de lobjet
try
{instruction}
finally
Ob.Free; // Libération
end;
end;
function OuvrirF(Nom : TFileName) : boolean;
var
F : Textfile;
S : string;
i, j, valeur :integer;
begin
AssignFile(F,Nom);
try
Reset(F);
readln(F,S);
{instruction}
Result := True;
finally
CloseFile(F);
end;
end;
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. |
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 :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.
procedure TForm1.Button1Click(Sender: TObject);
var
param1 : Double;
begin
try
param1 := StrToFloat(Edit1.Text);
{suite des instructions}
except
on EconvertError do
MessageDlg('Erreur : Vous devez entrer un réel'
+#10#13+'Le séparateur décimal est : '+DecimalSeparator, mtError, [mbOk], 0);
end;
{Autre instruction non sensible}
end;
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 à :
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).
try
{instructions}
except
{instruction éventuelle commun à tous les cas possibles d'erreur}
// Gestion des cas prévisibles d'erreur
on Exception1 do InstructionTraitantErr1;
on Exception2 do InstructionTraitantErr2;
....
on Exception(n) do InstructionTraitantErr(n);
else
InstructionTraitantLesCasNonPrevue;
end;
Ceci est une liste incomplète des classes d'exception que vous pouvez être amené à utiliser.
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.
type
MonException = class(Exception)
[HelpContext : THelpContext; // Context dans l'aide]
[Message : string; // Message d'erreur]
public
FonctionGerantErreur(); // Fonction à appeler en cas d'erreur
end;
FonctionGerantErreur();
begin
{instructions}
end;
Rmq :
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 :
instruction except on E : Exception do ShowMessage('Message : ' + E.Message); // Et/Ou E.MaFonction; // Permet de Centraliser le code gérant un type d'erreur end; |
type
EValeurIncorrect = class(Exception);
if Valeur <> ValeurCorrect then
raise EValeurIncorrect.Create('Valeur ne fait pas partie des valeurs autorisées');
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.
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).
if Valeur <> ValeurCorrect then
raise EValeurIncorrect.Create('Valeur ne fait pas partie des valeurs autorisées');
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
end;
MonException = class(Exception)
public
function GestErr():string;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
function MonException.GestErr():string;
begin
if MessageDlg('La variable transmise est incorrect continuer avec la valeur par défaut', mtInformation, [mbYes, mbNo], 0) =
mrYes then
begin
Result := 'très petit';
end;
end;
function DoQuelqueChose(valeur : double):string;
begin
// La fonction ne travaille que sur des nombres réels positifs
if valeur < 0 then
begin
raise MonException.Create('Erreur: Travaille impossible !');
end;
// Travaille sur valeur complètement sans intérêt
if valeur < 10 then
result := 'très petit'
else if valeur <= 50 then
result := 'moitié de cent'
else if valeur <= 100 then
result := 'égal à cent'
else
result := 'très grand';
end;
procedure TForm1.Button1Click(Sender: TObject);
var
r : double;
tmpstr : string;
begin
try
r := StrToFloat(Edit1.Text);
tmpstr := DoQuelqueChose(r);
except
on E : MonException do
tmpstr := E.GestErr;
on EconvertError do
begin
ShowMessage('Erreur de Saisie : nombre attendu'
+#10#13+'Séparteur décimal : '+DecimalSeparator);
tmpstr := 'Mauvaise saisie';
end;
else
begin
ShowMessage('Erreur Inconnue');
tmpstr := 'Invalid';
end;
end;
ShowMessage('Résultat : '+tmpStr);
end;
end.
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é.