Comment déboguer un programme qui se trompe
Déboguer, c’est trouver pourquoi un programme fait le mauvais travail, puis le corriger. Le plus dur n’est pas de taper la correction. C’est de savoir quoi regarder parmi cent choses.
Deviner et changer le code au hasard peut prendre un après-midi. Vérifier une chose à la fois prend quelques minutes. Cette leçon montre comment.
À la fin de cette leçon
Remarque
Je peux trouver un bogue en lisant l’erreur et en vérifiant une chose à la fois.
Mots-clés
| Mot | Ce que ça veut dire |
|---|---|
| Bogue | une faute dans un programme qui lui fait faire le mauvais travail |
| Reproduire | refaire apparaître le bogue, de la même façon, à volonté |
| Trace d’erreur | la liste des appels que Python affiche quand un programme s’arrête sur une erreur |
| Hypothèse | une idée sur la cause qu’une seule vérification peut réfuter |
| Affichage de contrôle | une ligne qui montre une valeur, pour voir ce que le programme contient vraiment |
| Coupe en deux | vérifier le milieu de ce qui reste, pour écarter la moitié à chaque fois |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
D’abord, faire apparaître le bogue à volonté
On ne corrige pas ce qu’on ne voit pas. Avant de changer quoi que ce soit, trouvez une entrée qui provoque le bogue à chaque fois, et gardez-la.
Ici, un petit programme calcule la moyenne des ventes d’un fichier, une ligne par jour. Les données ont un trou. Créez d’abord le dossier et le fichier.
Créer un dossier pour la leçon
mkdir -p ~/debogageAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Entrer dans le dossier
cd ~/debogageAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le fichier des ventes
printf 'lun 120\nmar 80\nmer n/d\njeu 200\n' > ventes.txtAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le programme du rapport
printf 'def lire_ligne(ligne):\n jour, montant = ligne.split()\n return jour, int(montant)\n\n\ndef moyenne(lignes):\n total = 0\n for ligne in lignes:\n jour, montant = lire_ligne(ligne)\n total = total + montant\n return total / len(lignes)\n\n\nprint(moyenne(open("ventes.txt").read().splitlines()))\n' > rapport.pyAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Le lancer et le voir s’arrêter
python3 rapport.pyAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Lire une trace d’erreur en partant du bas
Une trace d’erreur liste les appels ouverts quand le programme s’est arrêté. L’appel le plus récent est en bas : lisez donc en partant du bas.
La dernière ligne dit ce qui s’est passé : ValueError, avec un message. La ligne File juste au-dessus dit où : un fichier, un numéro de ligne et une fonction. Les lignes plus haut disent par où le programme est passé.
Lister seulement les lignes File
python3 rapport.py 2>&1 | grep "File"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Vérifier une chose, puis la suivante
L’erreur montre où le programme s’est arrêté, pas pourquoi la donnée était fausse. Faites donc une hypothèse : une idée qu’une seule vérification peut réfuter.
Ici, l’idée est qu’une ligne des données n’est pas un nombre. Cherchez-la dans le fichier. Si la vérification ne trouve rien, l’idée était fausse, et c’est aussi un progrès.
Chercher le texte dans les données
grep -n "n/d" ventes.txtAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Une correction rapide peut cacher le bogue suivant
La ligne 3 dit mer n/d : il n’y avait pas de chiffre de vente ce jour-là. Le changement le plus rapide est de le compter pour 0. Essayez, puis relancez le programme.
Compter un chiffre absent pour 0
sed -i 's| return jour, int(montant)| if montant == "n/d":\n return jour, 0\n return jour, int(montant)|' rapport.pyAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Le relancer
python3 rapport.pyAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Sans erreur, le bogue peut rester
Le programme s’est exécuté et a affiché 100.0. Or la moyenne des trois jours qui ont un chiffre, 120, 80 et 200, est 133.3. Le programme se trompe et ne dit rien.
Il n’y a plus de trace à lire. Ajoutez un affichage de contrôle à l’endroit où l’on additionne, et regardez ce que le programme contient vraiment.
Afficher chaque jour au moment de l’ajouter
sed -i 's| total = total + montant| print(jour, montant)\n total = total + montant|' rapport.pyAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Le lancer et lire chaque ligne
python3 rapport.pyAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Couper le problème en deux
Le programme a quatre étapes. Vérifiez d’abord celle du milieu. Si le total est juste, la faute est après. S’il est faux, la faute est avant. Chaque vérification écarte la moitié.
C’est ainsi qu’une équipe trouve lequel de mille changements a cassé une fonction.
Exemple travaillé : vérifier les étapes
| Étape | Ce qu’elle devrait contenir | Ce qu’elle contenait | Verdict |
|---|---|---|---|
| Lire le fichier | 4 lignes | 4 lignes | Juste |
| Lire chaque montant | 120, 80, n/d, 200 | 120, 80, 0, 200 | Le 0 est un nombre inventé |
| Additionner | 400 | 400 | Juste |
| Diviser par les jours comptés | 400 / 3 = 133.3 | 400 / 4 = 100.0 | Un jour sans chiffre a été compté |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Réécrire le programme, avec la correction
printf 'def lire_ligne(ligne):\n jour, montant = ligne.split()\n if not montant.isdigit():\n return None\n return jour, int(montant)\n\n\ndef moyenne(lignes):\n total = 0\n nombre = 0\n for ligne in lignes:\n paire = lire_ligne(ligne)\n if paire is not None:\n total = total + paire[1]\n nombre = nombre + 1\n return total / nombre\n\n\nprint(round(moyenne(open("ventes.txt").read().splitlines()), 1))\n' > rapport.pyAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Lancer le programme corrigé
python3 rapport.pyAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Une erreur fréquente
Attention
Beaucoup changent le code avant de connaître la cause, puis changent autre chose quand cela échoue. Après cinq changements, personne ne sait lequel a aidé. Vérifiez d’abord, changez une seule chose, puis relancez.
Pour aller plus loin : trouver le changement fautif
Une équipe garde chaque changement dans Git. Quand un bogue apparaît, elle sait qu’une ancienne version était bonne et que la dernière est mauvaise. La commande git bisect demande de tester la version du milieu, et coupe l’historique en deux jusqu’à ce qu’il ne reste qu’un changement.
Écrivez les étapes pour 1000 changements. Combien de versions testez-vous au plus ?