🔝 Retour au Sommaire
Les exceptions sont des événements anormaux qui surviennent pendant l'exécution d'un programme et qui perturbent son flux normal. Une bonne gestion des exceptions permet de :
- Éviter les plantages de l'application
- Informer l'utilisateur de manière claire
- Maintenir l'intégrité des données
- Faciliter le débogage en identifiant les problèmes
- Rendre le code plus robuste et professionnel
Sans gestion d'exceptions, une simple erreur (division par zéro, fichier introuvable, etc.) peut faire planter toute l'application.
Une exception est un objet qui représente une erreur ou une condition anormale. Lorsqu'une erreur se produit, on dit que le programme "lève une exception" (raise an exception).
Exemples de situations générant des exceptions :
- Division par zéro
- Conversion de chaîne invalide (transformer "abc" en nombre)
- Fichier introuvable
- Accès à un index hors limites d'un tableau
- Mémoire insuffisante
- Connexion réseau perdue
Voyons ce qui se passe sans gestion d'exceptions :
procedure TForm1.Button1Click(Sender: TObject);
var
Nombre: Integer;
begin
Nombre := StrToInt(Edit1.Text); // Si l'utilisateur entre "abc", le programme plante !
ShowMessage('Nombre saisi : ' + IntToStr(Nombre));
end;Problème : Si l'utilisateur entre du texte non numérique, l'application génère une erreur et peut se fermer brutalement.
La structure try...except permet d'intercepter et de gérer les exceptions.
Syntaxe :
try
// Code susceptible de générer une exception
except
// Code exécuté en cas d'exception
end;Exemple simple :
procedure TForm1.Button1Click(Sender: TObject);
var
Nombre: Integer;
begin
try
Nombre := StrToInt(Edit1.Text);
ShowMessage('Nombre saisi : ' + IntToStr(Nombre));
except
ShowMessage('Erreur : veuillez entrer un nombre valide');
end;
end;Avantage : Le programme ne plante plus. L'utilisateur reçoit un message clair et peut corriger sa saisie.
Vous pouvez récupérer l'exception pour afficher son message :
procedure TForm1.Button1Click(Sender: TObject);
var
Nombre: Integer;
begin
try
Nombre := StrToInt(Edit1.Text);
ShowMessage('Nombre saisi : ' + IntToStr(Nombre));
except
on E: Exception do
ShowMessage('Erreur : ' + E.Message);
end;
end;Explication :
on E: Exception do: capture toutes les exceptions dans la variableEE.Message: contient le message d'erreur descriptif
Vous pouvez traiter différemment selon le type d'exception :
procedure TForm1.Button1Click(Sender: TObject);
var
Nombre: Integer;
begin
try
Nombre := StrToInt(Edit1.Text);
Nombre := 100 div Nombre; // Division
ShowMessage('Résultat : ' + IntToStr(Nombre));
except
on E: EConvertError do
ShowMessage('Format de nombre invalide : ' + E.Message);
on E: EDivByZero do
ShowMessage('Erreur : division par zéro impossible');
on E: Exception do
ShowMessage('Erreur inattendue : ' + E.Message);
end;
end;Important : Les exceptions sont testées dans l'ordre. Placez les exceptions les plus spécifiques en premier, et l'exception générale Exception en dernier.
procedure TForm1.Button1Click(Sender: TObject);
var
Fichier: TextFile;
Ligne: string;
begin
try
AssignFile(Fichier, 'C:\données.txt');
Reset(Fichier);
while not Eof(Fichier) do
begin
ReadLn(Fichier, Ligne);
Memo1.Lines.Add(Ligne);
end;
CloseFile(Fichier);
ShowMessage('Fichier chargé avec succès');
except
on E: EInOutError do
ShowMessage('Erreur d''accès au fichier : ' + E.Message);
on E: Exception do
ShowMessage('Erreur : ' + E.Message);
end;
end;La structure try...finally garantit qu'un code sera exécuté, qu'une exception se produise ou non. Elle est essentielle pour libérer des ressources (fichiers, objets, connexions, etc.).
Syntaxe :
try
// Code susceptible de générer une exception
finally
// Code exécuté TOUJOURS (avec ou sans exception)
end;procedure TForm1.Button1Click(Sender: TObject);
var
Liste: TStringList;
begin
Liste := TStringList.Create;
try
Liste.LoadFromFile('C:\données.txt');
Memo1.Lines.Assign(Liste);
finally
Liste.Free; // Libération garantie, même en cas d'erreur
end;
end;Pourquoi finally est important :
Sans finally, si une exception se produit pendant LoadFromFile, l'objet Liste ne serait jamais libéré, causant une fuite mémoire.
procedure TForm1.Button1Click(Sender: TObject);
var
Fichier: TextFile;
Ligne: string;
begin
AssignFile(Fichier, 'C:\données.txt');
Reset(Fichier);
try
while not Eof(Fichier) do
begin
ReadLn(Fichier, Ligne);
Memo1.Lines.Add(Ligne);
end;
finally
CloseFile(Fichier); // Fermeture garantie
end;
end;procedure TForm1.Button1Click(Sender: TObject);
begin
Screen.Cursor := crHourGlass; // Curseur sablier
try
// Traitement long
Sleep(5000);
// ... opérations ...
finally
Screen.Cursor := crDefault; // Restauration garantie du curseur
end;
end;procedure TForm1.Button1Click(Sender: TObject);
begin
Button1.Enabled := False;
try
// Traitement
TraiterDonnees;
finally
Button1.Enabled := True; // Réactivation garantie
end;
end;Vous pouvez combiner except et finally pour gérer les exceptions ET garantir le nettoyage :
Syntaxe :
try
// Code susceptible de générer une exception
except
// Gestion des exceptions
end;
finally
// Nettoyage (toujours exécuté)
end;Attention : Cette syntaxe n'est pas directement supportée. Il faut imbriquer deux blocs try :
procedure TForm1.Button1Click(Sender: TObject);
var
Liste: TStringList;
begin
Liste := TStringList.Create;
try
try
Liste.LoadFromFile(Edit1.Text);
Memo1.Lines.Assign(Liste);
ShowMessage('Fichier chargé avec succès');
except
on E: Exception do
ShowMessage('Erreur lors du chargement : ' + E.Message);
end;
finally
Liste.Free; // Toujours libéré
end;
end;Autre approche (plus lisible) :
procedure TForm1.Button1Click(Sender: TObject);
var
Liste: TStringList;
begin
Liste := TStringList.Create;
try
Liste.LoadFromFile(Edit1.Text);
Memo1.Lines.Assign(Liste);
ShowMessage('Fichier chargé avec succès');
except
on E: Exception do
ShowMessage('Erreur lors du chargement : ' + E.Message);
end;
// Libération après le bloc try...except
Liste.Free;
end;Mais attention : Cette dernière approche ne garantit pas la libération si une exception se produit APRÈS le bloc except. Utilisez try...finally autour du tout pour être sûr.
Meilleure pratique :
procedure TForm1.Button1Click(Sender: TObject);
var
Liste: TStringList;
begin
Liste := TStringList.Create;
try
try
Liste.LoadFromFile(Edit1.Text);
Memo1.Lines.Assign(Liste);
except
on E: Exception do
ShowMessage('Erreur : ' + E.Message);
end;
finally
Liste.Free;
end;
end;Vous pouvez lever vos propres exceptions pour signaler des erreurs.
Syntaxe :
raise Exception.Create('Message d''erreur');procedure ValiderAge(Age: Integer);
begin
if Age < 0 then
raise Exception.Create('L''âge ne peut pas être négatif');
if Age > 150 then
raise Exception.Create('L''âge semble invalide');
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Age: Integer;
begin
try
Age := StrToInt(Edit1.Text);
ValiderAge(Age);
ShowMessage('Âge valide : ' + IntToStr(Age));
except
on E: Exception do
ShowMessage('Erreur : ' + E.Message);
end;
end;function DiviserSecurise(A, B: Double): Double;
begin
if B = 0 then
raise Exception.Create('Division par zéro impossible');
Result := A / B;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Resultat: Double;
begin
try
Resultat := DiviserSecurise(10, 0);
ShowMessage(FloatToStr(Resultat));
except
on E: Exception do
ShowMessage('Erreur : ' + E.Message);
end;
end;Vous pouvez intercepter une exception, effectuer une action, puis la re-lever :
procedure TForm1.Button1Click(Sender: TObject);
begin
try
// Code risqué
TraiterDonnees;
except
on E: Exception do
begin
// Journaliser l'erreur
Log('Erreur survenue : ' + E.Message);
// Re-lever l'exception pour qu'elle soit gérée plus haut
raise;
end;
end;
end;Delphi fournit de nombreux types d'exceptions prédéfinis :
| Exception | Description | Exemple |
|---|---|---|
Exception |
Exception de base (parent de toutes) | Toute erreur générique |
EConvertError |
Erreur de conversion | StrToInt('abc') |
EDivByZero |
Division par zéro | X := 10 div 0 |
EInOutError |
Erreur d'entrée/sortie | Fichier introuvable |
ERangeError |
Dépassement de plage | Accès hors limites d'un tableau |
EAccessViolation |
Violation d'accès mémoire | Pointeur nil déréférencé |
EOutOfMemory |
Mémoire insuffisante | Allocation impossible |
EIntOverflow |
Dépassement d'entier | Résultat trop grand |
EInvalidOp |
Opération invalide | Opération mathématique invalide |
EAbort |
Abandon silencieux | Interruption volontaire |
// EConvertError
try
X := StrToInt('abc');
except
on E: EConvertError do
ShowMessage('Conversion impossible');
end;
// EDivByZero
try
X := 10 div 0;
except
on E: EDivByZero do
ShowMessage('Division par zéro');
end;
// ERangeError
var
Tableau: array[0..9] of Integer;
begin
try
Tableau[100] := 5; // Hors limites
except
on E: ERangeError do
ShowMessage('Index hors limites');
end;
end;
// EAccessViolation
var
Objet: TStringList;
begin
Objet := nil;
try
Objet.Add('test'); // Objet nil
except
on E: EAccessViolation do
ShowMessage('Objet non initialisé');
end;
end;Vous pouvez créer vos propres types d'exceptions pour des situations spécifiques.
Syntaxe :
type
EMonException = class(Exception);Exemple simple :
type
EAgeInvalide = class(Exception);
procedure ValiderAge(Age: Integer);
begin
if (Age < 0) or (Age > 150) then
raise EAgeInvalide.Create('Âge invalide : ' + IntToStr(Age));
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Age: Integer;
begin
try
Age := StrToInt(Edit1.Text);
ValiderAge(Age);
ShowMessage('Âge valide');
except
on E: EAgeInvalide do
ShowMessage('Erreur de validation : ' + E.Message);
on E: EConvertError do
ShowMessage('Veuillez entrer un nombre');
end;
end;Exemple avec plusieurs exceptions personnalisées :
type
ECompteInvalide = class(Exception);
ESoldeInsuffisant = class(Exception);
EMontantNegatif = class(Exception);
procedure Retirer(Compte: TCompte; Montant: Double);
begin
if Compte = nil then
raise ECompteInvalide.Create('Compte non initialisé');
if Montant < 0 then
raise EMontantNegatif.Create('Le montant ne peut pas être négatif');
if Compte.Solde < Montant then
raise ESoldeInsuffisant.CreateFmt('Solde insuffisant. Disponible : %.2f €',
[Compte.Solde]);
Compte.Solde := Compte.Solde - Montant;
end;
// Utilisation
try
Retirer(MonCompte, 100);
except
on E: ECompteInvalide do
ShowMessage('Erreur de compte : ' + E.Message);
on E: ESoldeInsuffisant do
ShowMessage('Erreur : ' + E.Message);
on E: EMontantNegatif do
ShowMessage('Montant invalide : ' + E.Message);
end;type
EValidationErreur = class(Exception)
private
FChamp: string;
FValeur: string;
public
constructor Create(const Champ, Valeur, Message: string);
property Champ: string read FChamp;
property Valeur: string read FValeur;
end;
constructor EValidationErreur.Create(const Champ, Valeur, Message: string);
begin
inherited Create(Message);
FChamp := Champ;
FValeur := Valeur;
end;
// Utilisation
procedure ValiderEmail(const Email: string);
begin
if Pos('@', Email) = 0 then
raise EValidationErreur.Create('Email', Email, 'Format d''email invalide');
end;
// Capture
try
ValiderEmail('adresse.invalide');
except
on E: EValidationErreur do
ShowMessage(Format('Erreur dans le champ "%s" (valeur: "%s"): %s',
[E.Champ, E.Valeur, E.Message]));
end;EAbort est une exception spéciale qui ne génère pas de message d'erreur. Elle est utilisée pour interrompre proprement une opération.
procedure TForm1.Button1Click(Sender: TObject);
begin
if MessageDlg('Continuer ?', mtConfirmation, [mbYes, mbNo], 0) = mrNo then
Abort; // Lève EAbort
// Suite du traitement
TraiterDonnees;
end;Particularité : Si EAbort n'est pas capturé, il ne génère pas de message d'erreur à l'utilisateur.
Delphi fournit des versions sécurisées des fonctions de conversion qui ne lèvent pas d'exception :
var
Nombre: Integer;
begin
if TryStrToInt(Edit1.Text, Nombre) then
ShowMessage('Nombre : ' + IntToStr(Nombre))
else
ShowMessage('Conversion impossible');
end;var
Valeur: Double;
begin
if TryStrToFloat(Edit1.Text, Valeur) then
ShowMessage('Valeur : ' + FloatToStr(Valeur))
else
ShowMessage('Conversion impossible');
end;var
Date: TDateTime;
begin
if TryStrToDate(Edit1.Text, Date) then
ShowMessage('Date : ' + DateToStr(Date))
else
ShowMessage('Format de date invalide');
end;Avantage des fonctions Try... :
- Plus rapides (pas de gestion d'exception)
- Code plus lisible pour les validations simples
- Recommandées pour les conversions fréquentes
// ✅ BON : capture spécifique
try
Nombre := StrToInt(Edit1.Text);
except
on E: EConvertError do
ShowMessage('Format invalide : ' + E.Message);
end;
// ❌ Moins bon : capture tout
try
Nombre := StrToInt(Edit1.Text);
except
ShowMessage('Erreur'); // Quelle erreur ? On ne sait pas
end;// ✅ BON : libération garantie
Liste := TStringList.Create;
try
// Utilisation
finally
Liste.Free;
end;
// ❌ MAUVAIS : risque de fuite mémoire
Liste := TStringList.Create;
// Utilisation
Liste.Free; // Si erreur avant, Free n'est pas appelé// ❌ MAUVAIS : capture et ignore
try
OperationCritique;
except
// Ne rien faire cache le problème
end;
// ✅ BON : laisser remonter ou gérer correctement
try
OperationCritique;
except
on E: Exception do
begin
Log('Erreur critique : ' + E.Message);
raise; // Re-lever pour que l'appelant sache
end;
end;// ❌ MAUVAIS : message vague
raise Exception.Create('Erreur');
// ✅ BON : message descriptif
raise Exception.Create('Impossible d''ouvrir le fichier : ' + NomFichier);// ✅ BON : validation au début
function Diviser(A, B: Double): Double;
begin
if B = 0 then
raise Exception.Create('Division par zéro');
Result := A / B;
end;
// ❌ Moins bon : vérification tardive
function Diviser(A, B: Double): Double;
begin
// Beaucoup de code...
if B = 0 then
// Erreur découverte trop tard
raise Exception.Create('Division par zéro');
Result := A / B;
end;// ✅ BON : plus performant
if TryStrToInt(Edit1.Text, Nombre) then
Traiter(Nombre)
else
ShowMessage('Nombre invalide');
// ❌ Moins efficace pour des conversions fréquentes
try
Nombre := StrToInt(Edit1.Text);
Traiter(Nombre);
except
on E: EConvertError do
ShowMessage('Nombre invalide');
end;/// <summary>
/// Divise deux nombres
/// </summary>
/// <exception cref="Exception">Levée si le diviseur est zéro</exception>
function Diviser(A, B: Double): Double;
begin
if B = 0 then
raise Exception.Create('Division par zéro impossible');
Result := A / B;
end;procedure TraiterFichier(const NomFichier: string);
var
Fichier: TextFile;
begin
AssignFile(Fichier, NomFichier);
Reset(Fichier);
try
// Traitement
if not ConditionValide then
raise Exception.Create('Condition non valide');
// Suite du traitement...
finally
CloseFile(Fichier); // Nettoyage garanti, même si exception levée
end;
end;Point important : Ne fermez pas la ressource manuellement avant de lever l'exception : le bloc finally s'en chargera automatiquement. Sinon, vous risquez une double fermeture.
Vous pouvez gérer globalement les exceptions non capturées :
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnException := GererExceptionGlobale;
end;
procedure TForm1.GererExceptionGlobale(Sender: TObject; E: Exception);
begin
// Journaliser
Log('Exception non gérée : ' + E.Message);
// Afficher un message personnalisé
MessageDlg('Une erreur est survenue : ' + E.Message,
mtError, [mbOK], 0);
end;Il est important de garder une trace des exceptions pour le débogage :
procedure LogErreur(const Message: string);
var
Fichier: TextFile;
begin
AssignFile(Fichier, 'C:\Logs\erreurs.log');
if FileExists('C:\Logs\erreurs.log') then
Append(Fichier)
else
Rewrite(Fichier);
try
WriteLn(Fichier, FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + ' - ' + Message);
finally
CloseFile(Fichier);
end;
end;
// Utilisation
try
OperationRisquee;
except
on E: Exception do
begin
LogErreur('Erreur dans OperationRisquee : ' + E.Message);
ShowMessage('Une erreur est survenue');
end;
end;// ❌ MAUVAIS : cache les problèmes
try
OperationImportante;
except
// Rien...
end;// ❌ MAUVAIS : fuite mémoire si exception
Liste := TStringList.Create;
Liste.LoadFromFile('fichier.txt');
Liste.Free;
// ✅ BON
Liste := TStringList.Create;
try
Liste.LoadFromFile('fichier.txt');
finally
Liste.Free;
end;// ❌ MAUVAIS : Exception générale en premier
try
// Code
except
on E: Exception do
ShowMessage('Erreur générale'); // Capture TOUT
on E: EConvertError do
ShowMessage('Conversion'); // Ne sera jamais atteint !
end;
// ✅ BON : spécifique avant générique
try
// Code
except
on E: EConvertError do
ShowMessage('Conversion');
on E: Exception do
ShowMessage('Erreur générale');
end;// ❌ MAUVAIS : perd la trace de l'exception originale
try
// Code
except
on E: Exception do
raise Exception.Create(E.Message); // Nouvelle exception
end;
// ✅ BON : conserve la trace
try
// Code
except
on E: Exception do
begin
Log(E.Message);
raise; // Re-lève l'exception originale
end;
end;// ❌ MAUVAIS : exception pour la logique normale
function TrouverUtilisateur(ID: Integer): TUtilisateur;
begin
if not UtilisateurExiste(ID) then
raise Exception.Create('Utilisateur non trouvé');
// ...
end;
// ✅ BON : utiliser un booléen ou nil
function TrouverUtilisateur(ID: Integer): TUtilisateur;
begin
if not UtilisateurExiste(ID) then
Result := nil // Ou retourner False avec un paramètre out
else
// Chercher l'utilisateur
end;- try...except : capture et gère les exceptions
- try...finally : garantit l'exécution du code de nettoyage
- raise : lève une exception
- Capturer les exceptions spécifiques avant les génériques
- Toujours libérer les ressources dans un bloc finally
- Utiliser Try... fonctions pour éviter les exceptions fréquentes
- Ne pas capturer les exceptions qu'on ne peut pas gérer correctement
- Fournir des messages d'erreur clairs et informatifs
- Journaliser les exceptions pour le débogage
- Les exceptions sont pour les situations anormales, pas la logique normale
La gestion des exceptions est essentielle pour créer des applications robustes et professionnelles. Elle permet de prévoir les erreurs, d'informer clairement l'utilisateur et de maintenir l'intégrité de votre application. Dans la section suivante, nous explorerons la programmation orientée objet, qui permettra de structurer encore mieux votre code.