Diagnostiquer les bugs
Une discipline pour les bugs difficiles. Ne sautez des phases que lorsque cela est explicitement justifié.
Lors de l'exploration de la base de code, lisez CONTEXT.md (s'il existe) pour obtenir un modèle mental clair des modules pertinents, et vérifiez les ADR dans la zone que vous touchez.
Rédaction
Cette compétence vous demande d'afficher des commandes, des sorties et des artefacts capturés. Rédigez d'abord chaque secret : écrivez <REDACTED> à sa place. Construisez des boucles avec des variables d'environnement, afin que l'identifiant reste dans l'environnement plutôt que dans ce que vous montrez. Les artefacts capturés transportent des en-têtes d'authentification : ne citez que les lignes qui portent le signal.
Si la sortie rédigée ne suffit pas à diagnostiquer le bug, dites-le et demandez à l'utilisateur.
Phase 1 : Construire une boucle de rétroaction
C'est la compétence. Tout le reste est mécanique. Si vous avez un signal serré de réussite/échec pour le bug (un qui passe au rouge sur ce bug), vous en trouverez la cause ; la bissection, le test d'hypothèses et l'instrumentation ne font que le consommer. Si vous n'en avez pas, regarder le code ne vous sauvera pas.
Consacrez un effort disproportionné ici. Soyez agressif. Soyez créatif. Ne renoncez pas.
Façons d'en construire une, dans cet ordre approximatif
- Test échouant à la jointure qui atteint le bug : unitaire, intégration, e2e.
- Script Curl / HTTP contre un serveur de développement en cours d'exécution.
- Invocation CLI avec une entrée de fixture, en comparant la sortie standard avec un instantané de référence.
- Script de navigateur sans tête (Playwright / Puppeteer) qui pilote l'interface utilisateur et vérifie le DOM/la console/le réseau.
- Rejouer une trace capturée. Enregistrez une vraie requête réseau / charge utile / journal d'événements sur le disque ; rejouez-la via le chemin de code de manière isolée.
- Harnais jetable. Démarrez un sous-ensemble minimal du système (un service, dépendances simulées) qui exécute le chemin de code du bug avec un seul appel de fonction.
- Boucle de propriété / fuzz. Si le bug est 'sortie parfois erronée', exécutez 1000 entrées aléatoires et recherchez le mode d'échec.
- Harnais de bissection. Si le bug est apparu entre deux états connus (commit, jeu de données, version), automatisez 'démarrer à l'état X, vérifier, répéter' pour que vous puissiez
git bisect run. - Boucle différentielle. Exécutez la même entrée sur l'ancienne version par rapport à la nouvelle version (ou deux configurations) et comparez les sorties.
- Script bash avec humain dans la boucle. Dernier recours. Si un humain doit cliquer, guidez-le avec
scripts/hitl-loop.template.shpour que la boucle reste structurée. La sortie capturée vous est renvoyée.
Construisez la bonne boucle de rétroaction, et le bug est résolu à 90 %.
Resserrez la boucle
Considérez la boucle comme un produit. Une fois que vous avez une boucle, resserrez-la :
- Puis-je la rendre plus rapide ? (Mettre en cache la configuration, ignorer l'initialisation non pertinente, réduire la portée du test.)
- Puis-je rendre le signal plus net ? (Vérifier sur le symptôme spécifique, pas 'n'a pas planté'.)
- Puis-je la rendre plus déterministe ? (Figer l'heure, initialiser le générateur aléatoire, isoler le système de fichiers, geler le réseau.)
Une boucle instable de 30 secondes n'est guère meilleure qu'aucune boucle ; une boucle déterministe de 2 secondes est serrée, un super-pouvoir de débogage.
Bugs non déterministes
L'objectif n'est pas une reproduction propre mais un taux de reproduction plus élevé. Bouclez le déclencheur 100×, parallélisez, ajoutez du stress, réduisez les fenêtres temporelles, injectez des délais. Un bug qui se reproduit à 50 % est débogable ; à 1 %, il ne l'est pas, alors continuez à augmenter le taux jusqu'à ce qu'il soit débogable.
Quand vous ne pouvez vraiment pas construire de boucle
Arrêtez-vous et dites-le explicitement. Listez ce que vous avez essayé. Demandez à l'utilisateur : (a) l'accès à l'environnement qui reproduit le problème, (b) un artefact capturé rédigé (fichier HAR, vidage de journal, vidage mémoire, enregistrement d'écran avec horodatage), ou (c) l'autorisation d'ajouter une instrumentation de production temporaire. Ne pas procéder à des hypothèses sans boucle.
Critère d'achèvement : une boucle serrée qui passe au rouge
La phase 1 est terminée lorsque la boucle est serrée et capable de passer au rouge : vous pouvez nommer une commande (un chemin de script, une invocation de test, une commande curl) que vous avez déjà exécutée au moins une fois (montrez l'invocation et sa sortie, rédigée), et qui est :
- Capable de passer au rouge : elle conduit le chemin de code réel du bug et vérifie le symptôme exact de l'utilisateur, elle peut donc passer au rouge sur ce bug et au vert une fois corrigé. Pas 's'exécute sans erreur' ; elle doit pouvoir attraper ce bug spécifique.
- Déterministe : même verdict à chaque exécution (bugs instables : un taux de reproduction élevé et figé, comme ci-dessus).
- Rapide : secondes, pas minutes.
- Exécutable par l'agent : vous pouvez l'exécuter sans surveillance ; un humain dans la boucle uniquement via
scripts/hitl-loop.template.sh.
Si vous vous surprenez à lire du code pour construire une théorie avant que cette commande n'existe, arrêtez-vous : sauter directement à une hypothèse est l'échec exact que cette compétence empêche. Pas de commande capable de passer au rouge, pas de phase 2.
Phase 2 : Reproduire + minimiser
Exécutez la boucle. Regardez-la passer au rouge lorsque le bug apparaît.
Confirmez :
- La boucle produit le mode d'échec que l'utilisateur a décrit, pas un échec différent qui se trouve être à proximité. Mauvais bug = mauvais correctif.
- L'échec est reproductible sur plusieurs exécutions (ou, pour les bugs non déterministes, reproductible à un taux suffisamment élevé pour déboguer).
- Vous avez capturé le symptôme exact (message d'erreur, sortie incorrecte, temps lent) afin que les phases ultérieures puissent vérifier que le correctif le résout réellement.
Minimiser
Une fois qu'il est rouge, réduisez la reproduction au plus petit scénario qui reste encore rouge. Supprimez les entrées, les appelants, la configuration, les données et les étapes une par une, en réexécutant la boucle après chaque suppression, et ne gardez que ce qui est essentiel à l'échec.
Pourquoi s'en donner la peine : une reproduction minimale réduit l'espace des hypothèses dans la phase 3 (moins de pièces mobiles à suspecter) et devient le test de régression propre dans la phase 5.
Terminé lorsque chaque élément restant est essentiel : en supprimer un seul fait passer la boucle au vert.
Ne continuez pas avant d'avoir reproduit et minimisé.
Phase 3 : Émettre des hypothèses
Générez 3 à 5 hypothèses classées avant d'en tester une seule. La génération d'une seule hypothèse ancre sur la première idée plausible.
Chaque hypothèse doit être falsifiable : énoncez la prédiction qu'elle fait.
Format : 'Si est la cause, alors fera disparaître le bug / l'aggravera.'
Si vous ne pouvez pas énoncer la prédiction, l'hypothèse est une impression : rejetez-la ou précisez-la.
Affichez la liste classée à l'utilisateur avant de tester. Ils ont souvent une connaissance du domaine qui reclasse instantanément ('nous venons de déployer une modification sur #3'), ou connaissent des hypothèses qu'ils ont déjà éliminées. Point de contrôle peu coûteux, gain de temps important. Ne vous bloquez pas là-dessus ; continuez avec votre classement si l'utilisateur est absent.
Phase 4 : Instrumenter
Chaque sonde doit correspondre à une prédiction spécifique de la phase 3. Modifiez une variable à la fois.
Préférence d'outil :
- Inspection par débogueur / REPL si l'environnement le permet. Un point d'arrêt bat dix journaux.
- Journaux ciblés aux limites qui distinguent les hypothèses.
- Ne jamais 'tout journaliser et grepper'.
Étiquetez chaque journal de débogage avec un préfixe unique, par exemple [DEBUG-a4f2]. Le nettoyage à la fin devient un seul grep. Les journaux non étiquetés survivent ; les journaux étiquetés meurent.
Branche de performance. Pour les régressions de performance, les journaux sont généralement faux. Au lieu de cela : établissez une mesure de référence (harnais de timing, performance.now(), profileur, plan de requête), puis bisectez. Mesurez d'abord, corrigez ensuite.
Phase 5 : Corriger + test de régression
Écrivez le test de régression avant le correctif, mais uniquement s'il existe une jointure correcte pour celui-ci.
Une jointure correcte est celle où le test exerce le vrai modèle de bug tel qu'il se produit au site d'appel. Si la seule jointure disponible est trop superficielle (test d'un seul appelant alors que le bug nécessite plusieurs appelants, test unitaire qui ne peut pas reproduire la chaîne qui a déclenché le bug), un test de régression là donne une fausse confiance.
S'il n'existe pas de jointure correcte, cela en soi est la constatation. Notez-le. L'architecture de la base de code empêche le bug d'être verrouillé. Signalez-le pour la phase suivante.
Si une jointure correcte existe :
- Transformez la reproduction minimisée en un test échouant à cette jointure.
- Regardez-le échouer.
- Appliquez le correctif.
- Regardez-le passer.
- Réexécutez la boucle de rétroaction de la phase 1 sur le scénario original (non minimisé).
Phase 6 : Nettoyage
Requis avant de déclarer terminé :
- La reproduction originale ne se reproduit plus (réexécutez la boucle de la phase 1)
- Le test de régression passe (ou l'absence de jointure est documentée)
- Toute instrumentation
[DEBUG-...]a été supprimée (greple préfixe) - Les prototypes jetables ont été supprimés (ou déplacés vers un emplacement de débogage clairement marqué)
- L'hypothèse qui s'est avérée correcte est indiquée dans le message du commit / de la PR, afin que le prochain débogueur apprenne