Mettre en oeuvre les meilleures pratiques pour la gestion des exceptions
Introduction aux Exceptions
Qu’est-ce qu’une exception ?
Une exception est un événement qui interrompt le flux normal d’un programme.
Pourquoi les exceptions sont-elles importantes ?
Elles permettent de gérer les erreurs de manière structurée et de maintenir le flux normal du programme.
Objectifs de la Gestion des Exceptions
Anticiper les erreurs : Identifier les scénarios d’erreur potentiels.
Détecter les erreurs : Implémenter des mécanismes pour capturer les erreurs lorsqu’elles se produisent.
Réagir aux erreurs : Définir comment le programme doit répondre à différents types d’erreurs.
Pourquoi sécuriser un programme ?
Anticiper les erreurs : Identifier les scénarios d’erreur potentiels.
Détecter les erreurs : Implémenter des mécanismes pour capturer les erreurs lorsqu’elles se produisent.
Réagir aux erreurs : Définir comment le programme doit répondre à différents types d’erreurs.
Sécuriser un programme sans gestion spécifique des erreurs
Prédiction des erreurs : Analyser le code et l’utilisation pour identifier les erreurs possibles.
Ajout de code de prévention/détection : Utiliser des vérifications de conditions (if statements) et des assertions pour détecter les erreurs.
Réactions aux erreurs : Définir comment gérer les erreurs :
Avertissements : Informer l’utilisateur ou le développeur.
Corrections dynamiques : Tenter de corriger l’erreur automatiquement.
Arrêts contrôlés : Arrêter le programme de manière sécurisée.
Propagation des erreurs : Décider si l’appelant ou l’appelé gère l’erreur.
publicbooleanvalidateInput(String input){if(input ==null|| input.isEmpty()){System.err.println("Entrée invalide : l'entrée ne doit pas être nulle ou vide.");returnfalse;}returntrue;}publicvoidprocessInput(String input){if(validateInput(input)){// Traitement de l'entréeSystem.out.println("Traitement de l'entrée : "+ input);}}processInput(null);
Entrée invalide : l'entrée ne doit pas être nulle ou vide.
Défis
Lisibilité : Mélanger le code de test avec le code de l’application rend la lecture difficile.
Maintenabilité : Difficile de maintenir le code de gestion des erreurs.
Responsabilité : Incertitude sur qui doit gérer l’erreur, l’appelant ou l’appelé.
Approche de Java pour la sécurité
Java propose un mécanisme robuste pour gérer les erreurs via les exceptions.
Avantages :
Sépare le code de gestion des erreurs du code régulier.
Propage les erreurs dans la pile d’appels.
Regroupe et différencie les types d’erreurs.
La classe Exception
Définition : La classe Exception est la classe de base pour toutes les exceptions en Java.
Les exceptions sont des objets qui encapsulent des informations sur les erreurs.
Constructeurs :
Exception(String message): Construit une nouvelle exception avec le message détaillé spécifié.
Exception(String message, Throwable cause): Construit une nouvelle exception avec le message détaillé spécifié et la cause.
La “cause” est éventuellement une autre exception qui a conduit à l’exception actuelle.
Cela permet de chaîner les exceptions pour mieux comprendre la séquence d’erreurs qui a conduit à un problème.
Exceptions prédéfinies : IllegalArgumentException, NullPointerException, ArrayIndexOutOfBoundsException, etc.
Méthodes :
String getMessage(): Retourne le message détaillé de l’exception.
Throwable getCause(): Retourne la cause de l’exception.
printstacktrace() : Affiche la trace de la pile
Exception e =newException("Message d'erreur");System.out.println("Le message d'erreur est : %s".formatted(e.getMessage()));e.printStackTrace();// Affiche la trace de la pile
Le message d'erreur est : Message d'erreur
java.lang.Exception: Message d'erreur
at REPL.$JShell$62C.do_it$($JShell$62C.java:52)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at io.github.spencerpark.ijava.execution.IJavaExecutionControl.lambda$execute$1(IJavaExecutionControl.java:95)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)
Types d’Exceptions
Exceptions vérifiées (Checked Exceptions) :
Vérifiées à la compilation.
Héritent de Exception (mais pas de RuntimeException) qui hérite de Throwable.
Doivent être gérées ou déclarées dans la signature de la méthode avec throws.
Exemple : IOException, SQLException
Exceptions non vérifiées (Unchecked Exceptions) :
Vérifiées à l’exécution.
Héritent de RuntimeException (qui hérite de Exception).
Exemple : NullPointerException, ArrayIndexOutOfBoundsException
Erreurs (Errors) :
Problèmes sérieux qu’une application ne devrait pas essayer de capturer.
Bloc try-catch : Utilisé pour entourer le code qui peut générer une exception.
Bloc try-finally : Utilisé pour exécuter un code qui doit s’exécuter, qu’une exception soit levée ou non.
Bloc try-catch-finally : Combinaison des deux blocs précédents.
try{// Code pouvant générer une exception}catch(ExceptionType1 e1){// Gérer l'exception de type ExceptionType1}catch(ExceptionType2 e2){// Gérer l'exception de type ExceptionType2}finally{// Code qui s'exécute toujours}
try(BufferedReader reader =newBufferedReader(newFileReader("/etc/issue.net"))){String line;while((line = reader.readLine())!=null){System.out.println(line);}}catch(FileNotFoundException e){ logger.severe("Erreur de fichier non trouvé %s".formatted(e.getMessage()));}catch(IOException e){ logger.severe("Erreur de lecture du fichier %s".formatted(e.getMessage()));}
Ubuntu 24.04.1 LTS
Création d’Exceptions Personnalisées
Les exceptions personnalisées permettent de définir des erreurs spécifiques à une application.
Elles héritent de la classe Exception ou d’une de ses sous-classes.
EvalException: Message d'erreur personnalisé
Execution exception
---------------------------------------------------------------------------
REPL.$JShell$78E$CustomException: Message d'erreur personnalisé
at .someMethod(#79:1)
at .(#107:1)
Autoclôture des Ressources
Java 7 a introduit le concept d’autoclôture des ressources pour simplifier la gestion des ressources.
Les ressources qui implantent l’interface AutoCloseable peuvent être utilisées dans un bloc try-with-resources.
Les ressources sont automatiquement fermées à la fin du bloc try.
Pas besoin de bloc finally pour fermer les ressources.
Exemple avec l’ouverture d’un fichier
publicclass MyDataException extendsException{publicMyDataException(String message,Throwable cause){super(message, cause);}}publicvoidreadIssueFile(String filepath)throws MyDataException {try(BufferedReader reader =newBufferedReader(newFileReader(filepath))){String line;while((line = reader.readLine())!=null){System.out.println(line);}}catch(FileNotFoundException e){thrownewMyDataException("Erreur de fichier non trouvé", e);}catch(IOException e){thrownewMyDataException("Erreur de lecture du fichier", e);}}
Ubuntu 24.04.1 LTS
readIssueFile("/etc/issue.net");
Ubuntu 24.04.1 LTS
readIssueFile("/etc/missingfile");
EvalException: Erreur de fichier non trouvé
Execution exception
---------------------------------------------------------------------------
REPL.$JShell$85$MyDataException: Erreur de fichier non trouvé
at .readIssueFile(#89:1)
at .(#93:2)
Propagation des Exceptions
Définition : La propagation des exceptions est le processus par lequel une exception est transmise dans la pile d’appels jusqu’à ce qu’elle soit capturée par un bloc catch approprié ou jusqu’à ce que le programme se termine.
Envelopper une exception : Une méthode peut envelopper une exception dans une autre exception plus spécifique avant de la propager. Cela permet de fournir plus de contexte sur l’erreur.
Traitement total ou partiel : Un bloc catch peut traiter une exception partiellement et ensuite la propager ou en propager une autre. Cela permet de gérer certaines parties de l’erreur tout en laissant d’autres parties être gérées par des niveaux supérieurs.
Exception capturée : Erreur enveloppée dans method1
REPL.$JShell$78C$CustomException: Erreur enveloppée dans method1
at REPL.$JShell$97C.method1($JShell$97C.java:58)
at REPL.$JShell$94B.test($JShell$94B.java:56)
at REPL.$JShell$99.do_it$($JShell$99.java:54)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at io.github.spencerpark.ijava.execution.IJavaExecutionControl.lambda$execute$1(IJavaExecutionControl.java:95)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: REPL.$JShell$95D$CauseException: Erreur dans method2
at REPL.$JShell$96C.method2($JShell$96C.java:55)
at REPL.$JShell$97C.method1($JShell$97C.java:56)
... 9 more
Avantages de la Propagation des Exceptions
Séparation des préoccupations : Permet de séparer le code de gestion des erreurs du code de logique métier.
Réutilisabilité : Facilite la réutilisation des méthodes sans avoir à dupliquer le code de gestion des erreurs.
Lisibilité : Améliore la lisibilité du code en réduisant le nombre de blocs try-catch imbriqués.
Assertions
Les assertions sont des outils de vérification utilisés pour tester les hypothèses faites par le programmeur.
Elles permettent de détecter les erreurs de logique dans le code pendant le développement et les tests.
Utilisation de l’instruction assert :
assert condition : message;
Exemple d’utilisation
Vérification d’une condition simple :
int age =-1;assert age >=0:"L'âge ne peut pas être négatif : "+ age;System.out.println("L'âge est : "+ age);
Par défaut, les assertions sont désactivées à l’exécution.
Pour les activer, utilisez l’option -ea (ou -enableassertions) lors de l’exécution du programme :
java-ea AssertionExample
Meilleures Pratiques
Utiliser des exceptions spécifiques : Capturer des exceptions spécifiques plutôt qu’une exception générique.
Éviter de masquer les exceptions : Ne pas capturer une exception sans la gérer.
Journaliser les exceptions : Toujours journaliser les exceptions pour le débogage.
Nettoyer les ressources : Utiliser le bloc finally ou try-with-resources pour nettoyer les ressources.
Documenter les exceptions : Utiliser Javadoc pour documenter les exceptions qu’une méthode peut lancer.
Exemple 1 : Gestion des Entrées/Sorties de Fichiers
publicclassFileHandler{publicvoidreadFile(String filePath){try(BufferedReader reader =newBufferedReader(newFileReader(filePath))){String line;while((line = reader.readLine())!=null){System.out.println(line);}}catch(FileNotFoundException e){System.err.println("Fichier non trouvé : "+ e.getMessage());}catch(IOException e){System.err.println("Erreur de lecture du fichier : "+ e.getMessage());}}}