Exception

Besoin

Fournir dans un langage une gestion robuste et objet des erreurs .

Analyse

Les exceptions représentent des erreurs. Tout code pouvant signaler des erreurs est susceptible de "lever" ou lancer (throw) des exceptions.

Lorsqu'un code appele un autre code pouvant lever des exceptions, la compilation lui impose de choisir entre :

Conception

Une exception est un objet caractérisé par une classe.

Les exceptions peuvent être catégorisées en fonction de leur :

Implémentation

Toutes les exceptions implémentent l'interface java.lang.Throwable, ce qui permet de les lancer (throw). Elles le font généralement en dérivant de :

Exemples

Des exemples d'exceptions sont :

Exemples de types d'exceptions Contrôlées Non-contrôlées
Sans exception(s) imbriquée(s) Exception, IOException RuntimeException, ArrayOutOfBoundsException, NullPointerException
Avec exception(s) imbriquée(s) SQLException JDOException

Un exemple de classe d'exception est :

public <strong>class</strong> ProblemeFichierException<br> <strong>extends java.lang.Exception</strong> {<br> <strong>public </strong>ProblemeFichierException (String someMessage) {<br> <strong>super </strong>(someMessage);<br> } <br> }

Un exemple code Java pouvant lever une exception est :

public <strong>class</strong> FileLoader {<br> <strong>public static </strong>load (String someFileName) <strong>throws </strong>ProblemeFichierException {<br> <strong>if (</strong>problemeFichier<strong>)<br> throw new </strong>ProblemeFichierException <strong>(<span class="codeString">"</span></strong><span class="codeString">Informations sur le problème<strong>"</strong></span><strong>);</strong><br> } <br> }

Un exemple code Java gérant des exceptions est :

public <strong>class</strong> Main {<br> <strong>public </strong>static void main (String[] args)<br> <strong>throws </strong>java.io.IOException { <span class="codeComment">// Peut propager cette exception</span><br> <strong>try</strong> { <br> FileLoader.load<strong> (<span class="codeString">"</span></strong><span class="codeString">NomFichier.txt<strong>"</strong></span><strong>);</strong><br> <strong>System.out.println </strong>(<span class="codeString">"Le fichier a été ouvert"</span>);<br> }<br> <strong>catch </strong>(ProblemeFichierException erreurFichier) {<br> <strong>System.err.println </strong>(<span class="codeString">"Le fichier n'a pu être ouvert"</span>);<br> } <br> } <br> }

Anti-exemples

Absorption

N'éludez jamais le traitement d'une exception contrôlée. Cela pourra donner l'illusion que votre code tourne, alors que ce n'est pas le cas (erreurs difficiles à trouver) :

try {
faitQuelqueChose();
}
catch (SomeException uneExceptionFatiguanteAGerer) {
}

La gestion minimale d'une exception devrait être :

try {<br> faitQuelqueChose();<br> }<br> catch (SomeException uneExceptionFatiguanteAGerer) {<br> <strong>uneExceptionFatiguanteAGerer.printStackTrace</strong>();<br> }

Dans de rares cas, il pourra être admissible d'asborber une exception. Cependant, même dans ce cas le lecteur doit savoir que c'est normal et pourquoi c'est normal :

try {<br> testErreurAttendue();<br> throw new TestException (<span class="codeString">"Ce test devrait produire une erreur"</span>);<br> }<br> catch (SomeException uneException) {<br> <span class="codeComment"><strong>// Ok, c'était attendu</strong></span><br> }

Utilisation d'une ressource

Vouloir éviter les blocs de gestion d'erreur peut rendre votre code moins robuste et moins clair. Voici un exemple de mauvaise gestion des erreurs lors de l'utilisation d'une ressource (connexion, fichier, etc.):

MaResource maResource = null;
try {
MaResource maResource = getResource();
maResource.utilise();
}
catch (MaResourceException uneErreur) {
// Gère l'erreur
}
finally {
if (maResource != null)
maResource.release();
}

Voici une version claire, qui sait différencier les causes des erreurs, et qui n'a pas besoin de faire des tests inutiles :

<strong>try</strong> { <br> MaResource maResource = getResource();<br> <strong> try</strong> {<br> maResource.utilise();<br> }<br> <strong> catch</strong> (MaResourceException erreurUtilisation) {<br> <span class="codeComment">// Gère l'erreur d'utilisation</span><br> }<br> <strong> finally</strong> {<br> maResource.release(); // Or close, etc.<br> }<br> } <br> <strong>catch</strong> (MaResourceException erreurAllocation) {<br> <span class="codeComment">// Gère l'erreur d'allocation</span><br> }

Mauvaise portée

Une erreur courante est de ne gérer les exceptions qu'au niveau d'une instruction ou au niveau d'un ensemble réduit d'instructions qui pourtant dépendent de la réussite d'instructions précédentes :

SomeResult resultat;
try
{
resultat = effectuePremiereTache();
}
catch (SomeExceptionType unePremiereErreur) {
// Gère le cas d'erreur
}

try {
effectueSecondeTache(resultat); // Quid si resultat n'a pas pu s'initialiser ?
}
catch (AnotherExceptionType uneSecondeErreur) {
// Gère le cas d'erreur
}

Une version correcte inclue dans une bloc try un ensemble d'actions dépendantes :

<strong>try</strong> {<br> SomeResult resultat = effectuePremiereTache();<br> effectueSecondeTache (resultat);<br> }<br> <strong>catch</strong> (SomeExceptionType unePremiereErreur) {<br> <span class="codeComment">// Gère le cas d'erreur</span><br> }<br> <strong>catch</strong> (AnotherExceptionType uneSecondeErreur) {<br> <span class="codeComment">// Gère le cas d'erreur</span><br> }

Perte d'information

L'information étant critique pour comprendre et éventuellement corriger une erreur, ne pas remonter toutes les informations instructives peut être très dommageable pour l'utilisateur ou le code appelant.

Il faut donc éviter du code du genre :

try {
SomeResult result = doFirstTask(parameter);
doSecondTask (result);
}
catch (SomeException someException) {
System.err.println ("Il y a eu un problème"); // Lequel ? Pourquoi ?
}

Ou encore :

try {
SomeResult result = doFirstTask(parameter);
doSecondTask (result);
}
catch (SomeException someException) {
throw new AnotherException(); // Perd l'info de l'erreur initiale
}

À la place, utilisez les exceptions imbriquées (il en existe dans certaines exceptions standards ou dans toutes depuis J2SE 1.4), ou ajouter des champs d'info à remplir dans vos propres classes d'exceptions, ou au pire, fournissez un message d'erreur détaillé :

try {<br> SomeResult result = doFirstTask(parameter);<br> doSecondTask (result);<br> }<br> catch (SomeException someException) {<br> throw <strong>new AnotherException (someException)</strong>;<br> }

Ou :

try {<br> SomeResult result = doFirstTask(parameter);<br> doSecondTask (result);<br> }<br> catch (SomeException someException) {<br> throw <strong>new AnotherException (parameter, result)</strong>;<br> }

Ou :

try {<br> SomeResult result = doFirstTask(parameter);<br> doSecondTask (result);<br> }<br> catch (SomeException someException) {<br> throw <strong>new AnotherException (someException.getClass().getName() + ": " + someException.getMessage(</strong>));<br> }

Traiter les exceptions comme des branches d'algorithmes

Une exception ne doit pas être considérée comme un cas de traitement normal :

public Result method (Object someParam) {
if (someParam != null)
{
return doTask(someParam);
}
else {
throw new MyException ("Parameter " + someParam + " cannot be null");
}
}

Une exception plutôt, comme son nom l'indique, prévenir les cas exceptionnels et être gérées comme des pré/postconditions de traitements :

public Result method (Object someParam) {<br> <strong> if (someParam == null) {<br> throw new MyException ("Parameter " + someParam + " cannot be null");<br> }<br> </strong><br> return doTask(someParam);<br> <strong></strong>}

Notes