Introduzione
Quando un programma JavaScript non si comporta come previsto, il primo istinto è spesso quello di aggiungere qualche console.log() qua e là. È un buon punto di partenza, ma il debugging davvero utile nasce dalla combinazione tra console e strumenti del browser, come la scheda Sources, i breakpoint e l’ispezione delle variabili in tempo reale.
In questo tutorial vedremo un approccio pratico per individuare bug in modo ordinato, senza riempire il codice di log temporanei difficili da gestire. L’obiettivo è imparare a capire cosa sta succedendo, dove succede e perché succede, usando gli strumenti già integrati nel browser.
Il sotto-argomento che approfondiamo qui è molto utile nella pratica: debugging con console.log e breakpoint nel browser. È una competenza fondamentale per chi sviluppa applicazioni front-end o lavora con codice asincrono, eventi DOM e chiamate API.
Codice completo
Immaginiamo una piccola pagina con un pulsante che carica dei dati finti e li mostra a schermo. Il bug che vogliamo analizzare è semplice: a volte la lista non viene aggiornata correttamente.
// Simuliamo un piccolo stato applicativo
const appState = {
loading: false,
items: [],
lastFetchAt: null
};
const button = document.querySelector(´#loadBtn´);
const output = document.querySelector(´#output´);
button.addEventListener(´click´, async () => {
console.log(´Click sul pulsante: avvio caricamento´);
appState.loading = true;
render();
try {
const data = await fetchFakeItems();
console.log(´Dati ricevuti dal server simulato:´, data);
// Possibile punto di bug: i dati potrebbero non essere un array
if (!Array.isArray(data)) {
throw new Error(´Il formato dei dati non è valido´);
}
appState.items = data;
appState.lastFetchAt = new Date().toISOString();
} catch (error) {
console.log(´Errore durante il caricamento:´, error);
appState.items = [];
} finally {
appState.loading = false;
render();
console.log(´Stato finale:´, appState);
}
});
function render() {
if (appState.loading) {
output.textContent = ´Caricamento in corso...´;
return;
}
if (appState.items.length === 0) {
output.textContent = ´Nessun elemento disponibile´;
return;
}
output.innerHTML = appState.items
.map(item => `<li>${item.name}</li>`)
.join(´´);
}
function fetchFakeItems() {
return new Promise((resolve) => {
setTimeout(() => {
const shouldFail = Math.random() > 0.5;
if (shouldFail) {
resolve({ message: ´Formato non previsto´ });
} else {
resolve([
{ id: 1, name: ´Notebook´ },
{ id: 2, name: ´Mouse´ },
{ id: 3, name: ´Tastiera´ }
]);
}
}, 800);
});
} Questo esempio è utile perché contiene alcuni punti tipici da controllare: un evento click, una funzione asincrona, una risposta non sempre valida e un rendering della UI che dipende dallo stato.
Spiegazione
Il debugging con la console funziona bene quando inserisci messaggi mirati nei punti strategici del flusso. Nel nostro esempio, i log principali sono:
- prima della richiesta, per verificare che l’evento sia stato davvero attivato;
- subito dopo la risposta, per controllare la forma dei dati ricevuti;
- nel blocco catch, per capire se si è verificato un errore;
- alla fine, per osservare lo stato aggiornato dell’applicazione.
Perché questo approccio è utile? Perché non stai solo “stampando cose”, ma stai tracciando il percorso dei dati. In pratica, stai seguendo il ciclo di vita di una variabile: da quando nasce, a come cambia, fino a quando viene usata nella UI.
Gli strumenti del browser completano il lavoro. Aprendo DevTools e andando nella scheda Sources, puoi:
- mettere un breakpoint sulla riga che ti interessa;
- eseguire il codice passo dopo passo con Step over;
- controllare il valore delle variabili nel pannello Scope;
- ispezionare lo stack delle chiamate per capire da dove arriva l’esecuzione.
Ad esempio, se il problema è che la lista mostra “Nessun elemento disponibile” anche quando la richiesta sembra andata a buon fine, puoi fermarti proprio dopo const data = await fetchFakeItems(); e verificare il contenuto di data. Nel nostro esempio, potresti scoprire che a volte non è un array ma un oggetto con un messaggio di errore. Questo spiega perché il controllo Array.isArray(data) fallisce e genera un’eccezione.
Un altro vantaggio dei breakpoint rispetto ai log è che non alterano il flusso del codice con troppe istruzioni temporanee. Inoltre, puoi ispezionare anche oggetti complessi, array annidati e variabili locali che non sempre sono facili da leggere in console.
Se vuoi aumentare la precisione, puoi usare anche breakpoint condizionali. Per esempio, puoi fermarti solo quando una certa condizione è vera, come quando data non è un array. Questo è molto utile in applicazioni grandi, dove un bug si verifica solo in casi rari.
Best practice
- Usa log brevi e significativi: evita messaggi generici come “qui” o “test”. Meglio scrivere cosa stai controllando, ad esempio “Dati ricevuti dal server simulato”.
- Rimuovi i log temporanei quando hai finito. Lasciare decine di
console.log()nel codice rende più difficile la manutenzione. - Preferisci breakpoint per il debugging profondo. La console è ottima per osservare, ma il debugger del browser è migliore quando devi analizzare il flusso.
- Controlla gli stati intermedi: spesso il bug non è nel risultato finale, ma in un passaggio precedente che modifica i dati in modo inatteso.
- Ispeziona oggetti e array in modo strutturato, non solo come stringhe. La console del browser permette di espandere i valori e vedere dettagli utili.
- Fai attenzione al codice asincrono: con
async/awaite promesse, il punto in cui il codice si ferma non è sempre ovvio. I breakpoint aiutano molto a seguire il flusso reale.
Un consiglio pratico: quando il bug è complesso, prova a combinare un console.log() con un breakpoint nello stesso punto. Il log ti dà una traccia rapida, mentre il breakpoint ti permette di fermarti e indagare con calma.
Riepilogo
Il debugging in JavaScript non significa solo stampare messaggi in console. Il vero salto di qualità arriva quando impari a usare in modo coordinato console.log() e gli strumenti di debug del browser.
- console.log() è utile per tracciare il flusso e verificare i valori.
- Breakpoint e Sources permettono di fermare l’esecuzione e analizzare il problema in dettaglio.
- Scope e Call stack ti aiutano a capire lo stato corrente e l’origine dell’esecuzione.
- Breakpoint condizionali sono ideali quando il bug appare solo in certe condizioni.
Se impari a leggere il comportamento del codice invece di limitarti a “sparare log”, debuggherai più velocemente e con meno frustrazione.
Approfondisci con risorse ufficiali
- MDN Web Docs - Console: documentazione completa sugli strumenti
consoledisponibili nei browser. - MDN Web Docs - Debugger: guida utile per comprendere il debugging nelle DevTools.
- Chrome DevTools - JavaScript debugging: documentazione ufficiale per usare breakpoint, step-by-step e ispezione variabili.
- Firefox Developer Tools - Debugger: risorsa ufficiale per il debugger di Firefox.
Studiare questi strumenti con esempi reali ti aiuterà a passare da un debugging “reattivo” a un debugging più metodico e professionale.
