Déboguer pas à pas
Points d’arrêt, pas à pas et inspection des variables.
Objectifs
À la fin de cette leçon, vous saurez :
- poser un point d'arrêt et figer l'exécution à l'endroit voulu ;
- distinguer continue, step over et step into — et choisir le bon ;
- inspecter variables locales et pile d'appels pendant l'arrêt ;
- choisir entre console.log ciblé et débogueur complet.
🔗 Pour vous rafraîchir la mémoire : lire une stack trace
Deux outils complémentaires
Le console.log stratégique reste l'outil n°1 : rapide, sans configuration, suffisant pour 80 % des cas. Le débogueur prend le relais quand la logique s'enchevêtre : il exécute le programme ligne par ligne en montrant l'état réel de chaque variable à chaque instant.
console.log ciblé → « quelle est la valeur ICI ? » (question ponctuelle)
débogueur → « comment l'état global arrive-t-il LÀ ? » (enquête complète)
Le console.log bien fait mérite lui-même une technique. Comparez :
console.log(total); // vague : « 12 »... c'est lequel des trois logs ?
console.log("fin boucle i=" + i + ", total=" + total); // étiqueté, situable
console.log({ i, total }); // objet étiqueté automatiquement par les clés
Étiquetez toujours vos logs : un log sans contexte est un indice sans scène de crime.
Les cinq gestes du débogueur
Cinq commandes couvrent 95 % des besoins :
| Commande | Effet | Quand l'utiliser |
|---|---|---|
| breakpoint | fige l'exécution à une ligne donnée | avant la zone suspecte |
| continue | reprend jusqu'au prochain point d'arrêt | sauter les parties saines |
| step over | exécute la ligne courante sans y entrer | par défaut |
| step into | entre dans la fonction appelée par la ligne | quand vous soupçonnez CETTE fonction |
| call stack + variables | montre où l'on est et avec quelles valeurs | à chaque arrêt |
Règle pratique : step over par défaut ; step into seulement sur intention. Entrer dans chaque fonction appelée (y compris celles de Node !) est le meilleur moyen de se perdre.
Démonstration avec node inspect
Node intègre un débogueur en ligne de commande. Prenons ce fichier, somme-bug.js :
function cumulJusqua(limite) {
let total = 0;
for (let i = 1; i <= limite; i++) {
total += i;
}
return total;
}
console.log(cumulJusqua(5)); // attendu : 15
console.log(cumulJusqua(0)); // attendu : 0... vérifions vraiment
Lançons sous débogueur :
node inspect somme-bug.js
Session commentée :
debug> sb(2) ← setBreakpoint(2) : point d'arrêt ligne 2
debug> c ← continue : démarrage, arrêt au point d'arrêt
debug> repl ← ouvre un interpréteur DANS l'état courant du programme
> limite ← la variable existe ici : on lit sa valeur réelle
5
> total ← encore initialisé ?
0
> Ctrl+C ← sortie du repl, retour au debug>
debug> n ← next : exécuter UNE ligne, puis se refiger
debug> watch("total") ← suivre `total` en continu
debug> watchers ← afficher les valeurs surveillées
debug> c ← reprendre jusqu'à la fin
Ce qui vient de se passer, conceptuellement : le programme est vivant mais figé ; vous marchez dedans, lisez les variables comme elles sont vraiment (pas comme vous les imaginez), avancez d'un pas, revérifiez. Chaque écart entre valeur attendue et valeur observée resserre le cercle autour de la cause.
Dans VS Code (recommandé dès que possible) : panneau Run and Debug, clic dans la marge pour poser un breakpoint, puis F10 = step over, F11 = step into, F5 = continue. Variables locales et call stack restent affichées en permanence à gauche — exactement la table ci-dessus, en continu.
Ce qu'on cherche en pas à pas
Un débogage efficace suit un plan, dérivé directement de « crash ≠ cause » :
- Poser le breakpoint avant l'endroit suspect — pas dessus : il faut voir arriver la mauvaise valeur, pas constater son existence ;
- Avancer et comparer : chaque variable a-t-elle la valeur attendue à cet instant ?
- Au premier écart attendu/réel : stop — la cause vit sur la ou les lignes précédentes ;
- Corriger UNE chose, relancer depuis zéro, vérifier.
L'étape 2 mérite d'être outillée mentalement : notez au brouillon les valeurs attendues (« i devrait valoir 1 puis 2 puis 3 ; total devrait faire 1, 3, 6 »). Sans attendu écrit, l'enquête dérive.
Quand le bug disparaît sous le débogueur
Phénomène célèbre — l'effet Heisenbug : tout semble correct pendant l'enquête, le bug ne se manifeste qu'en exécution normale. Causes fréquentes :
- le code exécuté n'est pas le code sauvegardé (fichier non rechargé, mauvais dossier) ;
- le timing change sous débogueur (cas asynchrone — chapitre 17) : les pauses artificielles masquent les courses de vitesse.
Premiers réflexes alors : vérifier que vous exécutez bien le fichier modifié (pwd, chemin), puis remplacer les points d'arrêt par des logs étiquetés — ils ne perturbent pas le rythme d'exécution.
Exercice
Soit ce programme, compteur-bug.js :
function decompte(n) {
let resultat = "";
for (let i = n; i > 0; i--) {
resultat += i;
}
return resultat;
}
const obtenu = decompte(3);
console.log(obtenu === "321"); // true ? false ?
- Prédisez la valeur affichée, par écrit, avant tout test.
- Décrivez votre plan d'enquête : où posez-vous le breakpoint, quelles variables surveillez-vous, à quels tours ?
- Tracez les valeurs de
ietresultattour par tour. - Maintenant testez
decompte(10)avec le même code. Votre prédiction de la question 1 tient-elle toujours ? Que révèle cet exercice ?
Résumé
- Breakpoint AVANT la zone suspecte ; cause toujours en amont du symptôme.
- Step over par défaut, step into sur intention, continue entre zones saines.
- À chaque arrêt : lire variables locales ET call stack, comparer aux valeurs attendues notées.
- Logs étiquetés pour les questions ponctuelles ; débogueur pour l'enquête d'état complet.
- Si le bug fuit sous le débogueur : soupçonner le fichier exécuté, passer aux logs.
Correction disponibleCherchez d’abord par vous-même.Voir la correction
Correction
Réponses détaillées
Question 1. Pour n = 3 : resultat += i concatène "3", puis "32", puis "321" → comparaison avec "321" → true. Si votre prédiction écrite disait autre chose, l'écart prédit/réel est exactement ce que ce chapitre vous apprend à exploiter.
Question 2. Breakpoint sur la ligne resultat += i (pas sur le return — trop tard). Surveillance : i et resultat, à chaque tour, contre un attendu noté au brouillon ("3", "32", "321").
Question 3.
tour 1 : i = 3, resultat = "" + 3 = "3"
tour 2 : i = 2, resultat = "32"
tour 3 : i = 1, resultat = "321"
sortie : i = 0 → condition fausse → return "321"
Chaque tour colle à l'attendu : aucun écart → aucune cause à chercher → le code fait bien ce que son texte dit.
Question 4. Avec decompte(10) :
console.log(decompte(10)); // "10987654321"
Le résultat est probablement différent de votre intuition initiale : beaucoup prédisent un comportement « chiffre par chiffre » et sont surpris que 10 s'écrive d'un bloc. La leçon de cet exercice dépasse le code : notre certitude sur un petit exemple ne garantit rien pour les autres entrées — d'où la discipline du chapitre 5 (tester les cas limites) et celle-ci : noter ses prédictions AVANT d'exécuter, pour transformer chaque surprise en apprentissage plutôt qu'en confusion.