venerdì, maggio 14, 2010

Guard Clauses & Fail Fast

Recentemente mi è capitato di discutere con un amico di come organizzare il codice all'interno di un metodo. La questione è tutto sommato semplice: unico punto di uscita come indicano i sacri testi oppure un approccio più pragmatico? Io sostengo fortemente il secondo.
Spesso il codice che scriviamo ha tantissime condizioni da controllare. Molte di queste sono in realtà condizioni di esecuzione, cioè condizioni che devono essere soddisfatte per poter poi eseguire il lavoro vero e proprio. Ad esempio, controllare che la connessione a DB sia attiva, che un valore numerico sia compreso in un certo intervallo e così via. Questo tipo di condizioni sono quindi più dei "check" o per dirla con Martin Fowler dei "Guard Clause", clausole di salvaguardia. Riporto l'esempio di Fowler. Partiamo da questo pezzo di codice:

double getPayAmount() {
 double result;
 if (_isDead) result = deadAmount();
 else {
  if (_isSeparated) result = separatedAmount();
  else {
   if (_isRetired) result = retiredAmount();
   else result = normalPayAmount();
  };
 }
return result;
};

Ecco come possiamo riscriverlo in modo molto più chiaro con "guard clauses":

double getPayAmount() { 
  if (_isDead) return deadAmount();
  if (_isSeparated) return separatedAmount();
  if (_isRetired) return retiredAmount();
  return normalPayAmount();
};

Questo modo di scrivere codice ha secondo me anche altri vantaggi oltre alla chiarezza. In primo luogo concentra nella parte iniziale tutti i controlli di accesso rendendo più facile il test ed il debug e semplifica l'aggiunta/modifica dei check stessi. Altro vantaggio è che si trova la strada facilitata per implementare un approccio "Fail Fast" (vedere qui l'articolo di Jim Shore).
Fail Fast è un approccio che condivido moltissimo. Il ragionamento di base è molto semplice: meglio un'applicazione che si pianta inaspettatamente che una che va avanti in condizioni precarie nascondendo gli errori. Mi conforta che questo è anche l'approccio scelto per la gestione delle eccezioni di background in .NET 2.0, che cambia radicalmente l'approccio rispetto a NET 1.0 / 1.1 che invece ignorava e nascondeva.

Torniamo a "Clause Guard": metto in testa tutti i controlli; per ogni controllo posso decidere se gestire la condizione e uscire in modo controllato dal metodo oppure se considerare la condizione come anomala e quindi sollevare un Exception.
Certamente Fail Fast è un approccio più ampio che non si limita a sollevare eccezioni nei controlli del codice. Ma indubbiamente i due metodi si sposano molto bene.