To be null or not to be null

Når du indlæser data fra en database bliver du som regel nødt til at forholde dig til null problematikken, fordi alle kolonner i en database kan potentielt tillade værdien DBNull. Udfordringer ligger i:

  1. Værdityper kan ikke være null.
  2. Objekter kan være null, men der skal tjekkes mange steder for null værdi. Det er i mod den objektorienterede tankegang, at sprede null tjeks og håndtering af null situationer over hele koden.

 

Løsning punkt 1.

Ved .NET 2.0 blev Nullable<> introduceret. Dette tillader at definere værdityper, som kan være null. Dette hjælper et godt stykke på vejen, men ligestiller værdityperne med objekterne i denne sammehæng. Dvs. anvendelse af Nullable<> giver nye muligheder, men introducerer sandsynligvis mange null tjeks i koden. En mulig løsning ville være at indkapsle alle anvendte værdityper i et objekt og ellers anvende løsning til punkt 2 på dette. Det giver dermed også mulighed om at anvende kontrakter på de definierede datatyper. Mere om det i et senere post.

 

Løsning punkt 2.

Anvend Martin Fowlers null object refactoring. Jeg vil beskrive det i det følgende. Vær dog opmærksom på at det er meget vanskeligt at introducere denne refactoring i kørende kode. Selv hvis der findes en del unittest, er det meget risikabelt. Du introducerer med store sandsynlighed en del NullReferenceExceptions.

 

Null object refactoring

[Kilde:  Fowler, side 261-262]

Ideen: I stedet for at have én klasse, implementeres to klasser. Denne ene er den almindige, ikke-null klasse, og den anden har samme type er null klassen. Dermed tilpasses og samles funktionalitet mht. de to situationer. Ikke-null klassen har en nedarvede klasse, som implementerer nogle/ alle metoder som superklassen. Denne nedarvede null klasse har dog enten tomme metoder, eller implementerer fejlhåndtering i metoderne. Det er jo metoderne af null klassen, som bliver kaldt, når objektet er null.

Implementering:

  1. Create a subclass of the source class to act as a null version of the class. Create an isNull operation on the source class and the null class. For the source class it should return false, for the null class it should return true.
  2. Find all places that can give out a null when asked for a source object. Replace them to give out a null object instead.
  3. Find all places that compare a variable of the source type with null and replace them with a call to isNull.
  4. Look for cases in which clients invoke an operation if not null and do some alternative behavior if null.
  5. For each of these cases override the operation in the null class with the alternative behavior.
  6. Remove the condition check for those that use the overridden behavior, compile, and test.

 

Vær opmærksom på at det stadigvæk kan går ‘gældt’, hvis du ikke anvender null tjeks og har ‘glemt’ at override en metode, som kan blev kaldt på dit null objekt. Du kan evt. løse dette vha. af en fælles, abstract superklasse. Alle metoder, som skal tvinges til at have både null og ikke-null definitioner, markeres som abstract. Således skal du implementere begge versioner.

 

Simple eksempel med en null og en ikke-null klasse:

    1 using System;

    2 

    3 namespace NullObject.Simple {

    4     /// <summary>

    5     /// Nullable interface. Indikerer at denne type har en null pendant.

    6     /// </summary>

    7     interface INullable {

    8         bool IsNull { get; }

    9     }

   10 

   11     /// <summary>

   12     /// Ikke-null klassen.

   13     /// </summary>

   14     class Underfag : INullable {

   15         public virtual decimal UnderfagId {

   16             get { return 34; }

   17         }

   18 

   19 

   20         #region INullable Members

   21 

   22         public virtual bool IsNull {

   23             get { return false; }

   24         }

   25 

   26         #endregion

   27     }

   28 

   29     /// <summary>

   30     /// Null klassen.

   31     /// </summary>

   32     class UnderfagNull : Underfag {

   33         public override decimal UnderfagId {

   34             get {

   35                 // Skriv til log filen.

   36                 return -1;

   37             }

   38         }

   39 

   40         public override bool IsNull {

   41             get { return true; }

   42         }

   43     }

   44 }

Leave a Reply

Your email address will not be published. Required fields are marked *