Si vous apprenez à écrire du code, alors vous avez sans aucun doute rencontré la redoutable trace de pile, et comme moi, vous vous êtes probablement entraîné à ignorer la lecture de leur sortie tronquée. Mais ce n’est pas absurde, et si vous prenez 10 minutes pour voir le motif répétitif, vous pouvez débloquer une compétence cruciale. La résolution des bogues fait partie intégrante de l’écriture du code, il est donc inévitable d’apprendre à lire les traces de la pile.
Qu'est-ce qu'une pile d'appels ?
Un composant crucial qui enregistre l'état des fonctions et l'historique des appels
UN pile d'appels (alias pile) est une structure de données qui permet de suivre l'état des fonctions. Il comprend une série d'adresses mémoire (pointeurs) et empiler les cadres.
Un cadre de pile est comme un conteneur. Il stocke l'état d'exactement une fonction en cours d'exécution (variables, etc.). Lorsqu'un appel de fonction se produit, un nouveau cadre est placé en haut de la pile et lorsque la fonction revient, il est détruit.
Vous pouvez considérer une pile d’appels comme une colonne verticale de trames, disposées dans l’ordre de la séquence d’appels. Il utilise des pointeurs pour suivre où se trouve chaque image en mémoire et en haut de la pile.
Si vous voulez plus de détails, regardez cette vidéo du célèbre professeur de Harvard Cours CS50:
Ils ont des dizaines de cours sur leur chaîne YouTube que vous pourriez trouver intéressant.
6 extraits JavaScript pour peaufiner votre site
Des gains rapides et faciles pour n'importe quel site que vous créez.
Comment sont organisées les traces de pile ?
Ils reflètent l'ordre des cadres de pile
Une trace de pile présente l'ordre d'appel menant à une fonction défaillante. Il transmet des informations utiles, telles que le numéro de fichier et de ligne pour chaque appel de fonction.
Pour comprendre une trace de pile, le meilleur point de départ est la trame incriminée où le crash s'est produit. Étant donné que les trames de pile sont ordonnées de manière séquentielle, la trame en panne doit être la plus récente. L'extrémité de la trace de pile à laquelle les informations sont affichées dépend du moteur d'exécution ou du compilateur spécifique que vous avez utilisé : elle se trouve en haut ou en bas.
Regardons quelques exemples et voyons.
Les trois traces de pile suivantes suivent trois appels de fonction (« un », « deux » et « trois »), où « trois » fait planter le programme en divisant par zéro.
Il s'agit d'une trace de pile JavaScript (Node.js), et vous pouvez voir que le cadre qui plante est en haut :
-
L'annotation « 1 » affiche le contenu de la ligne à l'origine du problème.
-
L'annotation « 2 » indique le fichier dans lequel le problème s'est produit.
-
L'annotation « 3 » indique le nom de la fonction où le problème s'est produit.
-
L'annotation « 4 » définit respectivement les numéros de ligne et de caractère problématiques.
Vous avez remarqué qu'il y a des éléments supplémentaires dans la trace de la pile ? Ceux grisés en bas représentent les appels supplémentaires effectués par Node.js. Tout ce qui suit « Objet » est l'endroit où commence notre code.
Vous pouvez voir dans le code ci-dessous que la ligne 4, caractère 15, est le symbole de division :
Cela nous amène directement au point d'échec et à chaque étape qui y mène, ce qui est pratique car parfois le problème vient d'un appelant qui transmet des valeurs non valides.
Regardons Python maintenant :
Vous remarquez que l'ordre est inversé ? Cependant, les informations fournies sont presque les mêmes, à une différence mineure près. Au lieu d'indiquer sans ambiguïté dans quelle fonction l'erreur s'est produite (comme le fait Node.js), Python affiche le site d'appel (« 1 ») et dit « en trois » (annotation « 2 ») pour montrer où l'erreur s'est produite. Je trouve cela moins lisible, mais certains ne le peuvent pas.
Passons à autre chose. Regardons Golang:
Une sortie beaucoup plus nette et uniforme, comme la trace de la pile Node.js. La principale différence que vous devez noter est l’absence de numéro de caractère, mais vous obtenez un numéro de ligne.
Vous voyez donc que la façon dont une trace de pile est organisée dépend du compilateur ou du runtime que vous utilisez. Cependant, certains environnements d'exécution, compilateurs ou écosystèmes négligent tellement les traces de pile qu'ils deviennent inutiles. Je vais ensuite vous montrer un exemple extrême, en espérant que vous ne confondez pas les bonnes et les mauvaises traces de pile et que vous ne les abandonniez pas complètement.
Certaines traces de pile sont illisibles
Ce n'est pas seulement vous ; ils démoralisent tout le monde, mais on peut parfois les réparer
Il est très facile de renoncer à lire les traces de pile et de décrire votre problème à Google (maintenant LLM). Il s’agit d’un comportement habitué depuis longtemps aux programmeurs grâce à une conception négligente. Ce que je veux dire, c'est que si certains langages se soucient de transmettre des informations utiles (comme Rust ou Python), d'autres non. Les pratiques courantes dans des langages comme JavaScript rendent les traces de pile impossibles à lire. Regroupement, transpileret minification sont les principaux coupables, et chacune de ces étapes supprime des informations utiles.
Prenons par exemple la trace de pile suivante. C'est transpilé Manuscritqui est ensuite regroupé et minifié à l'aide de Webpack et Terser. Cela signifie simplement que nous avons transformé un programme TypeScript multi-fichiers en un seul morceau de JavaScript, puis que nous avons renommé toutes les fonctions et noms de variables utiles en lettres simples.
Regardez l'annotation « 1 » ; vous pouvez voir que toutes les occurrences après « Objet » sont sur la ligne « 1 » car l'ensemble de notre programme est sur une seule ligne. Si vous regardez l'annotation « 2 », c'est là que le problème se produit, mais cela n'a toujours aucun sens. Le moteur d'exécution pointe même vers le morceau et dit : « Quelque part là-dedans ». Ce sont les traces de pile typiques que vous verrez dans un navigateur, sauf si vous avez cartes sources configuré.
Les mappages sources sont des fichiers JSON distincts qui pointent vers le code source d'origine, référencé par un commentaire dans le code transpilé. Lorsqu’un problème survient, il est beaucoup plus facile d’en trouver la source. Le navigateur, par exemple, vous permet même de cliquer et d'afficher la ligne TypeScript exacte. Cependant, ils ne sont généralement pas utilisés dans le code de production et sont souvent capricieux. La complexité des systèmes de build frontend est un tout autre sujet, et il suffit de vous conseiller d'éviter les configurations complexes, sauf si vous avez des exigences complexes. BAISER.
Utiliser des LLM pour faciliter le débogage
Déchets à l'intérieur ; solutions
Un excellent cas d'utilisation des LLM (comme Claude Code) consiste à lire les traces de pile, même lorsqu'elles sont illisibles comme dans l'exemple précédent. Claude n'aurait aucun problème à les comprendre s'il avait également accès au code source.
Pour fournir à Claude une trace de pile, vous pouvez fournir une capture d'écran ou demander à Claude d'exécuter la commande directement. J'utilise beaucoup Claude pour écrire un package Emacs (car mes connaissances d'Emacs ont des limites), et cela a rendu les traces de la pile Elisp réellement utiles et a débloqué un point de friction majeur.
Cela fonctionne bien car il n’y a pas besoin d’imagination (une faiblesse des LLM), et presque tout ce qui est nécessaire pour poser un diagnostic existe dans la trace. Claude (c'est-à-dire un ordinateur) peut lire des textes de plusieurs ordres de grandeur plus rapidement que moi, il exploite donc pleinement ses atouts.
Les LLM pour le débogage fonctionnent bien. Cependant, Claude peut toujours facilement rester coincé, ce n'est donc pas une solution miracle.
Devenez un meilleur programmeur : 7 habitudes à développer
Des habitudes éprouvées pour écrire de meilleurs programmes.
Récapitulons et résumons.
-
Une pile d'appels est une structure de données de pointeurs et de cadres de pile.
-
Un cadre de pile est un segment isolé de mémoire utilisé pour stocker l’état d’une fonction (ou d’une méthode).
-
Les traces de pile sont classées séquentiellement et contiennent le nom de la fonction, le chemin du fichier, le numéro de ligne et parfois le numéro de caractère de chaque appel effectué ayant conduit au problème.
-
La ligne la plus récente de la trace de la pile correspond à l'endroit où le problème se produit, qui peut se trouver en haut ou en bas (selon le runtime ou le compilateur que vous utilisez.)
Commencez là où le problème survient. La plupart du temps, le signal est clair et le problème est facile à résoudre. Vous devrez peut-être examiner les étapes précédentes si elles transmettent des valeurs corrompues.
Les principaux points à retenir sont les suivants regarde autour du point d'échec et comprends que la plupart des informations de trace de pile pointent vers des emplacements dans le code source.
Pour le code frontend, disposez d'une version de développement dédiée, qui inclut des cartes sources et ignore la minification et le regroupement. Serveurs Web de développement comme Vite adopter cette approche et servir Modules ES (JS simple et lisible) en mode développement mais regroupez le code pour les versions de production.
Vous pouvez lire l’article original (en Angais) sur le blogwww.howtogeek.com