Node.js è un runtime JavaScript basato sullo stesso motore V8 utilizzato nel browser Chrome di Google. È spesso utilizzato per costruire applicazioni multipiattaforma lato server e terminale. Node.js è diventato sempre più popolare negli ultimi dieci anni perché è facile da installare, pratico da usare, veloce e permette agli sviluppatori web lato client di sfruttare le proprie competenze in un posto diverso.

Tuttavia, lo sviluppo del software rimane un compito complesso e il codice per Node.js ad un certo punto andrà in errore. Questo tutorial dimostra vari strumenti per aiutarvi nel debug delle applicazioni e a trovare la causa di un problema.

Cominciamo subito.

Guardate la nostra video-guida al debug del codice Node.js

Panoramica sul Debugging

“Debugging” è il nome dato ai vari mezzi di correzione dei difetti del software. Risolvere un bug è spesso semplice. Trovare la causa del bug può essere molto più complesso e può richiedere ore di grattacapi.

Le seguenti sezioni descrivono tre tipi generali di errori.

Errori di Sintassi

Il codice non segue le regole del linguaggio – per esempio, quando si omette una parentesi di chiusura o si scrive male una frase, ad esempio console.lag(x).

Un buon editor di codice può aiutare a individuare i problemi comuni:

  • Codice a colori delle dichiarazioni valide o non valide
  • Controllo del tipo delle variabili
  • Completamento automatico dei nomi di funzioni e variabili
  • Evidenziazione delle parentesi corrispondenti
  • Auto-indentazione dei blocchi di codice
  • Rilevamento del codice irraggiungibile
  • Refactoring di funzioni disordinate

Gli editor gratuiti come VS Code e Atom, hanno un ottimo supporto per Node.js, JavaScript e TypeScript (che traspila in JavaScript). I problemi di sintassi di base possono normalmente essere individuati prima di salvare e testare il codice.

Un linter di codice come ESLint segnalerà anche errori di sintassi, cattiva indentazione e variabili non dichiarate. ESLint è uno strumento per Node.js che potete installare globalmente:

npm i eslint -g

Potete controllare i file JavaScript dalla riga di comando usando:

eslint mycode.js

…ma è più facile usare un plugin per editor come ESLint per VS Code o linter-eslint per Atom, che convalidano automaticamente il codice mentre si scrive:

ESlint in VS Code
ESlint in VS Code.

Errori logici

Il vostro codice viene eseguito ma non funziona come vi aspettate. Ad esempio, un utente non viene disconnesso quando lo richiede; un rapporto mostra cifre errate; i dati non vengono salvati completamente in un database; ecc.

Gli errori logici possono essere causati da:

  • Utilizzo della variabile sbagliata
  • Condizioni errate, ad esempio if (a > 5) piuttosto che if (a < 5)
  • Calcoli che non tengono conto della precedenza dell’operatore, ad esempio 1+2*3 dà come risultato 7 invece che 9.

Errori di Runtime (o di Esecuzione)

Un errore diventa evidente solo quando l’applicazione viene eseguita, il che spesso porta ad un crash. Gli errori di runtime potrebbero essere generati dalle seguenti cause:

  • Dividere per una variabile che è stata impostata a zero
  • Tentare di accedere ad un elemento di un array che non esiste
  • Cercare di scrivere in un file di sola lettura

Gli errori di logica e di runtime sono più difficili da individuare, anche se le seguenti tecniche di sviluppo possono aiutare:

  1. Test-Driven Development: il TTD vi incoraggia a scrivere dei test prima che una funzione venga sviluppata, ad esempio X viene restituito dalla funzione Y quando Z viene passato come parametro. Questi test vengono eseguiti durante lo sviluppo iniziale e i successivi aggiornamenti per assicurarsi che il codice continui a funzionare come previsto.
  2. Usare un sistema di tracciamento dei problemi: non c’è niente di peggio di un’email che dice “Il tuo software non funziona”! I sistemi di tracciamento dei problemi permettono di registrare problemi specifici, documentare i passi di riproduzione, stabilire le priorità, assegnare gli sviluppatori e tracciare il progresso delle correzioni.
  3. Usare il controllo dei sorgenti: un sistema di controllo dei sorgenti come Git vi aiuterà a fare il backup del codice, gestire le revisioni e identificare il punto in cui è stato introdotto un bug. I repository online, compresi Github e Bitbucket, forniscono spazio e strumenti gratuiti per progetti più piccoli o open-source.

Incontrerete altri bug di Node.js, ma le sezioni che seguono descrivono i metodi per individuare errori sfuggenti.

Impostare le Opportune Variabili d’Ambiente di Node.js

Le variabili d’ambiente impostate nel sistema operativo host possono controllare le impostazioni dell’applicazione e del modulo Node.js. La più comune è NODE_ENV, che di solito è impostata su development quando si esegue il debug e production quando si esegue su un server live. È possibile impostare le variabili d’ambiente su macOS o Linux con il comando:

NODE_ENV=development

o al (classico) prompt dei comandi di Windows:

set NODE_ENV=development

oppure Windows Powershell:

$env:NODE_ENV="development"

Nel popolare framework Express.js, l’impostazione di NODE_ENV su development disabilita la cache dei template file e produce messaggi di errore descrittivi, che potrebbero essere utili durante il debug. Altri moduli possono offrire funzioni simili e potete aggiungere una condizione NODE_ENV alle vostre applicazioni, ad es.

// running in development mode?
const devMode = (process.env.NODE_ENV !== 'production');

if (devMode) {
  console.log('application is running in development mode');
}

Potete anche utilizzare il metodo util.debuglog di Node per emettere in modo condizionato messaggi di errore, ad es.

import { debuglog } from 'util';
const myappDebug = debuglog('myapp');
myappDebug('log something');

Questa applicazione mostrerà il messaggio di log solo quando NODE_DEBUG è impostato su myapp o su un carattere jolly come * o my*.

Utilizzare le Opzioni della Riga di Comando di Node.js

Gli script di Node sono normalmente lanciati con node seguito dal nome dello script di ingresso:

node app.js

Potete anche impostare le opzioni della riga di comando per controllare vari aspetti del runtime. I flag utili per il debug includono:

  • --check
    controlla la sintassi dello script senza eseguire
  • --trace-warnings
    produce uno stack trace quando le Promesse JavaScript non si risolvono o si rifiutano
  • --enable-source-maps
    mostra le mappe dei sorgenti quando si usa un transpiler come TypeScript
  • --throw-deprecation
    avvisa quando vengono utilizzate funzioni deprecate di Node.js
  • --redirect-warnings=file
    invia gli avvisi in uscita su un file invece che su stderr
  • --trace-exit
    mostra una traccia dello stack quando viene invocato process.exit().

Messaggi in Uscita sulla Console

Emettere un messaggio nella console è uno dei modi più semplici per eseguire il debug di un’applicazione Node.js:

console.log(`someVariable: ${ someVariable }`);

Pochi sviluppatori si rendono conto che ci sono molti altri metodi di console:

Metodo Console Descrizione
.log(msg) messaggio standard della console
.log('%j', obj) output dell’oggetto come stringa JSON compatta
.dir(obj, opt) proprietà dell’oggetto pretty-print
.table(obj) output di array e oggetti in formato tabellare
.error(msg) un messaggio di errore
.count(label) incrementa un contatore invocato e visualizza l’output
.countReset(label) resetta un contatore nominato
.group(label) indenta un gruppo di messaggi
.groupEnd(label) termina un gruppo
.time(label) avvia un timer nominato
.timeLog(label) riporta il tempo trascorso
.timeEnd(label) ferma un timer nominato
.trace() produce una traccia dello stack (un elenco di tutte le chiamate di funzione effettuate)
.clear() ripulisce la console

console.log() accetta anche un elenco di valori separati da virgole:

let x = 123;
console.log('x:', x);
// x: 123

…anche se la destrutturazione ES6 offre un risultato simile con meno sforzo:

console.log({ x });
// { x: 123 }

Il comando console.dir() stampa le proprietà degli oggetti allo stesso modo di util.inspect():

console.dir(myObject, { depth: null, color: true });

La Controversia della Console

Alcuni sviluppatori sostengono che non si dovrebbe mai usare console.log() perché:

  • State modificando il codice e potreste alterare qualcosa o dimenticare di rimuoverlo e
  • Non c’è bisogno quando ci sono opzioni di debug migliori.

Non credete a chi dice di non usare mai console.log()! La registrazione è veloce e sporca, ma ad un certo punto la usano tutti. Usate qualsiasi strumento o tecnica preferite. Risolvere un bug è più importante del metodo che adottate per trovarlo.

Utilizzare un Sistema di Registrazione di Terze Parti

I sistemi di registrazione di terze parti forniscono funzioni più sofisticate come i livelli di messaggistica, la verbosità, l’ordinamento, l’output di file, la profilazione, il reporting e altro. Le soluzioni più popolari includono cabin, loglevel, morgan, pino, signale, storyboard, tracer e winston.

Utilizzare l’Inspector V8

Il motore JavaScript V8 fornisce un client di debug che potete utilizzare in Node.js. Avviate un’applicazione utilizzando node inspect, ad es.

node inspect app.js

Il debugger si ferma alla prima riga e mostra un prompt debug>:

$ node inspect .\mycode.js
< Debugger listening on ws://127.0.0.1:9229/143e23fb
< For help, see: https://nodejs.org/en/docs/inspector
<
 ok
< Debugger attached.
<
Break on start in mycode.js:1
> 1 const count = 10;
  2
  3 for (i = 0; i < counter; i++) {
debug>

Inserite help per visualizzare un elenco di comandi. Potete fare un passo avanti nell’applicazione inserendo:

  • cont o c: continua l’esecuzione
  • next o n: esegue il comando successivo
  • step o s: entra in una funzione da invocare
  • out o o: esce da una funzione e ritorna all’istruzione chiamante
  • pause: mette in pausa il codice in esecuzione
  • watch(‘myvar’): controlla una variabile
  • setBreakPoint() o sb(): imposta un punto di interruzione
  • restart: riavvia lo script
  • .exit o Ctrl | Cmd + D: esce dal debugger

Certo, questa soluzione di debug è lunga e ingombrante. Usatela solo quando non avete altre opzioni, come quando state eseguendo del codice su un server remoto e non potete connettervi da un’altra parte o installare software aggiuntivo.

Utilizzare il Browser Chrome per il Debug del Codice di Node.js

L’opzione Node.js inspect descritta sopra avvia un server Web Socket che ascolta sulla porta localhost 9229. Avvia anche un client di debug basato sul testo, ma è possibile utilizzare client grafici – come quello integrato in Google Chrome e nei browser basati su Chrome come Chromium, Edge, Opera, Vivaldi e Brave.

Per eseguire il debug di una tipica applicazione web, avviatela con l’opzione –inspect per abilitare il server Web Socket del debugger V8:

node --inspect index.js

Nota:

  • si presume che index.js sia lo script di ingresso dell’applicazione.
  • Assicuratevi di utilizzare --inspect con i doppi trattini per non avviare il client del debugger basato sul testo.
  • Potete usare nodemon invece di node se volete riavviare automaticamente l’applicazione quando un file viene cambiato.

Di default, il debugger accetta solo connessioni in entrata dalla macchina locale. Se state eseguendo l’applicazione su un altro dispositivo, macchina virtuale o container Docker, usate:

node --inspect=0.0.0.0:9229 index.js
node inspect
node inspect

Potete anche usare --inspect-brk invece di --inspect per fermare l’elaborazione (impostare un punto di interruzione) sulla prima linea in modo da poter scorrere il codice dall’inizio.

Aprite un browser basato su Chrome e inserite chrome://inspect nella barra degli indirizzi per visualizzare i dispositivi locali e in rete:

Strumento di ispezione di Chrome.
Strumento di ispezione di Chrome.

Se la vostra applicazione Node.js non appare come Destinazione remota:

  • Cliccate su Open dedicated DevTools per Node e scegliete l’indirizzo e la porta, oppure
  • Spuntate Discover network targets, cliccate su Configure, poi aggiungete l’indirizzo IP e la porta del dispositivo dove sta girando.

Cliccate sul link Inspect del Target per lanciare il client debugger DevTools. Questo dovrebbe essere familiare a chiunque abbia utilizzato DevTools per il debug del codice lato client:

Chrome DevTools
Chrome DevTools.

Passate al pannello Sources. Potete aprire qualsiasi file premendo Cmd | Ctrl + P e inserendo il nome del file (come index.js).

Tuttavia, è più facile aggiungere la cartella del progetto all’area di lavoro. Questo vi permette di caricare, modificare e salvare i file direttamente da DevTools (se pensate che sia una buona idea è un’altra questione!)

  1. Clicca + Add folder to workspace
  2. Seleziona la posizione del progetto Node.js
  3. Premete Agree per permettere le modifiche ai file

Ora potete caricare i file dall’albero delle cartelle di sinistra:

Pannello Sources di Chrome DevTools.
Pannello Sources di Chrome DevTools.

Cliccate su qualsiasi numero di riga per impostare un punto di interruzione denotato da un marcatore blu.

Il debug si basa sui punti di interruzione. Questi specificano dove il debugger deve mettere in pausa l’esecuzione del programma e mostrare lo stato attuale (variabili, stack delle chiamate, ecc.)

Nell’interfaccia utente, potete definire un numero arbitrario di punti di interruzione. Un’altra opzione è quella di inserire un’istruzione debugger nel vostro codice, che si ferma quando un debugger è collegato.

Caricate e utilizzate la vostra applicazione web per raggiungere l’istruzione su cui è impostato un punto di interruzione. Nell’esempio qui, http://localhost:3000/ è aperto in qualsiasi browser e DevTools fermerà l’esecuzione alla linea 44:

Breakpoint di Chrome.
Breakpoint di Chrome.

Il pannello di destra mostra:

  • Una fila di icone di azioni (vedi sotto).
  • Un riquadro Watch permette di monitorare le variabili cliccando sull’icona + e inserendo i rispettivi nomi.
  • Un riquadro Breakpoints mostra un elenco di tutti i punti di interruzione e permette di abilitarli o disabilitarli.
  • Un riquadro Scope mostra lo stato di tutte le variabili locali, del modulo e globali. Controllerete questo riquadro molto spesso.
  • Un pannello Call Stack mostra la gerarchia delle funzioni chiamate per raggiungere questo punto.

Una fila di icone di azioni è mostrata sopra Paused on breakpoint:

Icone di breakpoint di Chrome.
Icone di breakpoint di Chrome.

Da sinistra a destra, queste eseguono le seguenti azioni:

  • resume execution: continua l’elaborazione fino al successivo punto di interruzione
  • step over: esegue il comando successivo ma resta all’interno del blocco di codice corrente – non salta in nessuna funzione chiamata
  • step into: esegue il comando successivo e salta in qualsiasi funzione se necessario
  • step out: continua l’elaborazione fino alla fine della funzione e ritorna al comando chiamante
  • step: simile a step into tranne per il fatto che non salta nelle funzioni asincrone
  • deactivate: disattiva tutti i punti di interruzione
  • pause on exceptions: ferma l’elaborazione quando si verifica un errore.

Breakpoint Condizionali

A volte è necessario esercitare un maggiore controllo sui punti di interruzione. Immaginate di avere un ciclo che ha completato 1.000 iterazioni, ma siete interessati solo allo stato dell’ultima iterazione:


for (let i = 0; i < 1000; i++) {
  // set breakpoint here
}

Piuttosto che cliccare su resume execution 999 volte, potete cliccare con il tasto destro sulla riga, scegliere Add conditional breakpoint e inserire una condizione come i = 999:

Breakpoint condizionale in Chrome
Breakpoint condizionale in Chrome.

Chrome mostra i breakpoint condizionali in giallo invece che in blu. In questo caso, il punto di interruzione viene attivato solo sull’ultima iterazione del ciclo.

Punti di Log

I punti di log implementano console.log() senza alcun codice! Un’espressione può essere emessa quando il codice esegue qualsiasi riga, ma non ferma l’elaborazione, a differenza di un punto di interruzione.

Per aggiungere un punto di log, cliccate con il tasto destro su qualsiasi linea, scegliete Add log point e inserite un’espressione, ad esempio 'loop counter i', i:

log point in Chrome.
log point in Chrome.

Nell’esempio qui sopra, la console di DevTools emette loop counter i: 0 a loop counter i: 999.

Utilizzare VS Code per il Debug delle Applicazioni Node.js

VS Code, o Visual Studio Code, è un editor di codice gratuito di Microsoft che è diventato popolare tra gli sviluppatori web. L’applicazione è disponibile per Windows, macOS e Linux ed è sviluppata utilizzando tecnologie web nel framework Electron.

VS Code supporta Node.js e ha un client di debug integrato. La maggior parte delle applicazioni possono essere sottoposte a debug senza bisogno di configurazione; l’editor avvierà automaticamente il server e il client di debug.

Aprite il file di partenza (come index.js), attivate il pannello Run and Debug, cliccate sul pulsante Run and Debug e scegliete l’ambiente Node.js. Cliccate su qualsiasi riga per attivare un punto di interruzione mostrato come un’icona a forma di cerchio rosso. Poi, aprite l’applicazione in un browser come prima. VS Code ferma l’esecuzione quando viene raggiunto il punto di interruzione:

Breakpoint di VS Code.
Breakpoint di VS Code.

I riquadri Variables, Watch, Call Stack e Breakpoints sono simili a quelli mostrati in Chrome DevTools. Il pannello Loaded Scripts mostra quali script sono stati caricati, anche se molti sono interni a Node.js.

La barra degli strumenti con le icone delle azioni vi permette di:

  • resume execution: continua l’elaborazione fino al successivo punto di interruzione
  • step over: esegue il comando successivo ma rimane all’interno della funzione corrente – non salta in nessuna funzione che invoca
  • step into: esegue il successivo comando e salta in qualsiasi funzione che chiama
  • step out: continua l’elaborazione fino alla fine della funzione e ritorna al comando chiamante
  • restart: riavvia l’applicazione e il debugger
  • stop: interrompe l’applicazione e il debugger

Come in Chrome DevTools, pome cliccare con il tasto destro del mouse su qualsiasi linea per aggiungere Conditional breakpoints e Log points.

Per maggiori informazioni, fate riferimento a Debugging in Visual Studio Code.

Configurazione Avanzata del Debug di VS Code

Può essere necessaria un’ulteriore configurazione di VS Code se volete eseguire il debug del codice su un altro dispositivo, una macchina virtuale, o se hai bisogno di utilizzare opzioni di lancio alternative come nodemon.

VS Code memorizza le configurazioni di debug in un file launch.json all’interno di una directory .vscode nel vostro progetto. Aprite il pannello Run and Debug, cliccate su create a launch.json file e scegliete l’ambiente Node.js per generare questo file. Viene fornita una configurazione di esempio:

Configurazione debugger VS Code.
Configurazione debugger VS Code.

Possono essere definite come oggetti nell’array "configurations" un numero arbitrario di impostazioni di configurazione. Cliccate su Add Configuration… e selezionate un’opzione appropriata.

Una singola configurazione di Node.js può:

  1. Avviare un processo stesso, o
  2. Collegarsi ad un server Web Socket di debug, magari in esecuzione su una macchina remota o su un container Docker.

Per esempio, per definire una configurazione di nodemon, selezionate Node.js: Nodemon Setup e cambiate lo script di inserimento del “program” se necessario:

{
  // custom configuration
  "version": "0.2.0",
  "configurations": [
    {
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen",
      "name": "nodemon",
      "program": "${workspaceFolder}/index.js",
      "request": "launch",
      "restart": true,
      "runtimeExecutable": "nodemon",
      "skipFiles": [
        "<node_internals>/**"
      ],
      "type": "pwa-node"
    }
  ]
}

Salvate il file launch.json e nodemon (il “nome” della configurazione) appare nell’elenco a discesa nella parte superiore del pannello Run and Debug. Cliccate sull’icona verde di esecuzione per iniziare ad utilizzare quella configurazione e lanciate l’applicazione utilizzando nodemon:

Debug VS Code con nodemon.
Debug VS Code con nodemon.

Come prima, potete aggiungere punti di interruzione, punti di interruzione condizionali e punti di log. La differenza principale è che nodemon riavvia automaticamente il vostro server quando viene modificato un file.

Per ulteriori informazioni, fate riferimento a VS Code Launch configurations.

Anche le seguenti estensioni di VS Code possono aiutarvi a eseguire il debug del codice ospitato su ambienti server remoti o isolati:

  • Remote — Containers: collega alle applicazioni in esecuzione in container Docker
  • Remote – SSH: collega alle applicazioni in esecuzione su un server remoto
  • Remote – WSL: collegati alle applicazioni in esecuzione su Windows Subsystem for Linux (WSL).

Altre Opzioni di Debug di Node.js

La Node.js Debugging Guide fornisce consigli per una serie di editor di testo e IDE, compresi Visual Studio, JetBrains WebStorm, Gitpod ed Eclipse. Atom offre un’estensione node-debug, che integra il debugger di Chrome DevTools nell’editor.

Una volta che la vostra applicazione è attiva, potreste pensare di utilizzare servizi commerciali di debug come LogRocket e Sentry.io, che possono registrare e riprodurre gli errori del client e del server riscontrati da utenti reali.

Riepilogo

Storicamente, il debug di JavaScript è stato sempre difficile, ma ci sono stati enormi progressi negli ultimi dieci anni. La scelta è altrettanto buona – se non migliore – di quelle fornite per altri linguaggi.

Usate qualsiasi strumento trovate pratico per individuare un problema. Non c’è niente di male in console.log() per una rapida ricerca di bug, ma Chrome DevTools o VS Code possono essere preferibili per problemi più complessi. Questi strumenti possono aiutarvi a creare codice più robusto e perderete meno tempo a risolvere i bug.

Su quale pratica di debugging di Node.js scommettete? Condividete le vostre considerazioni nella sezione dei commenti qui sotto!

Craig Buckler

Web developer freelance del Regno Unito, scrittore e divulgatore. È in giro da molto tempo e sproloquia su standard e prestazioni.