Eccezioni: prevedere l’imprevedibile.

Sale qb. Questa è la parte più antipatica di ogni ricetta di cucina per chi non ha esperienza di cuoco. Di solito arriva al fondo dell’elenco ingredienti. Cosa significa qb? Beh sì, significa “quanto basta”, ma a quanto corrisponde? Potremmo dire che il calcolo del valore della variabile qb procede per approssimazioni successive:

1. Assaggia l'impasto
2. È buono?
2A. Sì, allora non aggiungere sale, la ricetta è terminata
2B. No, allora aggiungi un pizzico di sale e torna al punto 1

Facile no? Magari è una procedura un po’ lenta e magari in alcuni casi si finisce per mangiarsi gran parte dell’impasto prima di raggiungere la quantità corretta di sale, ma nella maggior parte dei casi funziona. Non prevede però tutti i casi. Cosa succede se l’impasto è già troppo salato in partenza? Aggiungo zucchero? Butto via tutto e riparto da capo? Uso l’impasto per la cena del cane? Procediamo per passi. Prima di tutto traduciamo in codice la procedura descritta sopra. Faccio notare che ci sarebbero molti modi per tradurla in codice, questo è solo uno dei tanti:

public void adjustSalt()
{
  Recipe r = getRecipe();
  if (r.hasGoodTaste())
    return;
  else
  {
    r.addSalt(0.6);
    ajustSalt();
  }
}

Chiariamo subito che questo codice è incompleto, nulla si dice di cosa sia la classe Recipe o di come siano fatte le varie funzioni che vengono chiamate come se esistessero, a parte la adjustSalt() che è l’unica definita. Inoltre 0.6 è un numero arbitrario che ho deciso che significa “un pizzico di sale”. Quello che ho scritto è una via di mezzo fra codice Java e pseudocodice, serve solo a rendere l’idea ma così com’è non funzionerà mai da nessuna parte. È comunque utile ad illustrare il concetto.
Il problema di questa funzione è il fatto di dare per scontato che per rendere buono l’impasto basti aggiungere sale fino a quando la funzione hasGoodTaste() non ritorna true. Se la funziona hasGoodTaste() ritorna false, in realtà noi sappiamo che l’impasto non è buono, ma non sappiamo veramente il perché.
Un’alternativa potrebbe essere:

public void adjustSalt()
{
  Recipe r = getRecipe();
  try
  {
    r.taste();
    return;
  }
  catch(InsipidException ie)
  {
    r.addSalt(0.6);
  }
  catch(SaltyException se)
  {
    r.addPotato(0.4);
    r.addTomato(0.2);
    r.addCream(0.1);
    r.addWater(0.1);
  }
  adjustSalt();
}

Osserviamo che in questo codice non c’è più l’istruzione if. Al suo posto c’è una try, che significa: “prova a fare quel che ti dico, se succede qualcosa di strano, vediamo poi come arrangiarci”. Quello che si richiede di fare all’interno della try è assaggiare l’impasto, ovvero la chiamata alla funzione taste(). Se questa funzione termina il suo compito senza che accada nulla di strano, significa che l’impasto è buono: il programma prosegue normalmente con l’istruzione successiva che è una return e così la funzione adjustSalt() termina e il controllo ritorna al chiamante. Se invece l’impasto non è buono, la taste() lancia un’eccezione. La differenza, rispetto a prima, è che l’eccezione può essere ti qualsiasi tipo e portare con sè molte informazioni circa il problema che si è verificato, mentre prima il valore di ritorno della hasGoodTaste() poteva essere solo vero o falso, senza tanti perché.
L’istruzione try deve essere seguita da una o più istruzioni catch (non è del tutto vero, ma per ora prendiamolo per vero). Ogni istruzione catch specifica un tipo di problema che si può verificare all’interno del blocco try e la relativa soluzione. Per esempio se l’impasto è insipido (InsipidException) la soluzione è aggiungere sale, mentre se è già troppo salato la soluzione è stemperarlo con patate, pomodori, panna ed acqua.

Ci sono molti altri vantaggi nell’usare le eccezioni invece dei valori di ritorno contenenti codici di errore, ma per ora mi fermo qui. Il resto ve lo dirò in un prossimo post.