Parte II — Il flusso
Capitolo 10 — Quando qualcosa va storto
Gli errori non sono un incidente raro: sono parte della vita di ogni programma. Un file non c'è, un utente scrive «trenta» invece di «30», una divisione cade su uno zero. Un buon programma non pretende che il mondo sia perfetto: prevede gli imprevisti e li gestisce con grazia. Leo ha strumenti chiari per farlo, e — cosa non scontata — messaggi d'errore pensati per essere letti.
💡 L'idea — Un allarme antifumo
Pensa a un'eccezione come all'allarme antifumo di casa: quando sente il pericolo interrompe tutto e lo segnala subito, con un suono che non puoi ignorare. È molto meglio di una casa che brucia in silenzio. Un errore che si fa sentire al momento giusto ti permette di intervenire prima che il guaio si propaghi.
Leggere un errore
Partiamo da un errore non gestito, per imparare a leggerlo. Questo programma divide per zero:
funzione dividi(a, b)
restituisci a / b
fine
scrivi(dividi(10, 0))ErroreDivisione alla riga 2, colonna 19 di programma.leo
2 | restituisci a / b
| ^
Divisione per zero.
Chiamate (dalla più recente):
programma.leo:2 dentro `dividi(a = 10, b = 0)`
programma.leo:4 nel programma principaleOgni messaggio di Leo ha la stessa struttura, e vale la pena imparare a leggerla. In alto: il tipo di errore (ErroreDivisione) e dove è successo (riga 2, colonna 19). Poi la riga di codice incriminata, con un accento circonflesso sotto il punto esatto. Poi la spiegazione in italiano («Divisione per zero.»). E infine, cosa rara e preziosa, l'elenco delle chiamate: da dove è partito tutto. Qui leggiamo che dividi è stato chiamato con a = 10, b = 0 dalla riga 4 del programma principale. Nei programmi grandi questa traccia è una bussola: ti porta dritto all'origine del problema.
🦁 Curiosità — Il primo «bug» era un insetto vero
Nel 1947, cercando un guasto nel calcolatore Harvard Mark II, i tecnici del gruppo di Grace Hopper trovarono una falena incastrata in un relè: la staccarono, la incollarono nel registro di bordo e annotarono «primo caso reale di bug trovato». Da lì chiamiamo «bug» un errore e «debugging» il lavoro di scovarlo. Per onestà: la parola «bug» per un difetto tecnico girava già ai tempi di Edison; questo è però il caso famoso dell'insetto preso con le mani nel sacco.
I tipi di errore più comuni hanno nomi che si spiegano da soli: ErroreNome (una variabile che non esiste), ErroreTipo (operazioni tra tipi incompatibili, come "3" + 4), ErroreValore (il tipo è giusto ma il valore no, come intero("ciao")), ErroreIndice (fuori dai limiti di una lista), ErroreChiave (chiave assente in un dizionario), ErroreFile (file che non si apre).
prova, cattura, infine
Per gestire un errore invece di subirlo, si racchiude il codice rischioso in un blocco prova, e si dice con cattura cosa fare se qualcosa va storto:
prova
variabile n = intero("non un numero")
scrivi(n)
cattura ErroreValore come e
scrivi "Serve un numero: {e.messaggio}"
infine
scrivi "Ho finito di provare."
fineServe un numero: Non posso trasformare il testo "non un numero" in un intero.
Ho finito di provare.Leggiamo il flusso. Leo esegue il blocco prova. La prima riga solleva un ErroreValore (intero non sa cosa farsene di "non un numero"), quindi la riga scrivi(n) viene saltata e il controllo passa a cattura ErroreValore come e. Lì, la variabile e rappresenta l'errore: ha i campi tipo, messaggio, riga, colonna, suggerimento. Infine — ed è letterale — il blocco infine si esegue sempre, sia che ci sia stato un errore sia che tutto sia filato liscio: è il posto giusto per le pulizie (chiudere un file, rilasciare una risorsa).
Puoi avere più cattura, uno per tipo di errore, e vince il primo adatto. Un cattura senza tipo (o cattura come e da solo) cattura qualunque errore: usalo come rete finale, dopo quelli specifici.
Sollevare i propri errori: lancia
Gli errori non li subisci soltanto: puoi anche sollevarli tu, quando il tuo codice incontra una situazione che non deve accadere. Si usa lancia:
funzione controlla_voto(voto)
se voto < 0 o voto > 30 allora
lancia ErroreValore("Il voto deve essere tra 0 e 30")
fine
restituisci "Voto accettato: {voto}"
fine
prova
scrivi(controlla_voto(35))
cattura ErroreValore come e
scrivi "Rifiutato: {e.messaggio}"
fineRifiutato: Il voto deve essere tra 0 e 30lancia ErroreValore("...") interrompe la funzione e propaga l'errore verso chi l'ha chiamata, esattamente come farebbe un errore «naturale». Chi chiama può catturarlo, come qui, o lasciarlo salire. È il modo pulito per dire «questo input non è valido»: meglio un errore chiaro e immediato che un risultato sbagliato portato avanti di nascosto.
Mettere alla prova il codice: verifica
C'è un modo meraviglioso per dormire sonni tranquilli: scrivere, accanto alle funzioni, dei piccoli controlli che ne verificano il comportamento. In Leo si fa con i blocchi verifica "nome" fai ... fine:
funzione doppio(n)
restituisci n * 2
fine
verifica "doppio di un numero" fai
aspettati doppio(21) == 42
aspettati doppio(0) == 0
fine
verifica "un testo non numerico" fai
aspettati errore ErroreValore fai
intero("trenta")
fine
fineQuando esegui normalmente il programma, questi blocchi non partono: sono lì in attesa. Per lanciarli c'è un comando apposito, leo verifica:
leo verifica programma.leoprogramma.leo
ok doppio di un numero
ok un testo non numerico
2 verifiche: 2 riuscite.Dentro un blocco verifica, aspettati condizione controlla che qualcosa sia vero; con un confronto (doppio(21) == 42), se fallisce, il rapporto mostra il valore atteso e quello ottenuto. E aspettati errore TipoErrore fai ... fine controlla che un pezzo di codice dia proprio quell'errore — perfetto per collaudare la gestione dei casi limite. Scrivere verifiche mentre scrivi il codice è una delle abitudini che distinguono i programmi che reggono nel tempo: quando un domani modificherai doppio, le verifiche ti diranno subito se hai rotto qualcosa.
Esiste anche una verifica «al volo», da usare dentro il programma come controllo rapido: verifica doppio(21) == 42 ferma il programma con un errore se la condizione è falsa. È utile per sancire una cosa che deve essere vera a un certo punto dell'esecuzione.
Prova tu
- Scrivi un programma che chiede un numero all'utente e lo raddoppia, ma gestisce con
prova/catturail caso in cui l'utente non scrive un numero valido, stampando un messaggio gentile invece di andare in errore. - Scrivi una funzione
radice_sicura(x)che lancia unErroreValoresexè negativo, e altrimenti restituisceradice(x). Collaudala con un bloccoverifica. - Prendi la funzione
fattorialedel capitolo scorso e aggiungi una verifica che controllifattoriale(5) == 120e chefattoriale(0) == 1.
Finisce qui la Parte II. Hai dato ai tuoi programmi la capacità di decidere, ripetere, organizzarsi in funzioni e reagire agli imprevisti. Nella Parte III impareremo a organizzare programmi più grandi: oggetti, moduli, file.