Savoir déboguer ses programmes

 

I.1 - Problèmes détectés à la compilation

Toutes les erreurs détectées par le compilateur (javac) génèrent des messages d'erreur. Il suffit donc de savoir interpréter ces messages. Nous commençons par rappeler sept erreurs fréquentes avant d'effectuer un exercice.

Erreur fréquente 1 : emplacement ou nom de fichier incorrect

Lorsque le fichier à compiler avec javac n'existe pas, par exemple parce qu'il a été sauvé dans un autre répertoire, ou avec un nom différent (problèmes de majuscules et minuscules, extension de fichier .java absente, etc.), javac génère un message d'erreur. Par exemple, si l'on essaye de compiler le fichier Prog.java alors que le fichier n'existe pas, le message suivant est affiché :
error: cannot read: Prog.java
pour indiquer que le fichier Prog.java ne peut pas être lu (cannot read), puisqu'il n'existe pas.

Erreur fréquente 2 : oublier un point-virgule

Une erreur fréquente consiste à oublier un point-virgule pour terminer une instruction, comme dans l'exemple ci-dessous :
class Prog {

    public static void main(String[] args) {

        System.out.println("Bonjour")
        System.out.println("Monsieur");

    }
    
}
qui génère le message suivant à la compilation :
Prog.java:5: ';' expected
        System.out.println("Bonjour")
                                     ^
Dans cet exemple, javac vous informe qu'un ; est attendu (expected) à la ligne 5 du programme Prog.java (Prog.java:5: ';' expected), et vous indique l'emplacement du caractère manquant par un accent circonflexe (^).

Note : cette erreur aurait pu être trouvée avant la compilation en indentant le programme : les lignes 5 et 6 n'auraient alors plus été alignées.

Erreur fréquente 3 : oublier une parenthèse

Le programme ci-dessous :
class Prog {

    public static void main(String[] args) {

        System.out.println((3+2);

    }
    
}
génère le même type d'erreur, qui indique qu'un caractère est manquant:
Prog.java:5: ')' expected
        System.out.println((3+2);
                                ^
Cette fois, une parenthèse est attendue à la ligne 5 du programme Prog.java (Prog.java:5: ')' expected).

Attention : si la ligne 5 est remplacée par :

        System.out.println(3+2));
où il manque une parenthèse ouvrante, alors le message d'erreur devient :
Prog.java:5: ';' expected
        System.out.println(3+2));
                               ^
comme s'il manquait un point-virgule ! En fait, puisqu'une seule parenthèse ouvrante est présente, une seule parenthèse fermante est nécessaire. L'instruction, terminée, devrait donc être suivie d'un point-virgule.

Erreur fréquente 4 : oublier une accolade

Le programme ci-dessous :
class Prog {

    static double f(double x) {
	
		return 2*x;
		
		
    public static void main(String[] args) {

        System.out.println(3+2);

    }
    
}
génère le message suivant :
Prog.java:8: illegal start of expression
    public static void main(String[] args) {
    ^
qui vous informe qu'il est interdit de commencer l'expression indiquée (public) à la ligne 8 (Prog.java:8: illegal start of expression). Ceci vient du fait que l'accolade qui doit terminer la fonction static double f(double x) a été oubliée, et que javac vous soupçonne de vouloir écrire la fonction main à l'intérieur de la fonction f. De tels messages (illegal start of expression) signalent souvent des erreurs dans la structure du programme : accolades ou parenthèses oubliées ou mélangées, etc.

Note : ici aussi, cette erreur aurait pu être trouvée avant la compilation en indentant le programme. Répétons le une troisième fois : il est fortement conseillé d'indenter vos programmes !

Erreur fréquente 5 : oublier de déclarer une variable

Le programme ci-dessous :
class Prog {

    public static void main(String[] args) {

        for (i=0;i<10;i++) System.out.println(i);

    }
    
}

génère le message suivant :
Prog.java:5: cannot find symbol
symbol  : variable i
location: class Prog
        for (i=0;i<10;i++) System.out.println(i);
             ^
qui vous indique que la déclaration du symbole i ne peut pas être trouvée dans Prog (Prog.java:5: cannot find symbol, suivi de symbol : variable i puis de location: class Prog). Il faut spécifier le type de i, par exemple en remplaçant la ligne 5 par :
        for (int i=0;i<10;i++) System.out.println(i);

Erreur fréquente 6 : erreur de syntaxe

Ce message d'erreur peut être également affiché lorsqu'une erreur de syntaxe est détectée. Par exemple, si la ligne 5 du programme précédent est remplacée par :
        for (int i=0;ii<10;i++) System.out.println(i);
alors javac nous indique que le symbole ii n'est pas déclaré (dans la condition ii<10). Sans doute, l'auteur du programme voulait écrire i<10.

Note : lorsque l'erreur de syntaxe est dans un mot-clé java (par exemple en remplaçant for par fur ou bien public par publik), des messages d'erreurs plus compliqués peuvent être affichés, liés au rôle du mot-clé. Cependant, ces erreurs peuvent souvent être détectées avant la compilation, en indentant le programme, ou parce que le mot-clé est affiché avec une couleur incorrecte dans l'éditeur de texte.

Erreur fréquente 7 : oublier un return

Le programme ci-dessous :
class Prog {

    static double doubleValeur(double x) {
	
        if (x>0) return 2*x;
		
    }
	
    public static void main(String[] args) {

        System.out.println(doubleValeur(2.0));

    }
    
}
génère le message suivant :
Prog.java:7: missing return statement
        }
        ^
qui vous indique qu'un return devrait être présent avant que la fonction doubleValeur ne se termine à l'accolade ligne 7 (Prog.java:7: missing return statement). En effet, la fonction doubleValeur doit retourner un double quoi qu'il arrive (c'est-à-dire même si x<=0).

Attention : java n'est pas nécessairement capable de comprendre que tous les cas ont été envisagés (même si c'est évident pour vous, humain). Par exemple, le programme suivant :

class Prog {

    static double doubleValeur(double x) {
	
        if (x>0) return 2*x;
        else if (x<=0) return -2*x;
		
    }
	
    public static void main(String[] args) {

        System.out.println(doubleValeur(2.0));

    }
    
}
génère la même erreur !

Exercice 1

Copiez le programme ci-dessous dans un fichier Palindromes.java, essayez de le compiler (javac Palindromes.java), et corrigez les erreurs qui surviennent à la compilation (on exécutera le programme à l'exercice 2).

Note 1 : il est conseillé de corriger quelques erreurs, puis de sauver et de recompiler, et de recommencer jusqu'à ce qu'il n'y ait plus d'erreurs (au lieu d'essayer de tout corriger en une seule fois). Dans la pratique, il est d'ailleurs fortement conseillé d'essayer de compiler fréquemment au lieu d'attendre que les erreurs s'accumulent.

Note 2 : il est possible que quelques messages d'erreurs ne fassent pas partie des messages indiqués plus haut. Dans ce cas, ces messages indiqueront généralement le type d'erreur (une phrase en anglais) et l'emplacement de l'erreur (un numéro de ligne et un accent circonflexe).

Note 3 : ce programme a été volontairement mal indenté, et nous vous conseillons de l'indenter avant de le déboguer. Pour cela, vous pourrez appliquer (par exemple) les trois règles suivantes :

  • on va à la ligne après un point-virgule (sauf dans un for)
  • on n'écrit rien après une accolade ouvrante
  • on n'écrit rien avant et après une accolade fermante
  • class Palindrome 
        static boolean estPale(String mot){
      int longueur = mot.length();booleen resultat = true;
            i = 0;
            while (i < longueur/ 2 && resultat){ resultat = (mot.charAt(i) == mot.charAt(longueur - 1 -i));
                ii++;return result;}
    
    public stati String main(Strings[] args){
     string[] affichage=null;
       affichage[1]=" est un palindrome"
     affichage[2]=" n'est pas un palindrome";
       
            for (int i=O;i <= arg.length;l++){ System.out.print((args[i]);
     
     
                if (estPal(args[i])) { System.out.print1n(affichage[1]);
      else{ System.out.println(affichage[2]);
                }
           }}
           return "OK, je termine.";
        }
    }

    I.2 - Problèmes détectés par java

    Les problèmes détectés à l'exécution peuvent être classés en deux catégories : les problèmes détectés par java (le programme s'arrête), et les problèmes détectés par l'utilisateur (le programme ne fait pas ce qui était prévu). Commençons par les problèmes détectés par java.

    Erreur fréquente 1 : Prog.class est absent

    Pour pouvoir être exécuté, un programme doit avoir été compilé. Par exemple, pour exécuter Prog, le fichier Prog.class doit être présent dans le répertoire. Dans le cas contraire, la commande :
    java Prog
    génère le message suivant :
    Exception in thread "main" java.lang.NoClassDefFoundError: Prog
    qui vous indique que Prog.class est absent (NoClassDefFoundError: Prog : Prog.class n'est pas défini).

    Note : cette erreur intervient souvent lorsque l'on confond le nom du fichier qui contient le programme (avec une extension .java) et le nom du programme lui-même.

    Erreur fréquente 2 : la fonction main est absente

    Pour pouvoir être exécuté, un programme Prog doit contenir une fonction public static void main(String[] args). C'est la fonction recherchée par java lorsque l'on entre en ligne de commande :
    java Prog
    Ainsi, le programme suivant :
    class Prog {
    
        public static double doubleValeur(double x) {
        
            return 2.0*x;
            
        }
        
    }
    compile parfaitement, mais génère le message suivant lorsqu'on essaye d'exécuter Prog :
    Exception in thread "main" java.lang.NoSuchMethodError: main
    qui vous indique que la fonction main est absente (NoSuchMethodError: main).

    Note : la fonction main doit avoir exactement la signature indiquée plus haut. Ainsi, la fonction main ci-dessous :

        public static double main(String[] chaines) {
        
            return 2.0;
            
        }
    
    génère le même message d'erreur à l'exécution (note : l'erreur vient du type de retour double, et non du nom chaines donné à la référence à un tableau String[] passée en argument).

    Erreur fréquente 3 : utilisation d'une référence qui vaut null

    Une référence doit être une référence à quelque chose avant de pouvoir être utilisée. Ainsi, le programme suivant :
    class Prog {
    
        public static void main(String[] args) {
        
            int[] t=null;
            System.out.println(t.length);
            
        }
        
    }
    génère une erreur à l'exécution :
    Exception in thread "main" java.lang.NullPointerException
            at Prog.main(Prog.java:6)
    
    qui vous indique que la fonction main de Prog (Prog.main) essaye à la ligne 6 (Prog.java:6) d'utiliser une référence qui vaut null (NullPointerException).

    Dans cet exemple, t peut contenir une référence à un tableau d'entiers, mais est en fait une référence à rien (c'est-à-dire une référence qui vaut null). Dans ce cas, l'auteur a oublié de créer le tableau, et t.length n'a donc pas de sens.

    Erreur fréquente 4 : indice hors limites

    En java, un tableau de taille n est indicé de 0 à n-1. Si un programme essaye d'accéder à un élément du tableau d'indice i situé en dehors de ces limites, alors java génère un message d'erreur. Par exemple, le programme suivant :
    class Prog {
    
        public static void main(String[] args) {
        
            int[] t=new int[10];
            for (int i=0;i<=t.length;i++) t[i]=i*2;
            
        }
        
    }
    génère une erreur à l'exécution :
    Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10
            at Prog.main(Prog.java:6)
    
    qui vous indique que la fonction main de Prog (Prog.main) essaye à la ligne 6 (Prog.java:6) d'accéder à l'élément d'indice 10 d'un tableau (: 10), alors que cet indice est "hors limites" (ArrayIndexOutOfBoundsException: 10). Dans cet exemple, l'erreur vient de la condition i<=t.length.

    Exercice 2

    Reprenez le programme Palindromes.java que vous avez corrigé à l'exercice 1 (c'est-à-dire la version qui ne génère plus d'erreurs à la compilation).

    En exécutant le programme de la manière suivante :

    java Palindromes laval bonjour
    
    le programme est censé déterminer si laval et bonjour sont des palindromes. La sortie du programme devrait être :
    laval est un palindrome
    bonjour n'est pas un palindrome
    
    Corrigez les erreurs rencontrées lors de l'exécution.

    I.3 - Problèmes détectés par l'utilisateur

    Les problèmes les plus difficiles à corriger sont souvent ceux qui ne sont détectés ni par javac, ni par java, mais par l'utilisateur (lorsque le programme ne fait pas ce que l'on attend de lui). Dans ce cas, une première stratégie consiste à utiliser des affichages (par exemple avec la fonction System.out.println()) pour essayer de comprendre le déroulement du programme.

    Exercice 3

    Sauvez le programme ci-dessous :
    class Factorielle {
    
        public static void main(String[] args) {
    
            System.out.print("Entrez un nombre n =  ");
            int n = TC.lireInt();
            int facto = 1;
            int i;
            for (i=n+1; i>=0; i=i+1);{
    		
                facto = facto * i;
                
            }
            
            System.out.println(n+"! =  " + facto);
            
        }
    }
    dans un fichier Factorielle.java, compilez le, puis exécutez le. Le programme demande un entier à l'utilisateur et doit retourner la factorielle de cet entier. En utilisant des affichages (par exemple, en affichant la valeur de facto à chaque itération de la boucle for, ou en affichant des messages indiquant l'étape courante du programme, comme un System.out.println("avant la boucle for") juste avant la boucle for), essayez de comprendre le fonctionnement du programme et corrigez le.

    Rappel : on peut forcer un programme à s'arrêter en appuyant sur les touches CTRL et c en même temps.

    Exercice 4

    Sauvez le programme ci-dessous :
    class Valeur {
    
        int v;
    
        Valeur(int v) {
        
            this.v=v;
            
        }
        
    }
    
    class Echange {
    
        public static void main(String[] args) {
    
            int i;
            
            // creation du tableau
            
            Valeur[] t=new Valeur[10];
            for (i=0;i<10;i++) t[i]=new Valeur(i);
            
            // inversion du tableau
            
            for (i=0;i<10;i++) {
            
                Valeur temp=t[i];
                t[i]=t[t.length-1-i];
                t[t.length-1-i]=temp;
                
            }
            
            // affichage du tableau
            
            for (i=0;i<10;i++) System.out.println(t[i].v);
            
        }
        
    }
    dans un fichier Echange.java, compilez le (javac Echange.java), puis exécutez le (java Echange). Le programme est censé afficher tous les entiers de 9 à 0, mais affiche tous les entiers de 0 à 9, comme si l'inversion du tableau n'avait pas été effectuée. En vous aidant d'un dessin, essayez de comprendre le comportement du programme et corrigez le (on utilisera la notation habituelle pour les références, les tableaux, etc.).

    Page créée par Stéphane Redon.

    Dernière mise à jour : 07/06/2009