Si le langage que vous utilisez le permet, orientez votre politique
de gestion des erreurs de votre programme autour des exceptions.
Celles-ci permettent en effet de dissocier clairement le code lié
au déroulement normal de votre application et celui dédié
à la gestion des erreurs. Cela influera positivement sur
la lisibilité de vos sources.
Nous avions précedemment étudié les exceptions
avec le
langage Javascript, mais sachez également qu'elles sont
aussi disponibles avec le C++, le récent C#, ou dans un autre
registre, Cold Fusion, alors que Php les adoptera bientôt.
Les origines des exceptions
Une exception se produit, comme son nom l'indique, lorsqu'une situation
exceptionnelle survient pendant l'exécution de l'un de vos
programmes Java. Elles se situent donc en aval de la phase de compilation.
Nous allons voir cependant que le compilateur joue un rôle
important pour certaines classes susceptibles de lancer un type
particulier d'exceptions.
"Situation exceptionnelle", c'est vite dit. Il n'est
pourtant pas si "difficile" pour un programmeur de générer
une exception : accéder à l'indice d'un tableau non
défini ou une division par zéro (liste hélas
non exhaustive) suffisent à produire respectivement les exceptions
suivantes : "IndexOutOfBoundsException" et "ArithmeticException".
Si les programmeurs sont parfois désespérés
à la vue du nombre "d'alertes" ou autres "warning"
plus ou moins sévères générés
par leur compilateur, ces derniers permettent d'augmenter la fiabilité
future de l'application lorsqu'elle s'exécutera. Comprendre
par là que plus un compilateur est sévère au
départ, plus le risque de rencontrer des problèmes
à l'exécution ensuite est réduit. Les exceptions
sont alors un bon moyen de détecter des problèmes
éventuels.
Une exception est en fait un objet crée lorsqu'une
situation anormale se produit pendant l'exécution de votre
programme. Les exceptions sont toujours issues de la classe Throwable.
Deux sous-classes sont alors notables, elles sont dérivées
de la classe Throwable et se nomment "Error" et
"Exception".
Bien qu'il soit possible d'intercepter les classes d'exceptions
de la classe Error (ThreadDeath, LinkageError et VirtualMachineError)
notre pouvoir à leur égard est limité et concerne
des conditions que nous ne sommes pas censés gérer,
en tout cas pas couramment (arrêt d'un thread en cours d'exécution,
création d'un objet pour un type de classe inexistant, grave
problème sur la machine virtuelle Java...).
En conséquence, nous allons davantage nous focaliser sur
les exceptions représentées par des sous-classes de
la classe "Exception" (paquetage java.lang). Non seulement
il est possible d'intercepter ces exceptions mais il est également
obligatoire de le faire pour certaines d'entre elles.
En effet, on peut diviser en deux catégories les exceptions
représentées par les classes dérivées
de la classe "Exception" : les exceptions dont la classe
de base est "RuntimeException" (dérivée
de "Exception"), et les autres. Il n'est pas obligatoire
de gérer les exceptions qui pourraient se produire avec les
premières, par contre le compilateur refusera systématiquement
de... compiler si une méthode capable de provoquer une exception
représentée par une sous-classe de la classe "Exception"
n'est pas gérée (par un bloc de type try / catch comme
nous le verrons) ou tout du moins signalée comme susceptible
de générer ce type d'exceptions.
Analysons de plus près ce dernier point à l'aide de
deux exemples :
(ZeroDivide.java)
public class ZeroDivide
{
static public void main(String[] args)
{
int
a = 3;
int
b = 0;
System.out.println("Resultat
de la division : " + a/b);
}
}
Ce bout de code se compile sans problème, par contre c'est
au niveau de l'exécution qu'une exception apparaît
:
Exception in thread "main" java.lang.ArithmeticException:
/ by zero at ZeroDivide.main(ZeroDivide.java:8)
"java.lang.ArithmeticException" représente le
nom de la classe de l'objet reçu.
Nous verrons plus tard comment capturer cette exception et
éviter ainsi d'attirer l'attention des utilisateurs de votre
programme sur la ligne 8 de votre code source...
Si le premier exemple s'est compilé sans problème
c'est que la sous-classe ArithmeticException
dérive de "RuntimeException".
Voyons ce qu'il se passe quand ça n'est pas le cas :
(TaperTouche.java)
public class TaperTouche
{
static public void main(String[] args)
{
System.out.println("Tapez
une touche pour terminer le programme");
System.in.read();
}
}
La compilation est impossible...
TaperTouche.java:6: unreported exception java.io.IOException;
must be caught or
declared to be thrown
... En effet la méthode "read()"
de l'objet "in" est susceptible de lever une exception
"d'entrée / sortie" (IOException).
Passons maintenant à la correction
de ces deux exemples.
Les blocs "try" / "catch"
Découvrons brièvement ce qu'il faut retenir d'essentiel
à propos de la construction "try... catch... finally"
(nous reviendrons plus en détail sur celle-ci dans un tutoriel
ultérieur sur le même sujet).
Reprenons notre premier exemple. Il compilait mais générait
(levait) une exception que nous souhaiterions désormais faire
disparaître (capturer). Voici donc le même code
source doté des corrections adéquates :
(ZeroDivide.java)
public class ZeroDivide
{
static public void main(String[] args)
{
int
a = 3;
int
b = 0;
try
{
System.out.println("Resultat
de la division : " + a/b);
System.out.println("Instructions
suivant la division...");
}
catch(ArithmeticException
e)
{
System.out.println("Une
exception s'est produite ! (ArithmeticException)");
}
System.out.println("Instructions
qui suivent le bloc catch...");
}
}
Le bloc "try" cerne le bout de code susceptible
de générer l'exception que nous cherchons à
capturer. Le bloc "catch" doit suivre immédiatement
le bloc "try". Celui-ci est facultatif, il peut en effet
laisser sa place à un bloc "finally" (que nous
détaillerons dans un autre tutoriel) qui lui permet d'exécuter
systématiquement, qu'une exception se produise ou non, le
code qu'il contient (libération de ressources, fermeture
de fichier par exemple).
Le bloc catch permet de définir une action suite à
un type précis d'exception. Un catch se définit par
le type d'exception qu'il peut capturer. Plusieurs "catch"
peuvent se suivre, chacun pouvant capturer un type d'exception défini
par le programmeur.
Si vous exécutez cette version corrigée de l'exemple
"ZeroDivide", l'exception n'a pas disparue mais elle a
été "capturée" et provoque l'affichage
de notre message définit dans le catch
correspondant.
De plus, il a été rajouté ici deux affichages,
destinés à mieux cerner le comportement du programme
lors de la levée de l'exception. Lorsqu'on exécute
cette version modifiée de "ZeroDivide.java" on
obtient à l'écran :
Une exception s'est produite ! (ArithmeticException)
Instructions qui suivent le bloc catch...
Lorsque l'exception
est levée le bloc try est interrompu, le programme exécute
les instructions situées dans le bloc catch correspondant,
puis passe à l'instruction suivant le bloc catch.
Voyons maintenant les modifications à apporter au second
exemple :
(TaperTouche.java)
import java.io.IOException;
public class TaperTouche
{
static public void main(String[] args)
{
System.out.println("Tapez
une touche pour terminer le programme");
try
{
System.in.read();
}
catch(IOException
e)
{
System.out.println("Une
IOException a été détectée !");
}
}
}
Même principe que précedemment, avec en plus l'import
de la classe "java.io.IOException"
dont est issue l'exception susceptible de se produire.
Ce tutoriel n'est pas exhaustif sur la gestion des erreurs
en Java. Nous aborderons donc dans un prochain
article d'autres techniques relatives aux exceptions, nous verrons
notamment comment lever nos propres exceptions.
|