Type d'erreur
Lorsqu'on écrit un programme, on est confronté à différents types d'erreurs. On peut distinguer trois types d'erreurs : les erreurs de compilation, les erreurs d'exécution et, les plus terribles et difficiles à détecter, les erreurs logiques.
Avant de découvrir ces différents types d'erreurs, revenons encore une fois sur les différentes étapes pour avoir un programme Java. La première étape consiste à écrire le programme. On l'écrit sous forme d'un fichier texte : il s'agit du code source. Ensuite, on va le compiler grâce au compilateur Java (javac
) ; cette phase est appelée compilation.
Une fois la phase de compilation terminée, on obtient un nouveau fichier binaire qui contient le programme Java sous une forme compréhensible par la machine : le bytecode (ou code machine). On va pouvoir exécuter le programme en utilisant la machine virtuelle Java (java
). La phase durant laquelle le programme est exécuté est appelée exécution.
La figure 10 présente les différentes étapes à franchir pour obtenir, à partir du code source du programme Java, le code machine qui pourra être exécuté par l'ordinateur.
Erreur de compilation
Les erreurs de compilation sont détectées durant la phase de compilation. De quel genre d'erreurs s'agit-il ? Écrire un programme Java, c'est comme écrire un roman ou un poème : on ne peut pas écrire n'importe quoi, n'importe comment; il y a des règles à respecter.
Pour rédiger un texte dans une langue donnée, on respecte les règles d'orthographe et de syntaxe (communément appelées grammaire) de cette langue. De même, pour écrire un programme informatique, on se conforme à la syntaxe propre au langage utilisé. Celle-ci détermine si un code source est valide ou non dans le langage utilisé.
On a déjà vu certaines règles de la syntaxe de Java, comme par exemple que les identificateurs ne peuvent pas contenir le caractère &
, que toute accolade ouvrante doit avoir une accolade fermante qui lui correspond, que la méthode main
doit se trouver dans une classe, etc.
D'autres erreurs, qui ne sont pas des erreurs de syntaxe, sont également trouvées par le compilateur. On en a aussi déjà rencontrées quelques-unes. Par exemple, une variable doit être déclarée avant d'être utilisée, on ne peut pas modifier la valeur d'une constante initialisée, etc.
Quelques exemples
Voyons ensemble quelques exemples d'erreurs de compilation, ainsi que le message d'erreur qui sera produit par le compilateur Java. Tout d'abord, certaines lignes de code du programme (plus exactement chaque instruction simple ; on reviendra sur cette notion au chapitre 2) doivent se terminer par un point-virgule ( ;
). La figure 11 montre un programme qui s'appelle CompilerError1
et qui déclare une nouvelle variable de type int
et de nom taille
. La variable est directement initialisée à la valeur entière 5. Malheureusement, on a oublié un point-virgule à la fin de la ligne 5.
Si on tente de compiler ce programme, voici ce qui apparait comme erreur de compilation (qui est ici une erreur de syntaxe) :
> javac CompilerError1.java CompilerError1.java:5: ';' expected int taille = 5 ^ 1 error
Prenons un autre exemple. On a vu précédemment qu'en Java, toute variable possède un type et qu'on ne peut y placer que des valeurs qui correspondent à ce type. Si vous tentez d'affecter une valeur à une variable et que le type de la valeur ne correspond pas au type de la variable, vous aurez aussi une erreur de compilation.
Dans le programme de la figure 12, on met une valeur de type boolean
dans une variable de type double
. Si on tente de compiler ce programme, voici ce qui apparait comme erreur de compilation (qui n'est cette fois-ci pas une erreur de syntaxe) :
> javac CompilerError2.java CompilerError2.java:5: incompatible types found : boolean required: double double d = true; ^ 1 error
Notez bien le message d'erreur, il s'agit d'un problème de types non-compatibles. En effet, comme on verra plus loin, il est par exemple possible d'affecter une valeur de type int
à une variable de type double
.
Une erreur de compilation se produit également lorsqu'on tente de modifier la valeur d'une constante qui est déjà initialisée, ou lorsqu'on tente d'utiliser une variable qui n'a pas encore été initialisée. Si une erreur de compilation se produit, le bytecode n'est pas produit par le compilateur. La figure 13 reprend les erreurs de compilation que l'on connait déjà avec le message d'erreur correspondant.
Message d'erreur | Description |
---|---|
';' expected |
Point-virgule oublié à la fin d'une ligne de code |
incompatible types |
Affectation d'une valeur de type XXX à une variable de type YYY |
cannot assign a value to a final variable XXX |
Affectation d'une valeur à une constante XXX déjà initialisée |
variable XXX might not have been initialized |
Utilisation de la variable XXX non-initialisée |
not a statement |
Ligne de code invalide |
illegal start of expression |
Le compilateur trouve quelque chose qui ne devrait pas se trouver à cet endroit |
cannot find symbol |
Le compilateur trouve un mot XXX qu'il ne comprend pas |
La dernière erreur présentée dans le tableau peut se produire dans beaucoup de situations différentes : vous avez oublié de déclarer une variable, vous vous êtes trompé dans un mot réservé (par exemple duble i;
au lieu de double i;
), etc.
Erreur d'exécution
Une fois que le programme compile sans erreur, le bytecode est produit par le compilateur et on peut l'exécuter. On passe dans la phase d'exécution et le programme peut parfois se terminer de manière anormale et brutale : une erreur d'exécution s'est produite.
Ce genre d'erreur se produit lorsque le programme effectue une opération illégale. La figure 14 montre un exemple qui va produire une erreur d'exécution. Dans ce programme, on tente de diviser une valeur par zéro. On verra les opérateurs au chapitre suivant, mais sachez qu'on utilise /
pour la division. La compilation de ce programme se passe sans erreur, et c'est au moment de l'exécuter qu'une erreur se produit :
> javac RuntimeError.java > java RuntimeError Exception in thread "main" java.lang.ArithmeticException: / by zero at RuntimeError.main(RuntimeError.java:8)
Un autre exemple d'erreur d'exécution se produit lorsque vous tentez d'exécuter un programme qui ne contient pas de méthode public static void main (String[] args)
. L'erreur est de type Exception in thread "main" java.lang.NoSuchMethodError: main.
Les erreurs d'exécution peuvent arriver, mais il est possible d'écrire nos programmes de manière défensive en utilisant les tests qu'on verra au chapitre 3 et le mécanisme d'exception qu'on verra au chapitre 8. Un programme qui évite autant que possible les erreurs d'exécution est dit robuste.
Il est possible de s'assurer qu'un programme ne s'arrêtera jamais suite à une erreur d'exécution en utilisant par exemple des techniques de vérification formelle. Ces techniques sont essentiellement utilisées dans le monde académique ou pour des applications spécifiques ; elles ne sont pas encore très répandues dans le monde industriel (sauf dans certains cas particuliers, comme à la NASA, par exemple, où les systèmes développés sont des systèmes critiques. Ils utilisent par exemple l'outil Java™ PathFinder qui permet d'explorer toutes les exécutions possibles d'un programme Java pour détecter des situations non désirées (http://babelfish.arc.nasa.gov/trac/jpf)).
Erreur logique
Les erreurs logiques sont sans conteste les erreurs les plus difficiles à détecter et à corriger. Cependant, elles existent et apparaissent souvent sans même que le programmeur ne les remarque. Et pour cause, le programme compile et s'exécute sans aucun problème, mais ne fait pas ce pour quoi il a été conçu. Le programme de la figure 15 calcule et affiche la surface d'un rectangle, surface dont la valeur est donnée par la formule $longueur \times largeur$, rappelez-vous de vos primaires.
Le programmeur a fait une faute en calculant la surface, il aurait dû mettre L * l
. Peut-être la touche Shift du clavier était-elle un peu bloquée par des miettes de croissant. En tout cas, le programme compile et s'exécute sans erreur, mais il affiche 25, alors qu'il devrait afficher 35.
Pour détecter ces erreurs, il faut utiliser des programmes appelés débuggeur qui permettent d'exécuter le programme instruction par instruction tout en permettant de voir, entre autre, le contenu de toutes les variables à tout moment.
Une autre manière de faire est de décrire précisément ce que le programme doit faire, puis d'exécuter une série de tests qui couvrent tous les cas possibles et vérifient que, dans tous les cas, le programme fait bien ce qu'il faut (le framework JUnit est utilisé pour effectuer des tests de manière systématique pour des programmes Java (http://www.junit.org)). On verra au chapitre 7 le concept de spécification qui permet de définir ce que doit faire un programme.