modulo 02 / lezione 11

Leggere errori e stack trace

Stack trace: da dove arriva davvero il problema

Seguire la catena delle chiamate distinguendo punto in cui esplode e punto in cui nasce la causa, comprendendo l'ordine LIFO dello stack.

55 min Debugging

In parole semplici

Lo stack trace è la pila delle chiamate attive nel momento esatto dell’errore. Si legge dall’alto verso il basso: in alto la funzione dove l’errore è stato lanciato, sotto chi l’ha chiamata, sotto ancora chi ha chiamato quella, e così via. È una mappa di “come siamo arrivati qui”.

L’errore frequente del principiante: guardare solo la prima riga. La prima riga è dove esplode, non sempre dove nasce la causa.

Parole nuove

  • stack (LIFO): pila in cui l’ultima funzione chiamata è la prima ad uscire (Last In, First Out).
  • frame: una riga dello stack trace (una chiamata in corso).
  • call site: punto dove una funzione viene chiamata.
  • innermost frame: frame più in alto, dove l’errore è stato lanciato.

Cosa impari

  • Leggere uno stack trace in ordine corretto.
  • Distinguere il punto di esplosione dal punto in cui nasce la causa.
  • Riconoscere le righe “interne” (codice tuo) da quelle “esterne” (framework, librerie, browser).

Esempio commentato

TypeError: Cannot read properties of undefined (reading 'nome')
    at mostraUtente (app.js:42:18)        ← punto di esplosione
    at handleClick (app.js:60:5)          ← chi ha chiamato mostraUtente
    at HTMLButtonElement.<anonymous>      ← chi ha chiamato handleClick
        (app.js:75:3)                       (il listener registrato)

Lettura:

  1. Riga 42 di app.js prova a leggere .nome su qualcosa che è undefined.
  2. Stava eseguendo mostraUtente, che è stato invocato da handleClick a riga 60.
  3. handleClick era a sua volta un listener attaccato a un HTMLButtonElement, registrato a riga 75.

Esplosione vs causa

Lo stack ti dice dove esplode. Non sempre ti dice perché.

Esempio:

function caricaUtenti() {
  return null;        // ← la causa nasce qui
}

function mostraNomi() {
  const utenti = caricaUtenti();
  return utenti.map(u => u.nome);  // ← esplode qui
}

Stack trace:

TypeError: Cannot read properties of null (reading 'map')
    at mostraNomi (app.js:6:18)

L’errore parla solo di mostraNomi. Ma la causa è in caricaUtenti. Lo stack è il punto di partenza, non quello di arrivo.

Ordine LIFO: dall’innermost al chiamante

Lo stack cresce con le chiamate e si svuota con i return:

chiamata A → stack: [A]
A chiama B → stack: [A, B]
B chiama C → stack: [A, B, C]
C lancia errore → stack al momento dell'errore: [A, B, C]

Lo stack trace si stampa dall’alto (C, l’ultimo entrato) al basso (A, il primo).

Frame “tuoi” e frame “esterni”

In un’app reale, lo stack è pieno di frame di framework e librerie:

TypeError: ...
    at mostraUtente (app.js:42)              ← tuo
    at handleClick (app.js:60)               ← tuo
    at HTMLButtonElement.<anonymous>          ← bridge browser ↔ tuo codice
    at dispatchEvent (react-dom.js:1234)     ← framework
    at SafariEventDispatcher (react-dom.js:888) ← framework

Concentrati sui frame tuoi. I frame del framework sono utili solo per casi specifici (bug del framework stesso, configurazioni errate).

Async stack trace (cenno)

Con async/await, lo stack non sempre mostra la catena logica. Esempio:

async function a() { return await b(); }
async function b() { return await c(); }
async function c() { throw new Error("boom"); }
a();

In runtime moderni (Chrome, Node 16+) lo stack mostra c → b → a. In runtime vecchi vedresti solo c. Approfondiremo nel modulo 7.

Source map (cenno)

In produzione il codice è minificato: i nomi diventano a, b, c, le righe si compattano. Lo stack diventa illeggibile:

TypeError: ...
    at e (main.abc123.js:1:5432)

La source map (modulo 7) collega main.abc123.js:1:5432 al sorgente originale. Senza source map, lo stack di produzione è quasi inutile.

Esercizio guidato

Crea tre funzioni che si chiamano a catena:

function uno() { return due(); }
function due() { return tre(); }
function tre() { throw new Error("bersaglio"); }

uno();
  1. Esegui con node. Copia lo stack trace.
  2. Annota: chi è l’innermost frame? Chi è il più basso?
  3. Modifica tre in modo che usi undefined.x invece di lanciare un Error. Confronta gli stack.
  4. Aggiungi un quarto livello che chiama uno da un setTimeout. Osserva come cambia lo stack.

Errori frequenti del principiante

  • Fermarsi alla prima riga. Spesso il vero indizio è due righe sotto.
  • Confondere “esplosione” e “causa”. Lo stack ti dice dove, non perché.
  • Ignorare i frame propri perché sembrano “ovvi”. Il frame ovvio è spesso quello sbagliato.
  • Buttare via stack di produzione perché illeggibili. Carica le source map e riprova.