modulo 02 / lezione 13
Leggere errori e stack trace
Sintassi, runtime, logica: tre voci diverse
Capire perché non tutti gli errori parlano nello stesso modo e alcuni non generano eccezioni. Introdurre il concetto di assert.
In parole semplici
Le tre famiglie di errore introdotte nel modulo 0 (compile-time/sintassi, runtime, logica) hanno tre voci diverse: una grida prima ancora di partire, una grida durante l’esecuzione, la terza non grida affatto. Per gli errori silenziosi serve uno strumento dedicato: l’assertion.
Parole nuove
- eccezione (exception): meccanismo con cui un linguaggio segnala un errore a runtime.
- assertion (assert): controllo esplicito che fallisce se una condizione attesa non è vera.
- invariante: condizione che deve essere sempre vera in un certo punto del programma.
- pre/postcondizione: condizione che deve valere prima/dopo una funzione.
Cosa impari
- Riconoscere la “voce” giusta di ogni famiglia.
- Usare
assertper dare voce agli errori logici. - Distinguere assertion di sviluppo (verifica) da validazione di input.
Le tre voci
Voce 1: sintassi (compile-time)
Forte e immediata, prima ancora dell’esecuzione.
function saluta(nome
{
return "Ciao " + nome;
}
SyntaxError: Unexpected token '{'
L’editor o il parser ti fermano. Non ci sono dubbi né interpretazione.
Voce 2: runtime (eccezione)
Forte ma in differita: emerge quando il codice esegue il punto problematico.
function calcolaSconto(prezzo, percentuale) {
return prezzo * (1 - percentuale / 100);
}
calcolaSconto(undefined, 20);
// NaN ← attenzione: niente eccezione!
In questo caso il runtime non lancia eccezione. Restituisce NaN. Il bug è silenzioso. Andiamo a vedere la terza voce.
Voce 3: logica (silenziosa)
Nessuna eccezione, nessuna parola del runtime. Il programma “funziona”. Solo che il risultato è sbagliato.
function eta(annoNascita) {
return 2024 - annoNascita; // bug: anno hardcoded
}
eta(1990); // 34 nel 2025, dovrebbe essere 35
Nessun errore. Il bug si scopre solo confrontando l’output con quello atteso.
Dare voce alla logica: le assertion
Un assert è una riga che dichiara: “qui questa condizione deve essere vera; se non lo è, ferma tutto e dimmelo”.
In JavaScript / Node
import { strict as assert } from "node:assert";
function eta(annoNascita, annoCorrente) {
assert(typeof annoNascita === "number", "annoNascita deve essere numero");
assert(annoNascita > 1900, "annoNascita troppo basso");
assert(annoNascita <= annoCorrente, "annoNascita nel futuro");
return annoCorrente - annoNascita;
}
eta(undefined, 2025);
// AssertionError: annoNascita deve essere numero
In browser puoi usare console.assert:
console.assert(typeof annoNascita === "number", "annoNascita deve essere numero");
console.assert non interrompe l’esecuzione, ma scrive in console quando fallisce. Per fermare davvero serve throw o assert nativo.
In Python
def eta(anno_nascita, anno_corrente):
assert isinstance(anno_nascita, int), "anno_nascita deve essere int"
assert anno_nascita > 1900
assert anno_nascita <= anno_corrente
return anno_corrente - anno_nascita
In Java / Swift / Go
assert annoNascita > 1900 : "annoNascita troppo basso";
assert(annoNascita > 1900, "annoNascita troppo basso")
precondition(annoCorrente >= annoNascita)
if annoNascita <= 1900 {
panic("annoNascita troppo basso")
}
Assertion ≠ validazione di input
Distinzione importante:
- Validazione input: controlli su dati che vengono dall’esterno (utente, API, file). Fallimento = errore “atteso”, da gestire con messaggi e fallback.
- Assertion: controlli su invarianti del tuo codice. Fallimento = bug, da correggere.
Esempio:
function aggiungiAlCarrello(carrello, prodotto) {
// validazione input: il chiamante potrebbe sbagliare
if (!prodotto || prodotto.prezzo < 0) {
throw new Error("Prodotto non valido");
}
// assertion: la mia logica garantisce che carrello sia un array
assert(Array.isArray(carrello), "carrello deve essere array");
carrello.push(prodotto);
// postcondizione
assert(carrello.length > 0);
return carrello;
}
Quando piazzare assertion
- All’ingresso di una funzione: precondizioni.
- All’uscita: postcondizioni.
- Nei punti dove credi “non potrebbe mai succedere”: è esattamente lì che succedono i bug.
- Quando trovi un bug logico: aggiungi un assert che lo avrebbe colto prima.
Esercizio guidato
Riprendi una funzione che hai scritto in passato. Per ognuna:
- Aggiungi una precondizione su ogni parametro.
- Aggiungi una postcondizione sul valore di ritorno.
- Aggiungi una assertion in un punto interno dove “non dovrebbe mai succedere”.
- Esegui con input volutamente sbagliati e verifica quali assertion scattano.
Errori frequenti del principiante
- Usare assert al posto della validazione input. Il chiamante può essere “sporco”; l’assert è per te.
- Disattivare assertion in produzione. Alcuni linguaggi lo fanno di default (Python con
-O, Java con flag). Sappi cosa stai disattivando. - Lasciare assertion vaghe.
assert(x)senza messaggio non aiuta.assert(x, "x deve essere > 0 dopo normalizzazione")sì. - Pensare che assertion rallentino. In dev sono trascurabili; in produzione si possono disabilitare se serve.