Null References: The Billion Dollar Mistake

Unikaj przekazywania nulli. Jeśli nie będziesz ich przekazywał nie będziesz musiał sprawdzać czy ich nie masz. Prosto powiedzieć – trudniej zrobić. Proste rzeczy to przekazywanie pustych kolekcji, Null Object Pattern. Jednak to drugie może dobrze działać dla serwisów, ale już gorzej dla Value Objects.

Często pojawiającym się kodem jest wyciąganie obiektów z repozytorium.

var person = _repository.Get(id);
if (person != null)
{
    person.PhoneNumber = phoneNumer;
    _repository.Update(person);
}

Dla nieistniejącego identyfikatora user powinien być nullem i musimy to sprawdzić. Do niedawna myślałem, że w tym przypadku po prostu musimy przekazywać sobie nazwajem nulle. Już tak nie myślę – Avoid Null Checks by Replacing Finders with Tellers. Trzeba co prawda przeorać nasze dotychczasowe myślenie o dobrym flow w programie, ale myślę, że warto się przynajmniej zastanowić.

class Repository
{
    void Person(int id, Action<Person> action)
    {
        if (database.Contains(id))
        {
            var person = database.Get(id);
            action(person);
        }
    }
}
_repository.Person(id, person =>
{
    person.PhoneNumber = phoneNumer;
    _repository.Update(person);
});

Można też wykonać kod, który się wykona gdy nie znajdziemy id w bazie.

void Person(int id, Action<Person> action, Action notFoundAction)
{
  // 
}

Jeśli ktoś uważa, że nie warto unikać aż tak nulli skoro są w języku i zawsze wszyscy z nich korzystali to polecam filmik „twórcy nulla”. Twórca nazywa swóją decyzję “moja pomyłka za miliard dolarów” i tłumaczy to tym że „po prostu było łatwo to zaimplementować”.

http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare

Advertisements
Ten wpis został opublikowany w kategorii Programowanie i oznaczony tagami , , , . Dodaj zakładkę do bezpośredniego odnośnika.

7 odpowiedzi na „Null References: The Billion Dollar Mistake

  1. Pingback: dotnetomaniak.pl

  2. Michał pisze:

    Co do potrzeby unikania null’i to nie sposób się nie zgodzić.
    Podany przykład nie jest chyba jednak dość szczęśliwy bo wcale nie unika nulla. Operacja nie jest atomowa przez co po sprawdzeniu istnienia osoby, w momencie pobierania może już nie istnieć.

  3. rbl pisze:

    W przytoczonym linku jestraczej metoda pobierania kolekcji obiektów, a następnie wołania na nich przetwarzanie metodą foreach. Ale jak napisał jeden z komentatorów: to chowanie „if null” przed programistą a nie usuwanie. Poza tym (bazuje na kolejnym komenarzu), często brak obiektu oznacza błąd, a to nie jest po prostu pominięcie przetwarzania lecz cała związana z tym obsługa.

  4. Użycie callbacka ma pewną ważną wadę programista który go przekazuje zazwyczaj oczekuje że zostanie on kiedyś wywołany (w dodatku często spodziewa się że nie zostanie wywołany natychmiast).
    Moim zdaniem lepszym rozwiązaniem w przypadku Repozytorium byłoby dołożenie metody TryGet, Get natomiast powinien rzucić po prostu wyjątkiem. Wtedy błąd co prawda wystąpi ale w przeciwieństwie do NullReference wystąpi w miejscu które ma w CallStacku przyczynę wystąpienia (NullReference może wystąpić w linijce oddalonej o setki tysiące znaków…

  5. @Michał @rbl muszę to pokazać na innym przykładzie. W „Replacing Finders with Tellers” nie chodzi o przeniesienie jednego if’a w inne miejsca, ale w pozbyciu się tego sprawdzania na wielu poziomach. Muszę dopracować.

  6. @Wojtek „Użycie callbacka ma pewną ważną wadę programista który go przekazuje zazwyczaj oczekuje że zostanie on kiedyś wywołany” – nie rozumiem. Jeśli przekazujesz dwa callbacki jeden zamiast „if” drugi zamiaste „else” to dokładnie jeden zostanie dokładnie za chwilę wykonany.

    „… (w dodatku często spodziewa się że nie zostanie wywołany natychmiast)” – w wywołaniach asynchronicznych zdarza się, że jakiś warunek od razu jest spełniony i callback od razu się wykonuje. Jeśli chcemy polegać, że coś się wykona dopiero za jakiś czas to explicite opóźniamy takie wykonanie. To są chyba dwie różne rzeczy.

    • szogun1987 pisze:

      W twoim przykładzie brakuje jednak else.

      Co do warunków od razu spełnionych uważam że programista który pobrał element np z cache powinien zrobić Dispatcher.BeginInvoke a nie zrzucać taką odpowiedzialność na programiste-klienta. Jako przykład podam tu jquery w którym metody z callbackami często zwracają konfiguratory/buildery pozwalające dość poważnie zmienić sposób wykonania głównej metody, jeżeli metoda wykona się natychmiast nie uwzględni tej konfiguracji.

Możliwość komentowania jest wyłączona.