Une méthode systématique
Reproduire, isoler, tester une hypothèse à la fois.
Objectifs
À la fin de cette leçon, vous saurez :
- appliquer une démarche en six étapes à tout bug, du symptôme au correctif vérifié ;
- éviter le piège du « changement au hasard » ;
- isoler un problème par bissection quand la zone suspecte est trop grande ;
- documenter chaque bug résolu pour capitaliser.
🔗 Pour vous rafraîchir la mémoire : lire une stack trace · déboguer pas à pas
Le piège : modifier au hasard
Face à un bug, l'instinct débutant est de changer quelque chose, relancer, changer autre chose... Vingt minutes plus tard : trois « corrections » ajoutées, bug toujours là, code devenu incompréhensible. Ce mode opératoire échoue pour une raison précise : il ne produit aucune information sur la cause. Chaque changement au hasard détruit même de l'information — l'état d'origine n'existe plus.
La méthode ci-dessous transforme chaque étape en information acquise.
Les six étapes
1. REPRODUIRE : trouver les gestes exacts qui déclenchent le bug
2. LIRE : le message d'erreur complet, jusqu'au bout
3. LOCALISER : stack trace, logs étiquetés, réduire la zone suspecte
4. HYPOTHÉSER : formuler UNE cause précise et testable
5. TESTER : corriger UNIQUEMENT cette hypothèse, relancer
6. VÉRIFIER : cas normal + cas limites + rien d'autre n'a cassé
Étape 1 — Reproduire
Un bug qu'on ne sait pas provoquer ne peut ni être corrigé ni être vérifié comme corrigé. Notez la séquence exacte : données d'entrée, commandes, contexte. Si le bug n'apparaît qu'une fois sur dix, c'est déjà une information — probable lien avec un état ou un timing. Le rêve : une commande unique qui reproduit (node test.js), car elle rendra l'étape 5 instantanée.
Étapes 2 et 3 — Lire puis localiser
Le message d'erreur a été écrit pour vous (leçon 6-01) ; la stack trace relie symptôme et chemin (leçon 6-02). Ensuite, des logs étiquetés réduisent le cercle :
console.log({ entree, taille: entree.length }); // la donnée arrive-t-elle intacte ?
console.log({ totalApresBoucle: total }); // le calcul sort-il juste ?
Chaque log répond à une question binaire (« juste avant ou juste après cette ligne ? ») : trois ou quatre questions suffisent généralement à encercler la ligne fautive.
Étape 4 — Une hypothèse à la fois
Formulez-la en phrase testable, avec conséquence prévisible : « si i démarre à 0 au lieu de 1, alors la première note comptée double ». Deux hypothèses testées simultanément produisent des résultats ininterprétables — si ça marche ensuite, que savez-vous ? Rien : quelle correction a opéré ?
Étape 5 — Un seul changement
Modifiez exactement ce que prévoit l'hypothèse, relancez, comparez à la prédiction :
- conforme → cause confirmée, corriger proprement ;
- non conforme → annulez le changement avant d'en tester un autre. C'est la discipline qui manque dans le mode « au hasard » : sans annulation, on empile des modifications dont plus personne ne connaît le rôle.
Étape 6 — Vérifier large
Bug disparu ≠ programme réparé. Rejouez :
- le cas nominal ;
- les cas limites (vide, zéro, négatif, un seul élément) ;
- les usages précédents de la fonction réparée (rien d'autre n'a-t-il cassé ?).
Exemple complet, six étapes en conditions réelles
function moyenne(notes) {
let total = 0;
for (let i = 0; i <= notes.length; i++) {
total += notes[i];
}
return total / notes.length;
}
console.log(moyenne([12, 14, 16])); // NaN — attendu : 14
1. REPRODUIRE : node moyenne.js → NaN à chaque exécution ✓
2. LIRE : aucun crash ! Donc famille logique (chapitre 6-01) :
pas de stack trace, il faut fabriquer l'information.
3. LOCALISER : log étiqueté dans la boucle :
console.log({ i, note: notes[i], total });
... tour i=3 affiche { i: 3, note: undefined, total: NaN }
→ le mal apparaît AU tour 3.
4. HYPOTHÉSER : « la boucle visite un index de trop (<= au lieu de <),
donc notes[3] vaut undefined et contamine total ».
Prédiction : remplacer <= par < redonnera exactement 14.
5. TESTER : un seul caractère modifié. Relance : 14 ✓ conforme.
6. VÉRIFIER : moyenne([]) → ? moyenne([-4]) → -4 ?
moyenne([12,14,16]) réutilisée ailleurs → inchangée ✓
Notez combien l'étape 2 a orienté tout le reste : « pas de crash » signifiait « pas de trace gratuite, il faut instrumenter ».
Isoler par bissection
Quand la zone suspecte reste trop grande, divisez-la :
100 lignes suspectes ?
→ court-circuiter la moitié (commenter, ou injecter une valeur factice connue bonne)
bug toujours là ? → il vit dans l'autre moitié
bug disparu ? → il vit dans cette moitié
→ recommencer sur la moitié restante
7 essais suffisent pour passer de 100 lignes à ~1
La variante la plus rentable sépare données vs traitement : remplacez l'entrée douteuse par une valeur connue bonne (const entree = [1, 2, 3]; // TEMP) :
- le bug persiste → le traitement est en cause ;
- le bug disparaît → ce sont les données ; remontez leur fil jusqu'à leur source.
S'appuyer sur Git
Vous versionnerez tout votre travail dès demain (chapitre suivant). Deux usages immédiats pour le débogage :
git diffmontre exactement ce qui a changé depuis le dernier état fonctionnel — statistiquement, le bug y vit souvent ;- annuler proprement une piste abandonnée (
git checkout -- fichier) sans laisser de résidus — l'anti-« accumulation aveugle » par excellence.
Documenter le bug résolu
Trois lignes dans vos notes, pour chaque bug qui vous a coûté plus de dix minutes :
Symptôme : moyenne([12,14,16]) → NaN
Cause : boucle <= visites un index hors limites ; undefined contamine le total
Leçon : tracer le DERNIER tour d'une boucle avant toute autre hypothèse
Chaque bug ainsi noté devient un réflexe permanent. C'est ainsi qu'on devient « celui qui trouve vite » : non par don, mais par bibliothèque personnelle de causes déjà rencontrées.
Exercice
Appliquez la méthode à ce programme sans l'exécuter d'abord :
function filtrerMajeurs(personnes) {
const resultat = [];
for (const personne of personnes) {
if (personne.age > 18) {
resultat.push(personne.nom);
}
}
return resultat;
}
const groupe = [
{ nom: "Ana", age: 19 },
{ nom: "Bob", age: 18 },
{ nom: "Cléo", age: 21 },
];
console.log(filtrerMajeurs(groupe));
// obtenu : ["Ana", "Cléo"] — Bob (18 ans) est absent. Bug ou pas bug ?
- Quelle est l'« étape 0 » de la méthode ici, avant même de reproduire ?
- Le programme plante-t-il ? Quelle famille de problème ?
- Formulez une hypothèse sous forme de phrase testable.
- Proposez la correction unique correspondante, puis décrivez votre étape 6.
Résumé
- Reproduire → lire → localiser → hypothèse unique → un seul changement → vérifier large.
- Chaque étape produit une information ; le changement au hasard n'en produit aucune et en détruit.
- Bissection + injection de valeurs connues : encercler la cause par moitiés successives.
- Git diff localise, git checkout annule.
- Noter chaque bug coûteux : c'est ainsi qu'on devient rapide.
Correction disponibleCherchez d’abord par vous-même.Voir la correction
Correction
Réponses détaillées
Question 1 — l'étape 0 : définir le contrat. « Majeur » veut dire 18 ans inclus ou exclu ? Sans réponse écrite, aucune correction n'est possible — on ne sait même pas s'il y a un bug. Beaucoup de « bugs » sont des spécifications floues ; clarifier le contrat attendu est gratuit et décisif. Supposons ici le contrat légal français : majeur = 18 ans inclus.
Question 2. Aucun crash : erreur logique, la plus silencieuse des trois familles. Pas de stack trace, pas de message — seuls le contrat défini en question 1 et la comparaison obtenu/attendu révèlent quelque chose.
Question 3. Phrase testable : « si je remplace > par >=, alors filtrerMajeurs(groupe) renverra ["Ana", "Bob", "Cléo"] ». La prédiction précède la modification — c'est elle qui rendra le test informatif.
Question 4.
if (personne.age >= 18) { // un seul caractère ajouté
Étape 6 complète :
cas nominal : filtrerMajeurs(groupe) → ["Ana","Bob","Cléo"] ✓ conforme à la prédiction
cas limites : filtrerMajeurs([]) → [] ✓
filtrerMajeurs([{nom:"Zoé", age:-3}]) → [] ✓
régression : les autres fonctions utilisant `groupe` → comportement inchangé ✓
note : Symptôme/Cause/Leçon → « aux bornes d'un seuil, toujours demander :
inclus ou exclu ? »
Si le contrat avait été « strictement plus de 18 », le code était correct et le « bug » vivait dans l'attendu — preuve supplémentaire que l'étape 0 commande tout le reste.