Lire une stack trace
Exploiter une stack trace pour localiser l’origine d’un plantage.
Objectifs
À la fin de cette leçon, vous saurez :
- décoder les trois blocs d'une stack trace Node : ligne fautive, type + message, pile d'appels ;
- identifier fichier, ligne, fonction et cheminement du plantage ;
- distinguer l'endroit du crash de sa cause — la distinction qui fait les bons débogueurs.
🔗 Pour vous rafraîchir la mémoire : les trois familles d'erreurs
Fabriquons volontairement un plantage
Créez plante.js :
function calculerMoyenne(notes) {
const total = notes.reduce((somme, n) => somme + n, 0);
return total / notes.length;
}
function traiterClasse(classe) {
return calculerMoyenne(classe.notes);
}
traiterClasse({ eleve: "Paul" });
L'objet transmis n'a pas de propriété notes — le plantage est programmé. Exécution :
node plante.js
Résultat (chemins abrégés) :
/home/cours/plante.js:2
const total = notes.reduce((somme, n) => somme + n, 0);
^
TypeError: Cannot read properties of undefined (reading 'reduce')
at calculerMoyenne (/home/cours/plante.js:2:23)
at traiterClasse (/home/cours/plante.js:6:10)
at Object.<anonymous> (/home/cours/plante.js:9:1)
Ce texte a l'air hostile ; il est en réalité le rapport d'accident le plus bavard que la machine puisse vous offrir. Apprenons à le lire.
Anatomie : trois blocs
Bloc 1 — La ligne fautive, reproduite avec son curseur
const total = notes.reduce((somme, n) => somme + n, 0);
^
Node réimprime la ligne exacte du fichier et place un ^ sous l'expression qui a explosé — ici, sous notes. : c'est cette valeur-là qui pose problème.
Bloc 2 — Le type et le message
TypeError: Cannot read properties of undefined (reading 'reduce')
Lecture mot à mot : famille (TypeError = opération impossible sur ce type de valeur), puis traduction humaine — « j'ai tenté de lire la propriété reduce de quelque chose qui valait undefined ». Vous savez déjà (leçon précédente) qu'il s'agit du message « je cherche une pièce dans une maison vide ». Donc : au moment du crash, notes valait undefined.
Bloc 3 — La pile d'appels
at calculerMoyenne (...plante.js:2:23) ← là où le crash a eu lieu
at traiterClasse (...plante.js:6:10) ← qui appelait calculerMoyenne
at Object.<anonymous> (...plante.js:9:1) ← le niveau racine du fichier
La pile se lit comme une pile d'assiettes : le sommet est le dernier geste posé (le crash), chaque niveau en dessous indique qui a demandé ce geste, jusqu'au bas de fichier (lancement initial). Chaque ligne suit le format fichier:ligne:colonne — dans 2:23, la colonne pointe précisément .reduce.
Ordre de lecture professionnel, toujours le même :
- Type : quelle famille ?
- Message en entier : quelle opération, sur quelle valeur ?
- Première ligne de pile : où ça a cassé.
- Lignes suivantes : par quel chemin on y est arrivé.
- LA question finale : pourquoi cette valeur valait-elle
undefinedici ?
Crash ≠ cause : la distinction décisive
Dans notre exemple : le crash a lieu dans calculerMoyenne... mais la cause est ailleurs. Relisez la pile : traiterClasse a transmis classe.notes — or l'objet ne contient que eleve. Donc classe.notes vaut undefined, et c'est cette valeur empoisonnée qui a voyagé jusqu'à la ligne fautive.
Corriger la ligne 2 ne servirait à rien : la fonction fait correctement son travail quand on lui donne des notes ! Les vraies corrections sont en amont :
// Option A : garantir la donnée chez l'appelant
traiterClasse({ eleve: "Paul", notes: [12, 15] });
// Option B : défendre la fonction avec un échec explicite
function traiterClasse(classe) {
if (!classe.notes) {
throw new Error("Classe sans notes : " + classe.eleve);
}
return calculerMoyenne(classe.notes);
}
Notez la philosophie de l'option B : provoquer un plantage précoce avec un message clair vaut mieux qu'un comportement silencieux faux. Un crash honnête se débogue en trente secondes ; un mensonge discret coûte des heures.
Règle générale issue de cet exemple : le symptôme vit là où ça casse ; la cause vit là où la mauvaise valeur est née. La stack trace relie les deux.
Erreurs chaînées
Une erreur peut en masquer une autre : certaines traces se terminent par une section [ cause ] ou Caused by:. Lisez alors les deux piles : celle de l'erreur finale ET celle de sa cause première — c'est presque toujours cette dernière qui raconte l'histoire vraie. Vous rencontrerez cela concrètement avec les requêtes réseau et SQL plus tard dans le cursus.
Exercice
Analysez cette trace sans exécuter quoi que ce soit :
TypeError: Cannot read properties of undefined (reading 'push')
at ajouterTache (/proj/todo.js:12:9)
at gererJournee (/proj/agenda.js:40:3)
at main (/proj/index.js:5:5)
- Quel type d'erreur ? Que signifie-t-il précisément ?
- Quelle ligne planterait si on ouvrait le fichier ?
- Qui a appelé la fonction fautrice, et par quel chemin complet ?
- Proposez deux hypothèses de cause distinctes.
- Pourquoi « corriger la ligne 12 » serait-il probablement une erreur ?
Résumé
- Stack trace = ligne fautive + type/message + pile d'appels ; haut = lieu du crash, bas = contexte.
fichier:ligne:colonnelocalise l'expression exacte.- Le crash montre le symptôme ; remonter la pile révèle le chemin de la cause.
- Échouer tôt avec un bon message bat réussir tard en silence.
Correction disponibleCherchez d’abord par vous-même.Voir la correction
Correction
Réponses détaillées
1. TypeError : tentative d'appeler la méthode .push sur undefined. Concrètement, la variable sur laquelle ajouterTache fait son push n'est pas (ou plus) le tableau attendu au moment de l'appel.
2. La ligne 12 de todo.js, colonne 9 — l'emplacement exact signalé par la première ligne de la pile et reproduit par Node au-dessus du message.
3. Chemin complet, du bas vers le haut de la pile : le programme a démarré dans main (index.js:5) → main a appelé gererJournee (agenda.js:40) → qui a appelé ajouterTache (todo.js:12), où tout s'est arrêté.
4. Deux hypothèses plausibles, testables chacune séparément :
gererJourneea transmisundefined: propriété absente d'un objet (comme notreclasse.notes), ou paramètre oublié lors de l'appel ;ajouterTachereçoit le résultat d'une fonction qui retourneundefinedsur un cas non géré — typiquement une recherche sans résultat retournant implicitement rien (souvenez-vous : toute fonction sansreturnrendundefined).
5. Parce que la fonction fait correctement son travail quand on lui donne un tableau : le vrai bug est en amont, dans la donnée transmise. Corriger en amont (valider, garantir la donnée) supprime le problème pour tous les futurs appelants ; bricoler la ligne 12 pour « résister » aux mauvaises données masquerait le symptôme ici tout en le laissant prospérer ailleurs. C'est l'application directe de « crash ≠ cause ».