Krótki wpis o pracy z nullami

Czas czytania ~ 220 sekund

Dziś  bohaterem numer jeden będzie coś, za czym chyba nieszczególnie przepadamy. A właściwie przyjrzymy się różnym możliwym technikom walki z czymś w stylu

aaa

Pomijając zbędne podstawy, np. takie:

bb

zerknijmy na kilka bardziej praktycznych fragmentów kodu.

C# if-else

Pierwsza kwestia to zagnieżdżenia. Sprawdzanie, czy coś jest null-em często prowadzi do zagnieżdżania kodu, im więcej takich sprawdzeń, tym więcej wcięć, często w kodzie mamy tak.

public IActionResult GetResults(string userId)
{
    if(userId != null)
    {
        //do sth
        return View();
    }
    else
    {
        return BadRequest();
    }            
}

Powód jest prosty: programista myśli o optymistycznej ścieżce, ale jeśli z jakichś przyczyn będzie tu więcej logiki, to możemy dojść do większej liczby zagnieżdżeń. Dlatego jednak chyba lepiej jest tak:

public IActionResult GetResults(string userId)
{
    if(userId == null)
    {
        return BadRequest();
    }
    //do sth
    return View();           
}

Atrybuty i wyjątki

Wiele bibliotek waliduje parametry wejściowe metod sprawdzając, czy parametr jest równy null i rzucając jawnie wyjątkiem ArgumentNullException. Dla przykładu, tak wygląda konstruktor ConcurrentQueue z .NET Core:

public ConcurrentQueue(IEnumerable<T> collection)
{
    if (collection == null)
    {
        throw new ArgumentNullException(nameof(collection));
    }

    InitializeFromCollection(collection);
}

Dzięki temu nie musimy się dalej w kodzie martwić o to, że z jakichś przyczyn przedostał się tam null.

Można również w drugą stronę, przykładowo EF Core pozwala skonfigurować logowanie, a jednym z możliwych parametrów metody UseLoggerFactory jest null właśnie, do czego zachęca atrybut CanBeNullAttribute.

nono

Null coalescing i null condition

Inną metodą walki z null-ami jest podstawienie wartości. Przykładem na wyższym poziomie abstrakcji jest wzorzec projektowy Null Object, którego przykład można zobaczyć tutaj. Mamy również w C# null-coalescing operator, czyli dwa pytajniki.

var name = dto.Name ?? "Anonymous";

Od C# 6 mamy także operator ?. czyli null condition. Ma on zastosowanie wszędzie tam, gdzie nie jesteśmy pewni, czy odwołanie do referencji przez kropkę w kodzie nie rzuci wyjątkiem NullReference. Zobaczmy na przykład, jak można połączyć oba operatory:

var myVariable = args.FirstOrDefault()?.ToUpper() ?? "A";

Co to robi ? Pobiera pierwszy element z kolekcji args, sprawdza, czy jest nullem, jeśli nie to robi na nim uppercase, jeśli tak to przypisuje domyślną wartość. Wszystko w jednej linijce. Po dekompilacji (na przykład przez dotPeek) mamy cały ten brzydki kod, którego udało nam się uniknąć:

wdwdad

Pracując z kodem wokół Entity Framework często mamy relacje jeden do wielu, które wprost przekładają się na kolekcje z zagnieżdżonymi kolekcjami (Navigation Properties). Dzięki powyższemu operatorowi możemy sobie stworzyć odpowiednik metody String.IsNullOrEmpty:

var usersWithKnownPhoneNumbers = users.Where(user => user.PhoneNumbers?.Any() ?? false);

T-SQL

Null – coalsecing można również wykonywać w SQL-u. Transact SQL zawiera metodę COALESCE, która sprawdza, czy pierwszy argument jest różny od null i jeśli tak to zwraca jego, w przeciwnym przypadku zwracany będzie drugi argument.

SELECT TOP 1000 [Id]
      ,[Email]
      ,[IsActive]
      ,COALESCE([IsReadOnly],1) AS IsReadOnly
      ,[UserName]
  FROM [MyAspNetApplication].[dbo].[AspNetUsers]

JavaScript

W JavaScript za null-coalescing (i undefine-coalescing) jest odpowiedzialny logiczny OR (||). Zachowanie jego będzie bardzo podobne do dwóch pytajników, przy czym należy pamiętać o regułach konwersji typów. Dodatkowo możemy sobie stworzyć coś na kształt null -checking, korzystając z faktu, ze w JS odwołanie do klucza, którego nie ma w obiekcie zwraca undefined.

var myVariable = ((args || [])[0] || {}).myProperty || "A";

Bardzo bezpieczne, średnio czytelne, ale działa. W najgorszym scenariuszu już args jest równe undefined, w najlepszym args jest tablicą obiektów, w której pierwszy element ma property myProperty.

F# i null

5775-capture

Oczywiście jest to tylko żart, ale rzeczywiście w F# nie możemy przypisać do zmiennej wartości null. Nie oznacza to, że null się nigdy w runtime nie pojawi, wszystko zależy od tego, z jakich frameworków i zależności używamy.

Jeśli chcemy zezwolić na to, by dany typ umożliwiał wartości null musimy podpowiedzieć kompilatorowi. Przykładem użycia może być typ DTO mapowany z query stringa w Web API. Parametry są opcjonalne stąd domyślną wartością musi być null.

[<AllowNullLiteral>]
type FtsQueryModel() =
    member val q:string = null with get, set
    member val priceTo = defaultof<float> with get, set

 

Option types

A co jeśli jednak lubimy te null-e bo dzięki nim możemy jawnie wskazać, że coś nie ma wartości?  F# zawiera bardzo ciekawą alternatywę w postaci Option types. Jest to coś w stylu wrappera nad obiektem, który może przyjmować jeden z dwóch stanów: None jako brak ustawionej wartości, lub Some(x) jako stan przeciwny. Przykład użycia takiej zmiennej typu Option:

match html with
    | Some htmlDoc -> parseHtml htmlDoc
    | None - > None

Kompilator F# wymaga tutaj obsłużenia optymistycznej i pesymistycznej ścieżki.

Advertisements

One thought on “Krótki wpis o pracy z nullami

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s