Les exceptions #
L’utilité des exceptions #
Pour comprendre l’utilité d’un mécanisme de gestion d’exceptions, commençons par montrer un exemple sans gestion. En C
par exemple, les exceptions n’existent pas.
Voici un pseudo-code qui lit un fichier contenant des données altimétriques et calcule la moyenne des altitudes.
|
|
Dans cet exemple, plusieurs problèmes pourraient survenir:
- le fichier n’existe pas ou sa lecture est impossible,
- le contenu contient des valeurs non numériques impossibles à convertir en double,
- une division par zéro est effectuée (le fichier ne contient peut-être aucune altitude),
- …
Les langages qui n’ont pas d’exceptions retournent généralement une valeur arbitraire, appelée également valeur passerelle, à la place du résultat.
Un serveur http retourne un résultat avec un code indiquant l’erreur éventuelle.
En supposant qu’il soit possible de retourner un couple contenant le résultat et un code d’erreur, voici comment les anomalies devraient être gérées.
|
|
C’est dans ce cas où un mécanisme de gestion d’exceptions est très utile.
Utilité
Un mécanisme de gestion d’exception apporte un avantage considérable : celui de séparer la logique de la gestion des erreurs.
Voici une telle gestion à l’aide d’un pseudo-code.
|
|
Ceci simplifie la logique, mais apporte également beaucoup plus de clarté.
Lever sa propre exception #
Par le passé, j’ai préconisé dans un premier temps l’emploi de RuntimeException
pour lever tous types d’exceptions. En réalité, c’est une mauvaise pratique : il faudrait toujours utiliser une exception la plus spécifique possible en fonction du contexte. La signature de cette méthode n’indique rien d’utile :
Something getMeSomething(int id) throws RuntimeException;
Celle-ci est beaucoup plus intuitive:
Something getMeSomething(int id) throws NoSuchElementException;
Throwable #
En Java, une exception est une classe qui hérite de Throwable
. Le langage propose une multitude d’exceptions organisée autour de trois classes principales dont le schéma est illustré en dessous:
Throwable
: classe de base deError
etException
Error
: cette classe et ses sous-classes sont généralement intraitables- Le problème est tellement grave que l’arrêt du programme est nécessaire (par ex:
VirtualMachineError
,StackOverflowError
,OutOfMemoryError
…)
- Le problème est tellement grave que l’arrêt du programme est nécessaire (par ex:
Exception
: toute la hiérarchie des exceptions qui doivent être traitées, à l’exception de la brancheRuntimeException
.- Exemple d’exceptions qui doivent être traitées obligatoirement (avec un bloc
try/catch
par exemple):IOException
,SQLException
,IllegalClassFormatException
…
- Exemple d’exceptions qui doivent être traitées obligatoirement (avec un bloc
RuntimeException
: hérite deException
. Cette classe et ses sous-classes font partie des unchecked exceptions. C’est-à-dire qu’elles n’ont pas besoin d’être traitées (par ex:NullPointerExcpetion
,ArithmeticException
,IndexOutOfBoundException
,EmptyStackException
…). Il n’est pas nécessaire non plus de les déclarer dans les méthodes et constructeurs qui pourraient les lever.
Exceptions #
Toutes les classes qui héritent d'Exception
, à l’exception de RuntimeException
, doivent être traitées et déclarées.
La méthode test
doit déclarer qu’elle retourne une exception.
|
|
L’appelant doit traiter l’exception obligatoirement
- à l’aide d’un bloc
try/catch
|
|
- en propageant l’exception plus haut
|
|
- ou en la transformant en une nouvelle exception
|
|
Unchecked exceptions (RuntimeException & enfants) #
Comme il a été précisé, les unchecked exceptions
n’ont pas besoin d’être traitées. C’est un bon choix lorsque tous les moyens sont offerts à l’utilisateur pour éviter ce genre de problème. Imaginez s’il fallait utiliser un bloc try/catch
à chaque division.
Les exemples ci-dessous ne nécessitent pas obligatoirement de gestion.
|
|
Il est possible d’éviter ces erreurs de manière traditionnelle.
|
|
Conseil
Pensez également à offrir des fonctionnalités qui permettent aux utilisateurs de vérifier l’état d’un objet avant de faire une opération dangereuse. Vous pouvez également adopter un style déclaratif qui est discuté dans un chapitre choisi du cours.
Traitement des exceptions #
try-catch
#
Le traitement se fait à l’aide d’un bloc try/catch
.
|
|
Si le traitement des exceptions est le même, il est possible de les regrouper:
|
|
Un bloc finally
peut être utilisé. C’est le cas pour les ressources qui doivent être fermées dans tous les cas. Voici un (vieil) exemple pour la lecture de fichier:
|
|
Clause try-with-resources
et Autoclosable
#
Heureusement, depuis Java 8, la gestion se simplifie à l’aide l’instruction try-with-resources
. Notez les parenthèses après le try
. Ceci nous évite de devoir créer un objet null
au préalable et nous évite également de fermer les ressources explicitement. Ceci est souvent mal fait, oublié ou compliqué. Cette instruction fonctionne pour tous les objets qui héritent de l’interface AutoClosable
.
|
|
Réalisez vos propres exceptions #
Vous pouvez directement hériter votre classe de l’exception la plus adéquate.
|
|
Emploi d’exceptions existantes #
Avant de créer vos exceptions, vérifiez toujours si une exception appropriée existe. Sinon, réalisez les vôtres. Rappelez-vous de toujours être spécifique.
Evitez également de traiter un cas trop général.
|
|
Préférez indiquer quel type d’exception vous tentez de traiter.
|
|
Pareil lorsque vous levez des exceptions. Evitez un cas trop général.
Something getMeSomething(int id) throws RuntimeException;
Préférez une exception adaptée. Dans l’extrait ci-dessous, si un élément peut être inexistant par exemple.
// si l'élément peut être inexistant par exemple
Something getMeSomething(int id) throws NoSuchElementException;