Le guide pour apprendre à débugger du code

Trouver et corriger une défaillance dans du code s'apprend avec l'expérience, mais voici de quoi vous faire gagner du temps !

Article publié le 08/03/2022, dernière mise à jour le 19/09/2023

L'une des premières frustrations dans le développement est de rester bloqué pendant pendant de longues minutes (heures) sur une défaillance de notre code, parfois appelé "bug".

Si vous voulez savoir pourquoi je ne recommande pas l'utilisation du terme bug, je vous recommande la lecture de mon précédent article.

Toujours est-il que c'est une étape incontournable dans la vie d'un développeur ou d'une développeuse et que la recherche et la correction de dysfonctionnements dans notre code est une compétence très importante pour gagner en autonomie.

J'ai donc essayé de vous construire un guide pour apprendre à trouver les bugs, les comprendre et les corriger, étape par étape :

Étape 1 : Comprendre

Lire les erreurs

Dans la majorité des cas, lire l'erreur (la lire vraiment) et prendre le temps de l'analyser va vous permettre de la corriger très rapidement car le système (compilateur, interpréteur, environnement) va essayer de vous donner un maximum d'infos sur la défaillance en question.

Exemple SQL

SELECT * FROM movie WHERE title=`Mad Max`;
--ERROR 1054 (42S22) at line 1: Unknown column 'Mad Max' in 'where clause'

Ici tout nous est donné, la ligne, la position exacte et le type d'erreur (1054 -> Syntaxe) et la raison de l'erreur (Unknown column). En lisant attentivement, Mad Max devrait être une valeur, or elle est encadrée par des `backticks` au lieu des 'simple quotes' attendues pour les valeurs.

MySQL l'interprète donc comme une colonne, d'où l'erreur !

Exemple Java

Exception in thread "main" java.nio.file.NoSuchFileException: players.dat
    at sun.nio.fs.WindowsException.translateToIOException(Unknown Source)
    at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source)
    // ... more stack trace
    at java.nio.file.Files.readAllLines(Unknown Source)
    at java.nio.file.Files.readAllLines(Unknown Source)
    at Exceptions.getPlayers(Exceptions.java:12) <-- Exception arises in getPlayers() method, on line 12
    at Exceptions.main(Exceptions.java:19) <-- getPlayers() is called by main(), on line 19

Même chose en Java, où lorsqu'une exception est lancée, toute la stacktrace (la pile d'exécution) ayant conduit à l'erreur est affichée. Autant d'infos peuvent faire peur, mais en réalité si l'on décortique tout ça, toutes les informations nécessaires sont là.

À priori la méthode "getPlayers" devait ouvrir un fichier qui n'existe pas (ou a été déplacé, renommé), ce fichier porte le nom de "players.dat".

Exemple Javascript

Uncaught TypeError: undefined is not a function example_app.js:7
ExampleApp.initialize example_app.js:7
(anonymous function)

Comme en Java, l'interpréteur JS va nous donner la stacktrace qu'il faut examiner, et découvrir quelle fonction appelée peut se retrouver comme étant undefined dans notre script.

Une dépendance mal chargée, un objet vide, etc... Au moins vous avez les clés pour savoir où chercher !

Pas d'erreur ?

Il se peut que le dysfonctionnement de votre logiciel n'entraine pas d'erreur mais seulement un état invalide de ce dernier, alors c'est que votre logiciel ne spécifie pas suffisamment ses attentes et ne vérifie pas assez les données en entrée et en sortie.

Vous devrez alors ajouter des conditions, des try-catch afin de créer vous même vos propres erreurs et éviter que l'état non-valide reçu ne soit pas redirigé dans le chemin d'exécution classique de votre application.

Reproduire

Arriver à reproduire une défaillance est indispensable car c'est le seul moyen que vous aurez pour récupérer suffisamment d'informations afin de trouver une solution, mais également de vérifier, tester, que votre solution fonctionne comme prévu.

Pour reproduire un bug, il faut arriver à retrouver l'état dans lequel était votre logiciel au moment où ce dernier est apparu.

Cet état est représenté par :

  • La version de votre code à un instant T
  • L'environnement dans lequel tourne votre logiciel (Version de l'OS, matériel, RAM et espace disque disponible, etc...)
  • Les opérations précédemment effectuées
  • Les données passées à votre code lors de la défaillance.

En rassemblant un maximum de ces données, vous devriez être en mesure de remettre l'application (autant que possible) dans le même état invalide et reproduire le bug.

Pas de reproduction

Dans le cas où vous n'arriveriez pas à reproduire la défaillance, cela signifie que vous n'avez pas mis en place suffisamment d'outils pour analyser votre application.

Pour cela, pensez à mettre en place :

  • Un versioning précis de votre code (indispensable)
  • Un système de sauvegarde des exceptions
  • Un ensemble de logs pour votre application
  • Un monitoring de votre environnement

Et attendez que la défaillance se représente, vous aurez alors suffisamment d’infos pour la détecter et la reproduire sans problème.

Reproduction intermittente

Si jamais vous n'arrivez pas à reproduire le "bug" de manière régulière mais seulement par intermittence (même en injectant les mêmes données), alors le problème est sûrement induit par un facteur externe à votre logiciel, par votre environnement (des autorisations manquantes, le réseaux, l'espace disque, l'horodatage du système, etc...).

Isoler

Ce n’est pas parce que vous avez réussi à reproduire un dysfonctionnement que vous comprenez ce qui cloche exactement. Dans certains cas, le problème est si inhabituel que l’on arrive même pas à  mettre le doigt sur une possible cause.

Pour avancer dans votre  recherche, vous allez devoir isoler le dysfonctionnement.

Pour l’instant vous avez simplement une application défectueuse, mais l’objectif est d’isoler le plus précisément le fichier, la classe, la fonction, ou l’opération qui pose problème.

Pour cela, suivez pas à pas le fil d’exécution de votre application (avec un débugueur, des exceptions ou à défaut une série de logs) afin de remonter à l’opération précise où le résultat de sortie ne correspond pas au comportement théorique et aux données d’entrée.

Dans l’idéal, vous vous retrouverez avec une ligne précise qu’il vous faudra analyser et inspecter de manière rigoureuse.

Analyser / Cadrer

Maintenant que vous avez réussi à isoler le problème, vous allez pouvoir essayer de le tordre pour en apprendre un peu plus, et surtout essayer de multiplier le nombre d’états défectueux que vous arrivez à générer.

Un exemple : Une fonction qui transforme un entier ne renvoie pas le bon résultat lorsque l’on lui passe en paramètre le chiffre 0. Pour cadrer ce dysfonctionnement et comprendre ces limites, on pourra tester à cette fonction des paramètres étant plus susceptibles de provoquer une erreur  comme : 0.1, -0.1, 1, -1, null, etc...

Si vous arrivez à cadrer votre problème, il sera plus facile de poser des hypothèses éclairées.

Faire des hypothèses

Les hypothèses sont des questions ciblées auxquelles vous n’avez pas encore la réponse, mais dont l’une d’entre-elle devraient vous menez à la bonne piste, soit en analysant votre code, en cherchant vous-même la réponse ou en allant demander de l’aide.

Exemple : lorsque je m’inscris, je ne reçois pas d’emails, même si toutes mes données sont envoyées dans le SDK du service d’envoi de mail.

Hypothèses et moyens de les vérifier :

  • L’email arrive peut-être dans les spams => Vérifier moi-même
  • L’email n’est peut-être jamais envoyé => Vérifier sur la plateforme du service d’envoie de mail
  • Le service est peut-être temporairement down => Idem
  • Est-ce que j’utilise la bonne méthode du SDK => Documentation
  • Pourquoi je ne reçois pas d’erreur ? => Forum d'entraide
  • ...

Étape 2 : Rechercher

Expliquer

Pour toutes les hypothèses auxquelles vous n’aurez pas pu répondre par vous même, il faudra faire des recherches, ou demander de l’aide. Pour les recherches vous aurez besoin des meilleurs mots-clés/requêtes, et pour de l’aide vous aurez besoin de l’explication la plus claire et complète du problème possible.

L’une des meilleures méthodes pour expliquer un problème correctement est de passer par une phase de « rubber duck debugging ». Cela consiste à expliquer votre problème à un canard en plastique posé sur votre bureau.

Cette méthode vous forcera à expliquer votre problème de manière claire, en enlevant les détails inutiles et vous permettra même parfois de trouver le solution par vous même, car votre cerveau pourra détecter les informations incohérentes que vous pourriez verbaliser à haute voix.

Dans le cas contraire, cela vous permettra de préparer votre discours pour demander de l’aide à un.e collègue, sur un forum, etc... et justement, c’est le prochain sujet.

Trouver de l'aide

Dans l'ordre, vous pouvez trouver de l'aide :

  • Dans la documentation
  • Sur Github (pour les projets open-source)
  • StackOverflow/Forums
  • Google/Blogs/Sites
  • Groupes d'entraides/Slacks/Discords
  • Collègues/Pairs/Experts

Si vous voulez savoir pourquoi cet ordre spécifique, et quels sont les bonnes manières d'aborder la recherche d'aide, je vous invite à lire cet article :

Étape 3 : Corriger

Implémenter

Est-ce que j'ai vraiment besoin de détailler cette partie ?

Copiez-collez (non). Réécrivez, adaptez et surtout comprenez ce que vous faites !

Tester

On a vite tendance à arrêter la phase de « debugging » après l’implémentation du correctif, mais en réalité les phases qui suivent sont les plus importantes, et celles qui apportent le plus de valeur à votre code.

Testez manuellement votre modification avec plusieurs cas de tests valides et invalides est un minimum, mais pour être serein sur la pérennité de votre logiciel, mettez en place des tests automatisés.

Ajuster / Nettoyer

Une fois que votre code est implémenté et testé, vous pouvez passer à la phase de nettoyage : enlever les logs, éventuellement du code superflu, etc...

Mais vous pouvez aussi rendre votre code plus performant, plus lisible, plus adapté, tout en étant sûr d'éviter toute régression grâce à vos tests !

Documenter

Il arrive qu'une erreur soit simplement une faute d'inattention, alors cette étape n'aura pas forcément de grand intérêt.

Mais si le dysfonctionnement vient d'un problème de calcul, d'une imprécision dans la documentation d'un outil, d'une bizarrerie de la logique métier dans laquelle vous travaillez, le mieux est de documenter votre solution.

Ça peut-être un simple commentaire, des informations dans un README ou bien une entrée dans une base de connaissances interne à l'entreprise, mais pensez à ne pas sauter cette étape, elle vous sauvera peut-être la vie dans 6 mois.

J'espère que cet article vous aura été utile, et à bientôt sur le blog.


Elisa Ventur sur Unsplash

Vous avez terminé l'article ?

Commentaires (0)

pour laisser un commentaire

Aucun commentaire pour l'instant