870 likes | 1.05k Views
Chapitre XI. Gestion des erreurs et exceptions. La gestion des erreurs et exceptions. De nombreux problèmes peuvent survenir pendant l’exécution d’un programme: - insuffisance de mémoire, disque plein, - perte d’un fichier, imprimante non branchée ou à court de papier,
E N D
Chapitre XI Gestion des erreurs et exceptions
La gestion des erreurs et exceptions De nombreux problèmes peuvent survenir pendant l’exécution d’un programme: - insuffisance de mémoire, disque plein, - perte d’un fichier, imprimante non branchée ou à court de papier, - saisie non valide d’une valeur, - une fonction ou une classe qui fonctionnent mal, - etc. Rôle des programmeurs face à ces erreurs d’exécution: - prévoir ces erreurs, - une application étant construite à partir d’un enchaînement d’appels de fonctions, toute fonction pouvant être confrontée à une erreur d’exécution, il devient nécessaire de pouvoir gérer les appels (les erreurs) en cascade de fonctions - en informer l’utilisateur, sauver son travail et arrêter le programme de façon contrôlée, - éventuellement, de mettre en œuvre des solutions de reprises et de correction.
Mise en œuvre de la gestion des erreurs d’exécution PROBLÈME RENCONTRÉ : le code qui détecte l’erreur ne peut rien faire pour sauver le travail de l’usager et quitter élégamment. Supposer que les erreurs ne se produiront pas est une mauvaise approche. Utiliser une variable globale qui sera initialisée par la fonction provoquant l’erreur d’exécution. Le contenu de cette variable sera ensuite récupéré et traité par la fonction appelante de celle qui a entraîné l’erreur d’exécution. Les variables globales n’offrent aucune garantie de sécurité. Cela signifie que toutes les fonctions d’un programme peuvent y accéder sans restriction. **** à éviter **** Ignorer l’erreur Peut masquer des erreurs importantes de programmation Programmation défensive Prendre la décision de ne rien faire en cas de demande erronée. Ex. : Ne pas insérer un nouvel élément dans une pile pleine. Dans certains cas, on ne peut ignorer l’erreur parce que l’opération ne peut être complétée. Ex. : lors d’un calcul.
Mise en œuvre de la gestion des erreurs d’exécution • Imprimer un message d’erreur et / ou arrêter le programme • Le code de traitement des erreurs est dispersé et imbriqué tout au long du code du • système. • Les erreurs sont gérées là où ces erreurs sont le plus susceptibles de se produire. • Avantage: En lisant le code, on peut voir le traitement d’erreur dans le • voisinage immédiat du code et déterminer si une vérification • adéquate des erreurs a été mise en place. • Désavantage: Le code est « pollué » par le traitement d’erreurs. • Cela rend le code plus difficile à comprendre et à maintenir. Envisageable au cours de la phase de mise au point seulement. **** à éviter ****
Mise en œuvre de la gestion des erreurs d’exécution • Peut entraîner irritation et frustration. • L’usager ne devrait recevoir que des messages ou des avertissements qui ont du sens • à son niveau et sur lesquels il peut prendre action. Ex. : Un message avisant l’usager que la mémoire disponible est insuffisante permettrait à celui-ci de fermer des applications libérant de la mémoire supplémentaire. Arrêt de l’exécution grâce à des assertions Une assertion est une expression booléenne évaluée lors de l’exécution. Si l’expression est évaluée comme fausse, un message d’erreur de diagnostic est imprimé et le programme s’arrête. Cela comprend généralement le texte de l’assertion ayant échoué ainsi que le nom de fichier et le numéro de ligne où apparaît l’assertion.
Mise en œuvre de la gestion des erreurs d’exécution Exemple : #include <cassert> . . . double valeur_future(double solde_initial, double p, int n) { assert(p >= 0); assert(n >= 0); return solde_initial * pow(1 + p / 100, n); } Ce mécanisme peut être désactivé selon la volonté du programmeur. Si cela est fait pour des raisons de performance dans les programmes de production, on opte pour une politique à courte vue. Inconvénient : Plus important, mettre fin à l’exécution est souvent une méthode trop radicale pour répondre à une erreur. De nombreuses erreurs peuvent être gérées si elles sont signalées de façon à ce qu’un programmeur puisse les détecter et les analyser.
Mise en œuvre de la gestion des erreurs d’exécution • Approche classique d’interception des erreurs: retour de code d’erreur • -des fonctions symbolisent les traitements à mettre en œuvre; • - ces fonctions renvoient des valeurs qui peuvent servir à déterminer le succès • ou l’échec du traitement; • - on peut tester les valeurs de retour des fonctions et réagir en conséquence • aux erreurs d’exécution.
Utilisation des valeurs de retour des fonctions #include <iostream.h> int Fonction_1(………….) { ……... // Retourne 0 si aucune erreur n’est détectée; un entier positif autrement. …….. } ………... int Fonction_m(………….) { ……... // Retourne 0 si aucune erreur n’est détectée; un entier positif autrement. …….. } Chapitre XI - Gestion des erreurs et exceptions
Utilisation des valeurs de retour des fonctions int main() { ………. // Appel de la fonction Fonction_i. // Si une erreur est détectée, alors gérer cette erreur en lien avec la fonction Fonction_i sinon appel de la fonction Fonction_j si une erreur est détectée, alors gérer cette erreur en lien avec Fonction_j sinon ……….. ………. } Cela met en évidence l’architecture nécessaire à des appels de fonctions en cascade qui peuvent entraîner une erreur d’exécution. Chapitre XI - Gestion des erreurs et exceptions
Utilisation des valeurs de retour des fonctions Dans ce contexte, les appels de fonctions sont séparés par un test (if) qui permet de vérifier la validité de l’appel précédent. Architecture peu lisible et surtout très pénible à maintenir (if-else imbriqué qui alourdit l’écriture et la mise à jour du programme). int Fonction_B(char c) { if (c == ' ') return 1; if ((c < 'A') | (c > 'Z')) return 3; // Traitement. return 0; } #include <iostream.h> enum Nom_des_fonctions {Fn_A, Fn_B}; int Fonction_A(float v) { if (v < 0.0) return 1; if (v == 0.0) return 2; // Traitement. return 0; } Chapitre XI - Gestion des erreurs et exceptions
Utilisation des valeurs de retour des fonctions int Gestion_des_erreurs(Nom_des_fonctions Nom, int code_erreur) { if (Nom== Fn_A) switch(code_erreur) { case 0 : cout << "Parfait"; return 0; case 1 : cout<<"Erreur 1"; return 1; // Erreur grave case 2 : cout << "Erreur 2"; return 0; default: cout << "Erreur"; return 0; } else Chapitre XI - Gestion des erreurs et exceptions
Utilisation des valeurs de retour des fonctions if (Nom== Fn_B) switch(code_erreur) { case 0 : cout << "Parfait"; return 0; case 3 : cout <<"Erreur 3"; return 1; // Erreur grave default: cout << "Erreur"; return 0; } else cout << "incomplet "; return 0; } Chapitre XI - Gestion des erreurs et exceptions
Utilisation des valeurs de retour des fonctions int main() { char C = ' '; float U = -3.2f; if (Gestion_des_erreurs(Fn_A, Fonction_A(U))) return 1; if (Gestion_des_erreurs(Fn_B, Fonction_B(C))) return 1; return 0; } Un constructeur ne possédant pas de valeur de retour, cette fonction ne peut pas renvoyer de valeur pour informer la fonction appelante qu’une erreur s’est produite. Chapitre XI - Gestion des erreurs et exceptions
Mécanisme des exceptions • Solution fiable et standardisée de gestion d’erreurs d’exécution qui ne se fonde pas • sur les valeurs de retour des fonctions. • permet de capturer toutes les exceptions d’un type donné, • permet de lever des exceptions et transférer le contrôle avec les informations • pertinentes à une autre partie du code qui pourra gérer la situation correctement, • en retirant le code de traitement des erreurs du « flot principal » d’exécution d’un • programme, cela permet d’améliorer la lisibilité des programmes et faciliter leur • maintenance. • permet de gérer nativement les appels de fonctions en cascade, • - peut être utilisé dans le cas des constructeurs. Chapitre XI - Gestion des erreurs et exceptions
Mécanisme des exceptions Note : • Le traitement des exceptions est conçu pour traiter les erreurs synchrones • (Ex. : division par zéro) • mais, non, pour gérer des situations asynchrones • (Ex. : fin d’une opération d’E/S sur disque) • lesquelles seront prises en compte par un traitement d’interruption. • Il est bien adapté aux systèmes construits sur la base de composants développés • de manière distincte. Étapes de mise en œuvre: (a) définir une classe d’exception (b) lancer l’exception (c) intercepter l’exception. Chapitre XI - Gestion des erreurs et exceptions
Définition d’une classe d’exception Correspond à une classe C++ qui peut fournir des informations sur une erreur. La définition d’une classe d’exception est obligatoire pour mettre en œuvre le mécanisme d’exception mais il n’existe aucune contrainte particulière pour définir cette classe. class Erreur { // Données et fonctions membres permettant de traiter les cas // d’exception. // Ex. : Affichage d’un message d’erreur. } Ex.: Chapitre XI - Gestion des erreurs et exceptions
Lancement d’une exception Toute fonction qui souhaite lancer une exception doit utiliser un nouvel opérateur du langage C++, throw. Cet opérateur doit être suivi par un objet créé à partir d’une classe d’exception. But de cet opérateur : ° quitter la fonction qui l’utilise, ° informer la fonction appelante qu’une exception a été générée. void Positive(int v) { if (v<0) { Erreur exc; throw exc; } // Traitement propre à cette fonction réalisé uniquement si l’exception // n’est pas lancée. } Ex.: Voir à la prochaine étape comment la fonction appelante peut intercepter cette exception Chapitre XI - Gestion des erreurs et exceptions
Lancement d’une exception Réalise sur la même ligne la création de l’objet de type Erreur et l’envoi de cet objet avec throw. Syntaxe simplifiée: void Positive(int v) { if (v<0) throw Erreur(); // Traitement propre à cette fonction réalisé uniquement si l’exception // n’est pas lancée. } Un objet d’exception est créé sans lui attribuer de nom. Avantageux seulement si vous n’avez pas besoin d’appeler des fonctions membres de la classe d’exception. Dans ce cas, le simple fait de savoir qu’une exception d’un type donné a été lancée peut fournir une information suffisante pour que le gestionnaire catch puisse effectuer son travail correctement.
Lancement d’une exception Spécification d’exception d’une fonction Optionnel pour une fonctionsusceptible d’envoyer une ou plusieurs exceptions, il s’agit d’indiquer dans sa déclaration juste après la liste de ses arguments les exceptions susceptibles d’être lancées. float Racine_carree(float v) throw(Negatif) { // Traitement } Ex.: Cette fonction est capable de lancer des exceptions correspondant à la classe Negatif. Rôle : - permet au compilateur de contrôler la liste des exceptions envoyées par une fonction, - permettre aux programmeurs d’identifier très rapidement la liste des exceptions pouvant être lancées par une fonction. Autrement, le compilateur générera un avertissement. Chapitre XI - Gestion des erreurs et exceptions
Interception d’une exception L’interception d’une exception n’est pas obligatoire. Par contre, une exception envoyée et non interceptée provoque la fin de l’exécution du programme. Pour intercepter une exception, le C++ fournit le bloc try et un ou plusieurs blocs catch: try { // Traitement renfermant des appels de fonctions pouvant générer // des exceptions. } catch (Classe_Exception Ex) { // Ce bloc s’exécute pour une exception de type Classe_Exception. } ……. Chapitre XI - Gestion des erreurs et exceptions
Interception d’une exception catch (...) { // Ce bloc s’exécute pour tous les autres types d’exceptions. // Il faut toujours mettre catch(…) à la fin de la liste des gestionnaires // qui suivent un bloc try, sans quoi, cela empêche l’exécution des // blocs catch qui le suivent. } Pour intercepter les exceptions susceptibles d’être envoyées par certaines fonctions, vous devez appeler ces fonctions dans le cadre d’un bloc try délimité par des accolades. Après le bloc try, il faut obligatoirement spécifier au moins un bloc catch. Les exceptions ne peuvent être lancées que de l’intérieur de blocs try. Une exception lancée de l’extérieur d’un bloc try provoque la fin du programme. Chapitre XI - Gestion des erreurs et exceptions
Interception d’une exception Dans le cas de l’interception d’une classe d’exception précise, le bloc catch récupère directement une copie de l’objet créé et renvoyé par l’opérateur throw. Lorsqu’une exception est envoyée par une des fonctions appelées dans le bloc try, le mécanisme d’exception entraîne : Tous les objets créés dans le bloc try sont détruits. Le programme sort du bloc try juste après la fonction qui a entraîné l’exception et n’exécute pas les instructions situées après cette fonction. Au lancement, l’exception est capturée par le plus proche gestionnaire d’exceptions (p/r au bloc try depuis lequel l’exception a été lancée) qui spécifie un type approprié. Le C++ exécute dans l’ordre, soit le bloc catch correspondant à l’exception interceptée s’il existe, soit le bloc catch(…). Si aucune de ces conditions n’est remplie, cela entraîne la fin d’exécution du programme.
Interception d’une exception Si un des blocs catch a été utilisé, à la fin de son exécution, le programme continue à exécuter les instructions situées après le dernier bloc catch associé à ce bloc try. Il est impossible de retourner au point de lancement en plaçant une instruction return dans un gestionnaire catch. Un tel return provoque le retour à la fonction qui a appelé la fonction contenant le bloc catch. Si, à l’exécution, le code du bloc try ne lance pas d’exception, alors tous les gestion- naires catch qui suivent immédiatement le bloc try sont évités et l’exécution reprend à la première ligne de code suivant les gestionnaires catch. Chapitre XI - Gestion des erreurs et exceptions
Interception d’une exception libellé affiché à l’écran lancement d’une impression écriture dans un fichier log etc. Que doit-on mettre en œuvre dans un bloc catch? Tout dépend du niveau d’erreur de l’exception interceptée : * arrêter le programme avec ou sans message utilisateur, * corriger le problème avec ou sans message utilisateur, * uniquement informer l’utilisateur. l’administrateur du site informatique le programmeur mauvaise saisie à l’écran, instabilité numérique, etc. perte d’un fichier, d’un disque, etc. Chapitre XI - Gestion des erreurs et exceptions
Interception d’une exception Que doit-on mettre en œuvre dans un bloc catch? Lors de la capture d’une exception, il se peut que des ressources qui avaient été allouées n’aient pas encore été relâchées dans le bloc try. Le gestionnaire catch doit, si possible, les libérer. De même, le gestionnaire catch doit fermer tout fichier ouvert dans le bloc try. Chapitre XI - Gestion des erreurs et exceptions
Valeurs lancées et interceptées Vous pouvez lancer n’importe quel type de valeur, primitive ou objet. throw 3; Exemple : try { . . . } catch (int a) { . . . } Ce n’est pas une très bonne idée. Cela n’offre pas suffisamment d’informations pour expliquer l’erreur. Note : Les conversions implicites, comme celles de int en double ou de char * en string ne sont pas effectuées lorsqu’une valeur est lancée. Chapitre XI - Gestion des erreurs et exceptions
Exemple Fichier Syndicat.h class syndicat { /* Le président d'un syndicat local possède l'information suivante sur ses membres (dont le nombre ne dépassera jamais 100) : - le numéro d'employé (lequel est une identification unique de l'employé correspondant à un nombre de 4 chiffres dont le premier est 6), - son nom, - son prénom, - son adresse, - son traitement annuel - sa cotisation syndicale annuelle (entre 0 et 2%). Chaque composante de la classe syndicat renferme les caractéris- tiques d'un membre. */ Chapitre XI - Gestion des erreurs et exceptions
Exemple protected: struct employe { int numero_employe; char nom[20+1], prenom[20+1], adresse[40+1]; float traitement_annuel; float cotisation_annuelle; } ensemble_membres[100]; int Nombre_de_membres; /* Renferme le nombre de syndiqués.*/ Chapitre XI - Gestion des erreurs et exceptions
Exemple public: syndicat(); /* Permet de créer un syndicat avec aucun membre. Pré - Nil. Post - Le syndicat est créé avec aucun membre.*/ float Calcule_cotisation_annuelle_moyenne(); /* Fournit la moyenne des cotisations annuelles des membres. Pré - Le syndicat a déjà été créé et possède au moins un membre. Post - Retourne la moyenne des cotisations annuelles.*/ Chapitre XI - Gestion des erreurs et exceptions
Exemple void Inserer_nouveau_membre( int numero_employe, char * nom, char * prenom, char * adresse, float traitement_annuel, float cotisation_annuelle); /* Permet d'ajouter un nouveau membre syndiqué dont les caractéristiques sont passées en paramètres. Pré - Le syndicat a déjà été créé et le nombre d'employés est moindre que 100. L'employé ayant ce numéro n'est pas syndiqué jusqu'à maintenant. Post - Le nouveau membre dont les caractéristiques sont passées en paramètres fait maintenant partie du syndicat. */ }; Chapitre XI - Gestion des erreurs et exceptions
Exemple class Erreur { public: int code_erreur; int numero_employe; float cotisation_annuelle; }; Chapitre XI - Gestion des erreurs et exceptions
Exemple Fichier Syndicat.cpp #include <string.h> #include "syndicat.h" syndicat::syndicat() { Nombre_de_membres = 0; } Chapitre XI - Gestion des erreurs et exceptions
Exemple float syndicat::Calcule_cotisation_annuelle_moyenne() { Erreur Exc; float somme = 0.0f; if (Nombre_de_membres == 0) { Exc.code_erreur = 1; throw Exc; }; for (int i = 0; i < Nombre_de_membres; i++) somme += ensemble_membres[i].cotisation_annuelle; return somme / Nombre_de_membres; } Chapitre XI - Gestion des erreurs et exceptions
Exemple void syndicat::Inserer_nouveau_membre( int numero_employe, char * nom, char * prenom, char * adresse, float traitement_annuel, float cotisation_annuelle) { Erreur Exc; if (Nombre_de_membres == 100) { Exc.code_erreur =2; throw Exc; }; Chapitre XI - Gestion des erreurs et exceptions
Exemple for (int i = 0; i < Nombre_de_membres; i++) if (ensemble_membres[i].numero_employe == numero_employe) { Exc.code_erreur = 3; throw Exc; }; if ((numero_employe < 6000) | (numero_employe > 6999)) { Exc.code_erreur = 4; Exc.numero_employe = numero_employe; throw Exc; }; Chapitre XI - Gestion des erreurs et exceptions
Exemple if((cotisation_annuelle < 0) | (cotisation_annuelle > 2)) { Exc.code_erreur = 5; Exc.cotisation_annuelle = cotisation_annuelle; throw Exc; }; Chapitre XI - Gestion des erreurs et exceptions
Exemple ensemble_membres[Nombre_de_membres].numero_employe = numero_employe; strcpy(ensemble_membres[Nombre_de_membres].nom, nom); strcpy(ensemble_membres[Nombre_de_membres].prenom, prenom); strcpy(ensemble_membres[Nombre_de_membres].adresse, adresse); ensemble_membres[Nombre_de_membres].traitement_annuel = traitement_annuel; ensemble_membres[Nombre_de_membres].cotisation_annuelle = cotisation_annuelle; Nombre_de_membres += 1; } Chapitre XI - Gestion des erreurs et exceptions
Exemple Fichier Application.cpp #include <iostream.h> #include "syndicat.h" void main() { syndicat S; Chapitre XI - Gestion des erreurs et exceptions
Exemple try { S.Inserer_nouveau_membre( 6423, "Poulin", "Luc", "4356 rue Dupre, Quebec", 53500.0f, 1.97f); S.Inserer_nouveau_membre( 6677, "Fortin", "Diane", "4356 rue Duluth, Ste_Foy", 53900.0f, 0.35f); S.Inserer_nouveau_membre( 6820, "Leduc", "Pierre", "56 Lapointe, Cap_Rouge", 75700.0f, 2.35f); cout << S.Calcule_cotisation_annuelle_moyenne(); } Chapitre XI - Gestion des erreurs et exceptions
Exemple catch(Erreur Exc) { switch(Exc.code_erreur) { case 1 : cout << "Il faut au moins un employe"; break; case 2 : cout << "Il y a deja 100 membres."; break; case 3 : cout << "Cet employe est deja membre."; break; case 4 : cout << "Matricule invalide : " << Exc.numero_employe; break; case 5 : cout << "cotisation invalide : " << Exc.cotisation_annuelle; break; } } } Chapitre XI - Gestion des erreurs et exceptions
Mise en œuvre de plusieurs classes d’exception Fichier Syndicat.h Le fichier n’a pas changé à l ’exception de la classe Erreur qui a été remplacée par : class Erreur_Numero_employe { private: int numero_employe; public: Erreur_Numero_employe(int Numero_employe); void Afficher(); }; Chapitre XI - Gestion des erreurs et exceptions
Mise en œuvre de plusieurs classes d’exception Fichier Syndicat.h class Erreur_Cotisation { private: float cotisation_annuelle; public: Erreur_Cotisation(float cotisation); void Afficher(); }; class Erreur_Nombre_de_membres { private: int nombre_de_membres; public: Erreur_Nombre_de_membres (int Nombre_de_membres); void Afficher(); }; Chapitre XI - Gestion des erreurs et exceptions
Mise en œuvre de plusieurs classes d’exception Fichier Syndicat.cpp Erreur_Numero_employe::Erreur_Numero_employe (int Numero_employe) { numero_employe = Numero_employe; } Erreur_Nombre_de_membres::Erreur_Nombre_de_membres (int Nombre_de_membres) { nombre_de_membres = Nombre_de_membres; } Erreur_Cotisation::Erreur_Cotisation(float cotisation) { cotisation_annuelle = cotisation; } Chapitre XI - Gestion des erreurs et exceptions
Mise en œuvre de plusieurs classes d’exception Fichier Syndicat.cpp void Erreur_Numero_employe::Afficher() { cout << "Mauvais numero d'employe: " << numero_employe; } void Erreur_Cotisation:: Afficher() { cout << "Mauvais taux de cotisation : " << cotisation_annuelle; } void Erreur_Nombre_de_membres:: Afficher() { cout << "Le nombre de membres est inadequat : " << nombre_de_membres; } Chapitre XI - Gestion des erreurs et exceptions
Mise en œuvre de plusieurs classes d’exception Fichier Syndicat.cpp float syndicat::Calcule_cotisation_annuelle_moyenne() { float somme = 0.0f; if (Nombre_de_membres == 0) throw Erreur_Nombre_de_membres(Nombre_de_membres); for (int i = 0; i < Nombre_de_membres; i++) somme += ensemble_membres[i].cotisation_annuelle; return somme / Nombre_de_membres; } Chapitre XI - Gestion des erreurs et exceptions
Mise en œuvre de plusieurs classes d’exception Fichier Syndicat.cpp void syndicat::Inserer_nouveau_membre ( int numero_employe, char * nom, char * prenom, char *adresse, float traitement_annuel, float cotisation_annuelle) { if (Nombre_de_membres == 100) throw Erreur_Nombre_de_membres(Nombre_de_membres); for (int i = 0; i < Nombre_de_membres; i++) if (ensemble_membres[i].numero_employe == numero_employe) throw Erreur_Numero_employe(numero_employe); if ((numero_employe < 6000) | (numero_employe > 6999)) throw Erreur_Numero_employe(numero_employe); Chapitre XI - Gestion des erreurs et exceptions
Mise en œuvre de plusieurs classes d’exception Fichier Syndicat.cpp if((cotisation_annuelle < 0) | (cotisation_annuelle > 2)) throw Erreur_Cotisation(cotisation_annuelle); // Ajout d’un membre (idem à précédemment) } Chapitre XI - Gestion des erreurs et exceptions
Mise en œuvre de plusieurs classes d’exception Fichier Application.cpp #include <iostream.h> #include "syndicat.h" void main() { syndicat S; Chapitre XI - Gestion des erreurs et exceptions
Mise en œuvre de plusieurs classes d’exception Fichier Application.cpp try { S.Inserer_nouveau_membre( 6423, "Poulin", "Luc", "4356 rue Dupre, Quebec", 53500.0f, 1.97f); S.Inserer_nouveau_membre( 6677, "Fortin", "Diane", "4356 rue Duluth, Sainte_Foy", 53900.0f, 0.35f); S.Inserer_nouveau_membre( 6423, "Leduc", "Pierre", "56 rue Lapointe, Cap_Rouge", 75700.0f, 1.35f); cout << S.Calcule_cotisation_annuelle_moyenne(); } Chapitre XI - Gestion des erreurs et exceptions
Mise en œuvre de plusieurs classes d’exception Fichier Application.cpp catch(Erreur_Numero_employe Exc) { Exc.Afficher(); } catch(Erreur_Cotisation Exc) { Exc.Afficher(); } catch(Erreur_Nombre_de_membres Exc) { Exc.Afficher(); } } Chapitre XI - Gestion des erreurs et exceptions