modulo 00 / lezione 02

Fondamenta del debug

Compile-time, runtime, logico: tre famiglie di errori

Capire perché alcuni errori fermano il programma prima di partire, altri durante l'esecuzione, altri restano nascosti pur producendo un risultato.

45 min Debugging

In parole semplici

Gli errori non parlano tutti con la stessa voce. Alcuni urlano subito e bloccano il programma prima ancora che parta. Altri attendono il momento giusto durante l’esecuzione per emergere. I peggiori restano in silenzio: il programma sembra funzionare, ma il risultato è sbagliato.

In questa lezione classifichiamo gli errori in tre famiglie così da sapere, ancora prima di leggere il messaggio, dove guardare.

Parole nuove

  • compile-time (o parse-time): momento in cui il codice viene tradotto, analizzato o caricato.
  • runtime: momento in cui il codice viene effettivamente eseguito.
  • errore logico (o semantico): il programma gira senza protestare ma produce un risultato sbagliato.
  • eccezione (exception): meccanismo con cui un linguaggio segnala un errore a runtime.
  • assertion: controllo esplicito che fallisce se una condizione attesa non è vera.

Cosa impari

  • Riconoscere a quale famiglia appartiene un errore solo dal momento in cui appare.
  • Capire perché tipi e linter intercettano cose che a runtime sarebbero invisibili.
  • Sapere che gli errori logici si scoprono solo con test, asserzioni o occhio critico, non da soli.

Famiglia 1: errori di compile-time / parse-time

Il programma non parte affatto. Il sistema rifiuta il codice prima di eseguirlo.

function saluta(nome {
  return "Ciao " + nome;
}

Risultato in Node:

SyntaxError: Unexpected token '{'

Caratteristiche:

  • Velocità: si scoprono subito, anche senza eseguire.
  • Costo: bassissimo. L’editor o il compilatore li mostra in pochi secondi.
  • Esempi: parentesi mancanti, parole chiave sbagliate, tipo non assegnabile (TypeScript), import inesistente.

In JavaScript, che non è compilato come Java o Swift, il parser fa lo stesso lavoro: legge il file, non lo capisce, rifiuta tutto.

Famiglia 2: errori di runtime

Il programma parte e gira. Poi, a un certo punto, qualcosa cede.

function totale(prezzi) {
  return prezzi.reduce((somma, p) => somma + p.valore, 0);
}

totale(null);

Risultato:

TypeError: Cannot read properties of null (reading 'reduce')

Caratteristiche:

  • Velocità: dipendono dai dati e dal cammino di esecuzione.
  • Costo: medio. Spesso compaiono solo con certi input.
  • Esempi: accesso a null/undefined, divisione per zero, file mancante, rete giù, conversione di tipo a runtime.

Sono i più comuni nel debug quotidiano. Hanno un vantaggio: parlano. Lasciano stack trace, messaggi, codici. La famiglia 3 invece tace.

Famiglia 3: errori logici

Il programma non protesta. L’output esiste. Solo che è sbagliato.

function mediaPrezzi(prodotti) {
  let somma = 0;
  for (const p of prodotti) {
    somma += p.prezzo;
  }
  return somma / prodotti.length + 1;
}

Niente errori. Ma la media è sempre +1 rispetto al valore corretto. L’unico modo per accorgersene è confrontare l’output atteso con quello osservato.

Caratteristiche:

  • Velocità: zero allarmi automatici.
  • Costo: alto. Si scoprono solo con test, revisioni, segnalazioni utente.
  • Esempi: ordine sbagliato dei parametri, condizione < invece di <=, fuso orario non gestito, arrotondamento errato.

Tabella di riconoscimento rapido

FamigliaQuando appareChi la trovaEsempio classico
compile-timeprima di partireparser, type checker, linterSyntaxError, tipi incompatibili
runtimementre giraruntime del linguaggionull.something, file mancante
logicomai da solotest, occhio umanomedia sbagliata, off-by-one

Esercizio guidato

In un piccolo programma JavaScript (o nel linguaggio che preferisci) introduci uno errore per famiglia:

  1. Compile-time: dimentica una parentesi.
  2. Runtime: passa null a una funzione che si aspetta un array.
  3. Logico: cambia < in <= in una funzione che conta elementi entro una soglia.

Per ogni errore prendi nota di:

  • Dove appare (editor, terminale, browser, output finale).
  • Cosa dice (messaggio esatto se c’è).
  • Quanto tempo ci hai messo a notarlo senza aiuto esterno.

Errori frequenti del principiante

  • Chiamare “bug strano” ogni errore logico. Non è strano: è solo silenzioso. Servono test che lo facciano gridare.
  • Aspettarsi che il browser mostri tutti gli errori. Molti errori logici non producono nulla in console.
  • Pensare che “se compila, funziona”. Compile-time esclude solo la prima famiglia. Le altre due restano intere.