Jak číst zásobníkové výpisy k diagnostice problémů ve vašem kódu
Pokud se učíte programovat, nepochybně jste se setkali s děsivým stack trace a stejně jako já jste si pravděpodobně navykli přeskočit čtení jejich nesrozumitelného výstupu. Ale nejsou to nesmysly, a pokud si vezmete 10 minut, abyste viděli opakující se vzorec, můžete odemknout zásadní dovednost. Řešení chyb je nedílnou součástí psaní kódu, takže naučit se číst stack traces je nevyhnutelné.
Co je to volací zásobník?
Klíčová součást, která zaznamenává stav funkce a historii volání
Volací zásobník (také zásobník) je datová struktura, která pomáhá sledovat stav funkcí. Skládá se ze série paměťových adres (ukazatelů) a rámců zásobníku.
Rámec zásobníku je jako kontejner. Ukládá stav přesně jedné právě vykonávané funkce (proměnné atd.). Když dojde k volání funkce, nový rámec se umístí na vrchol zásobníku, a když funkce skončí, je zničen.
Můžeš si představit zásobník volání jako vertikální sloupec rámců, uspořádaných v pořadí volání. Používá ukazatele k sledování toho, kde se každý rámec nachází v paměti a vrcholu zásobníku.
Pokud chcete více podrobností, podívejte se na toto video ze slavného kurzu CS50 na Harvardu:
Na jejich kanálu YouTube mají desítky kurzů, které by vás mohly zajímat.
Jak jsou organizovány stopy zásobníku?
Odrážejí pořadí rámců zásobníku
Stack trace zobrazuje pořadí volání vedoucí k selhávající funkci. Poskytuje užitečné informace, jako je soubor a číslo řádku pro každé volání funkce.
Chcete-li porozumět stopě zásobníku, nejlepším místem, kde začít, je problémový rámec, kde došlo k pádu programu. Protože se rámce zásobníku řadí postupně, musí být padající rámec tím nejnovějším. Na kterém konci stopy zásobníku se tato informace zobrazí, závisí na konkrétním běhovém prostředí nebo kompilátoru, který jste použili—je to buď nahoře nebo dole.
Pojďme se podívat na některé příklady a uvidíme.
Všechny tři následující sledy zásobníku sledují tři volání funkcí („one“, „two“ a „three“), přičemž „three“ způsobí havárii programu dělením nulou.
Toto je zásobník chyb JavaScriptu (Node.js) a můžete vidět, že rámec, který způsobuje pád, je nahoře:
- Poznámka „1“ ukazuje obsah řádku, který způsobuje problém.
- Poznámka "2" ukazuje soubor, ve kterém došlo k problému.
- Poznámka „3“ ukazuje název funkce, kde se problém vyskytl.
- Poznámka „4“ definuje problematické číslo řádku a číslo znaku.
Všimněte si, že ve stack trace jsou další položky? Ty, které jsou zobrazena šedě dole, představují další volání, která provádí Node.js. Všechno po "Object" je místo, kde začíná náš kód.
Z kódu níže můžete vidět, že řádek 4, znak 15, je symbol dělení:
To nás přivádí přímo k bodu selhání a ke každému kroku, který k němu vedl, což je užitečné, protože někdy je problém v tom, že volající předává neplatné hodnoty.
Podívejme se nyní na Python:
Všimli jste si, že pořadí je obrácené? Poskytnuté informace jsou však téměř stejné, až na jeden drobný rozdíl. Místo toho, aby jasně uváděly, ve které funkci k chybě došlo (jako to dělá Node.js), Python ukazuje místo volání („1“) a říká „v three“ (poznámka „2“), aby ukázal, kde k chybě došlo. Považuji to za méně čitelné, ale někteří lidé to tak nemusí vidět.
Přejděme dál. Podívejme se na Golang:
Mnohem čistší a jednotnější výstup, podobně jako zásobník Node.js. Klíčový rozdíl, který byste si měli všimnout, je absence čísla znaku - ale dostanete číslo řádku.
Tak vidíte, jak se zásobníkový výpis organizuje, závisí na tom, jaký kompilátor nebo prostředí runtime používáte. Nicméně některá runtime, kompilátory nebo ekosystémy mají takový nezájem o zásobníkové výpisy, že se stanou nepoužitelnými. Ukážu vám příští extrémní příklad, doufajíc, že nespojujete dobré a špatné zásobníkové výpisy a úplně to vzdáte.
Některé stopy zásobníku jsou nečitelná
Nejsi to jen ty; demoralizují všechny, ale někdy je můžeš napravit
Je velmi snadné přestat číst výpisy zásobníků a místo toho popsat svůj problém Googlu (nyní LLM). Toto je chování, které je programátorům dlouhodobě vnucováno nedbalým designem. Co tím myslím, je, že zatímco některé jazyky se starají o předávání užitečných informací (například Rust nebo Python), jiné ne. Běžné praktiky v jazycích jako JavaScript činí výpisy zásobníků nemožnými k přečtení. Hlavními viníky jsou bundlování, transpiling a minifikace, a každý z těchto kroků odstraňuje užitečné informace.
Vezměte si například následující trasování zásobníku. Je to transpiled TypeScript, který je následně svázán a zmenšen pomocí Webpack a Terser. Znamená to jednoduše, že jsme převedli TypeScript program s více soubory do jednoho bloku JavaScriptu a pak přejmenovali všechny užitečné názvy funkcí a proměnných na jednotlivá písmena.
Podívejte se na anotaci „1“; můžete vidět, že všechny výskyty po „Objektu“ jsou na řádku „1“, protože celý náš program je na jednom řádku. Pokud se podíváte na anotaci „2“, zde nastává problém, ale pořád je to nesmysl. Runtime dokonce ukazuje na tento blok a říká: „Někde tam.“ To jsou typické stack trace, které uvidíte v prohlížeči, pokud nemáte nakonfigurované source mapy.
Zdrojové mapy jsou samostatné JSON soubory, které odkazují zpět na původní zdrojový kód, přičemž jsou ve transpileovaném kódu uvedeny prostřednictvím komentáře. Když nastane problém, je mnohem snazší najít jeho zdroj. Prohlížeč vám například dokonce umožní kliknout a zobrazit přesný řádek v TypeScriptu. Obecně se však nepoužívají v produkčním kódu a často jsou náladové. Složitost systémů pro sestavení frontendu je zcela jiná téma, a postačí vám doporučení držet se složitých nastavení, pokud nemáte složité požadavky—takže, KISS.
Používejte LLM k asistenci při ladění
Smetí dovnitř; řešení ven
Jedním skvělým případem použití LLM (například Claude Code) je čtení stack trace, i když jsou nečitelnou hromadou jako předchozí příklad. Claude by s tím neměl problém porozumět, pokud by měl také přístup ke zdrojovému kódu.
Chcete-li Claudeovi poskytnout zásobník trasování, můžete poskytnout snímek obrazovky nebo nechat Claudea příkaz spustit přímo. Claudea jsem hodně používal při psaní balíčku pro Emacs (protože mé znalosti Emacsu mají meze), a díky tomu se zásobníky Elisp staly opravdu užitečnými a odblokovaly hlavní problém.
Funguje to dobře, protože není potřeba představivost (slabina LLM), a téměř vše, co je potřeba k diagnóze, existuje ve stopě. Claude (tj. počítač) může číst texty o několik řádů rychleji než já, takže to přesně vyhovuje jeho silným stránkám.
Velké jazykové modely pro ladění fungují dobře. Claude se však stále může snadno zaseknout, takže to není univerzální řešení.
Pojďme si to shrnout a zopakovat.
- Volací zásobník je datová struktura ukazatelů a rámců zásobníku.
- Stack frame je izolovaný segment paměti používaný k ukládání stavu funkce (nebo metody).
- Stopy zásobníku jsou seřazeny postupně a obsahují název funkce, cestu k souboru, číslo řádku a někdy i číslo znaku každého volání, které vedlo k problému.
- Nejnovější řádek ve výpisu zásobníku je místo, kde k problému dochází, což může být nahoře nebo dole (v závislosti na tom, které runtime nebo kompilátor používáte).
Začněte tam, kde problém nastává. Většinou je signál jasný a problém je snadné vyřešit. Možná budete muset podívat se na předchozí kroky, pokud předávají poškozené hodnoty.
Hlavními závěry jsou podívat se kolem místa selhání a pochopit, že většina informací ve stopě zásobníku ukazuje na místa v rámci zdrojového kódu.
Pro frontendový kód mějte vyhrazenou vývojovou verzi, která zahrnuje zdrojové mapy a přeskočí minifikaci a spojování souborů. Vývojové webové servery jako Vite tento přístup využívají a v režimu vývoje poskytují ES moduly (čistý, čitelný JS), ale pro produkční sestavení kód spojí.





