Vai al contenuto
Leo

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:

Leo
funzione dividi(a, b)
    restituisci a / b
fine
scrivi(dividi(10, 0))
Risultato
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 principale

Ogni 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:

Leo
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."
fine
Risultato
Serve 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:

Leo
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}"
fine
Risultato
Rifiutato: Il voto deve essere tra 0 e 30

lancia 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:

Leo
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
fine

Quando esegui normalmente il programma, questi blocchi non partono: sono lì in attesa. Per lanciarli c'è un comando apposito, leo verifica:

Risultato
leo verifica programma.leo
Risultato
programma.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

  1. Scrivi un programma che chiede un numero all'utente e lo raddoppia, ma gestisce con prova/cattura il caso in cui l'utente non scrive un numero valido, stampando un messaggio gentile invece di andare in errore.
  2. Scrivi una funzione radice_sicura(x) che lancia un ErroreValore se x è negativo, e altrimenti restituisce radice(x). Collaudala con un blocco verifica.
  3. Prendi la funzione fattoriale del capitolo scorso e aggiungi una verifica che controlli fattoriale(5) == 120 e che fattoriale(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.