4 August 2019

Mit Null umgehen

Durch den Artikel „Result-Pattern statt Exceptions“ im Windows Developer Magazin [1] bin ich auf die Library Functional Extensions for C# [2] gestoßen. Zunächst fand ich das Result-Pattern auch als sehr interessanten Weg, mit verschiedenen erwarteten Fehlern umzugehen und wollte das direkt mal ausprobieren. Die verwiesene Library macht aber einiges mehr, für mich primär interessant ist das Problem mit den Null-Referenzen.

Zunächst einmal zum Result-Pattern selbst. Der Gedanke dahinter ist es, das mögliche Auftreten von Fehlern in einer Result-Struktur zu kapseln. Sprich: Man wirft in bestimmten Fällen keine Exceptions, sondern gibt so etwas wie Result.Error(„…“) zurück. Falls alles gut geht wird stattdessen Result.Ok(…) mit dem tatsächlichen Ergebnis als Argument aufgerufen. Der Gedanke dahinter ist, dass Exceptions wirklich nur im Ausnahmefall kommen und nicht etwa bei fast schon erwarteten Validierungsfehlern.

Das Pattern erlaubt, dass man verschiedene Funktionen, die nach und nach aufgerufen werden sollen, schön verketten und die Fehlerbehandlung dabei vereinfachen kann (Siehe nächsten Code-Ausschnitt). Die Methode ReadCsvFile gibt hierbei eine Result-Struktur zurück, die darauffolgenden OnSuccess-Methoden werden aufgerufen, wenn alles glatt geht. Bei einem Fehler irgendwo in der Kette landet man schließlich in der OnFailure-Methode. Für weitere Infos kann ich direkt auf das Github-Projekt Functional Extensions for C# [2] oder sogar das Pluralsight-Video dazu verweisen [3]. Die Sachen sind definitiv einen Blick wert.

public static void ProcessCsvFile(string filePath)
{
    Console.WriteLine();
    Console.WriteLine($"Start processing file {filePath}");

    CsvFileLoader.ReadCsvFile(filePath)
        .OnSuccess(csvContents => Console.WriteLine($"Read {csvContents.Count} lines"))
        .OnSuccess(csvContents =>
        {
            var stringDict = new Dictionary<string, object>();
            foreach (var actCsvLine in csvContents)
            {
                stringDict[actCsvLine.FirstName] = null;
            }
            Console.WriteLine($"We have {stringDict.Count} different names in the list");
        })
        .OnFailure(error => Console.WriteLine($"Error while reading or processing file: {error}"));
}

Zurück zum Thema, dieser Betrag führt schließlich den Titel „Mit Null umgehen“. Die Ansätze in diese Richtung finde ich in der verwiesenen Library und im Pluralsight-Video dazu höchst interessant. Häufig ist es ja so, dass man ein Argument in eine Methode bekommt und man selbst darin davon ausgeht, dass es nicht null sein kann. Was ist, wenn trotzdem null übergeben wird? Irgendwo kommt eine NullReferenceException und das Programm ist schnell in einem nicht klar definierten Zustand.

In verwiesener Library wird zunächst die Struktur Maybe<T> dazu genutzt, um klar zu beschreiben, welche Parameter null sein dürfen und welche nicht. Darf ein Parameter null sein, dann wird der Maybe<T> Typ verwendet. T ist dabei der echte Typ des Parameters, den man erwartet. Das Gleiche gilt für Rückgabewerte von Funktionen. Ich persönlich halte das für einen sehr eleganten und vor allem schlanken Weg – der Overhead der Maybe<T> Struktur ist schließlich sehr gering. Letzten Endes benötigt die Maybe<T> Struktur auch nur die Referenz zum Objekt als einziges Feld. Ein einfaches Beispiel für eine Methode mit einem Maybe-Übergabeparameter wäre im nächsten Code-Snippet.

public static string DoSomething(Maybe<string> teschterle)
{
    if (teschterle.HasNoValue)
    {
        return "No value given";
    }
    else
    {
        return teschterle + "...";
    }
}

Im Ergebnis bedeutet das: Wenn man null verarbeiten kann, dann Maybe<T>, wenn nicht, dann direkt den Typ des Parameters angeben. Für Ergebnisse einer Methode gilt das Gleiche. Jetzt wäre natürlich denkbar, dass man das gleiche umdreht – sprich eine Struktur wie Maybe<T> schreibt, welche kein null erlaubt. Eine solche gibt es aber in der verwiesenen Library nicht. Stattdessen verweist der Autor Vladimir Khorikov auf die Library Fody.NullGuard [4]. Hier wird der Ansatz verfolgt, während dem Kompilieren bei allen Referenz-Parametern automatisch eine Null-Prüfung einzubauen – außer man deaktiviert das explizit per [AllowNull] Attribut. Im Verbund mit dem Maybe<T> Typ ist das ein sehr interessanter Weg, mit null’s umzugehen.

Insgesamt bin ich nach diesem kurzen Ausflug in dieses Thema sehr überrascht, wie viele Leute sich damit beschäftigen. Tippt man z. B. Maybe oder Option (anstelle von Maybe wird auch gerne Option als Begriff verwendet) und C# in Google ein, bekommt man eine Fülle sehr detaillierter Artikel zum Thema Null. Davon unabhängig steht ein ähnliches Thema mit „Nullable Reference Types“ aktuell auf der Agenda für C# 8.0 [5]. Es steht somit gefühlt eine umfangreiche Auswahl von Werkzeugen bereit, besser mit null-Referenzen umzugehen, als im reinen Standard-Sprachumfang vorgesehen.

Verweise:
[1] Windows Developer, 7-2019. Result-Pattern statt Exceptions
[2] https://github.com/vkhorikov/CSharpFunctionalExtensions
[3] https://www.pluralsight.com/courses/csharp-applying-functional-principles
[4] https://github.com/Fody/NullGuard
[5] https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references


Schlagwörter:
Copyright 2019. All rights reserved.

Verfasst 4. August 2019 von Roland in category "Patterns & Practices

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.