sabato 15 settembre 2012

Come evitare NullReferenceException

Normalmente, quando due classi interagiscono, scriviamo il codice per restituire un valore all' invocazione di un metodo.

Facciamo un esempio:

   var student = repository.GetStudentByCode(1234);
   output.Write(student);

Ora ipotiziamo che non ci sia nessun studente con codice 1234. In questo caso GetStudentByCode restituisce null, quindi passiamo null al metodo Write.

Vediamo come è implementato il metodo Write:

   public class Output   
   {      
      public void Write(Student student)      
      {         
          Console.WriteLine(string.Format("Studente {0} {1}",
                                          student.Lastname,
                                          student.Name));
      }
   }

Ovviamente, otterremo una bella null reference exception; per evitare l'eccezione potremmo verificare se student é diverso da null:

   var student = repository.GetStudentByCode(1234);
   if (student != null)
      output.Write(student);

Questo introduce l'utilizzo di if, potremmo pensare di spostare l'if dentro il metodo Write per cercare di ridurre il prolificare di if prima di chiamare Write. Ma questo comporta la duplicazione del controllo quando creiamo un nuovo metodo per salvare su file i dati dello studente; quindi per ogni metodo che restituisce un valore dovremmo verificare che il valore sia valido. 

Anche intercettare l'eccezione, generata dall'utilizzo di una null reference, non risolvere il nostro problema. Il codice potrebbe avere la seguente forma:

   var student = repository.GetStudentByCode(1234);
   try
   {
      output.Write(student);
   }
   catch (Exception ex )
   {
      // do something
   }

Le problematiche di gestione risultano essere identiche per l'uso dell' if o del try..catch.

Per eliminare la necessità di verificare se il valore restituito da GetStudentByCode è diverso da null, potremmo introdurre un NullObject di tipo Student e quindi modificare GetStudentByCode per restituire NullStudent se la ricerca con matricola 1234 non da un risultato valido.

Questa scelta di design elimina questo if(student!=null) in tutti i punti dove viene utilizzato student.

Putroppo passando NullStudent al metodo Write avremmo un comportamento inaspettato, ad esempio delle righe con solo la scritta "Studente  ". 

Per ovviare a questo problema ed evitare di verificare in diverse parti del nostro codice se student é diverso da null, cambiamo la signature di GetStudentByCode e la definiamo come segue:

   public Student GetStudentByCode(int code)

diventa:

   public void UseStudentFoundByCode(int code,
                                     IStudentSubstitute substitute)
   {
      var student = _studentsDatabase.GetByCode(code);
      if (student == null)
         return;
      substitute.Perform(student);
   }

Ora refattoriziamo la classe Output in modo che implementi l'interfaccia IStudentSubstitute.

   public class OutputIStudentSubstitute
   {
      public void Perform(Student student)
      {
         Console.WriteLine(string.Format("Studente {0} {1}",
                                         student.Lastname, 
                                         student.Name));
      }
   }

A questo punto il codice chiamante può essere modificato da: 

   var student = repository.GetStudentByCode(1234);
   if (student != null)
      output.Write(student);

diventa:

   repository.UseStudentFoundByCode(1234,new Output());

Come possiamo notare il codice si è ridotto e non c'è possibilità che gli if (per il controllo di Student uguale a null) si prolifichino.