Follow my new blog

Samstag, 1. Mai 2010

Null oder nicht Null, das ist hier die Frage

Sollte man Null als Ergebnis einer Abfrage zurückgeben oder nicht? Diese Frage ergab sich anlässlich eines Blogartikels von Thomas Bandt, der in ein Problem gelaufen war, seiner Gewohnheit folgen zu können, eine Query-Methoden eines Repositories Null zurückliefern zu lassen:

“[Ich] erwarte […] von jeder Methode null als Rückgabewert für den Fall, dass keine Daten in der Datenbank gefunden wurden. Nun ist mir aber beim Testen meiner Repositories […] aufgefallen, dass Folgendes niemals null zurückliefert: […] return […].Select(…).ToList(); […]”

Letztlich hat Thomas zwar eine Lösung für sein Problem gefunden, doch ein Nachgeschmack bleibt. Ist das vielleicht nur eine Symptomkur? Ich würde sagen, ja.

Ob man nie Null zurückgeben darf oder nicht, will ich nicht entscheiden. Solche Allaussagen sind auch selten hilfreich. Soweit stimme ich mit einigen Kommentatoren des Blogartikels überein. Wie steht es allerdings in diesem Fall? Lässt sich im Konkreten doch vielleicht eine saubere Entscheidung treffen?

Für mich steht außer Zweifel, dass im Falle von Thomas´ Repositories die Entscheidung eindeutig für den Verzicht auf Null als Rückgabewert ausfallen sollte. Als Erklärung vier unterschiedliche Ansätze:

  • Philosophisch/Logischer-Blickwinkel: Eine Liste mit Resultaten und Null gehören unterschiedlichen Kategorien an. Eine Liste ist ein Container, Null ist… nichts oder nur ein generischer Wert, jedenfalls kein Container. Dass aber eine Methode Ergebnisse aus zwei Kategorien zurückliefert, halte ich für kategorisch falsch.
    Eine Liste mit Resultaten und eine Liste ohne Resultate (leere Liste) hingegen gehören beide derselben Kategorie Container an.
    In diesem Fall ist Null ganz kategorisch also nicht erlaubt. Anders mag das sein, wo eine Funktion object als Resultattyp hat. Der ist so allgemein, dass eine Zeichenkette, eine Zahl, eine Liste oder eben auch Null in seiner Hinsicht derselben Kategorie Wert angehörten.
  • Praktikscher Blickwinkel: Wer Thomas´ Repository-Query-Methoden benutzt, muss im Code eine Fallunterscheidung treffen. Falls eine Abfragebedingung keine Ergebnisse liefert, muss es zwangsläufig anders weitergehen als mit Resultaten, auch wenn vielleicht nur die Resultate gelistet werden sollen und eine leere Liste am Bildschirm für den Benutzer völlig ok wäre. Thomas´ Muster für Query-Rückgabewerte macht also im Zweifelsfall mehr Aufwand – ohne einen Gewinn zu bringen. Falls leere Liste und gefüllte Liste unterschiedlich behandelt werden sollten, ist die Prüfung auf Null und die auf Länge 0 gleich aufwändig.
  • Prinzipieller Blickwinkel: Ich würde sagen, ein Null-Ergebnis widerspricht dem Principle of Least Astonishment. Denn wenn ich eine Signatur sehe, die eine Liste als Rückgabewert definiert und dann kommt keine Liste, sondern Null zurück, dann bin ich erstmal ziemlich überrascht. Nicht, dass der Gedanke dahinter kompliziert wäre, nicht dass Thomas das nicht in einem XML-Kommentar erklären könnte – aber eine Überraschung bleibt es und deshalb auch ein zusätzlicher Dokumentationsaufwand.
    Auch ist eine Abfrage mit leerer Ergebnismenge keine Besonderheit. Sie rechtfertigt aufgrund ihrer Erwartbarkeit keine Sonderbehandlung durch Null oder gar eine Exception.
  • Analogischer Blickwinkel: Wenn ein Räuber mich zur Übergabe meines Geldes auffordert, dann gebe ich ihm besser mein Portemonais. Falls im Portemonais kein Geld ist, mag er zwar frustriert sein, doch er kann mich zumindest keines Widerstandes bezichtigen. Hätte ich hingegen geantwortet “Ich habe kein Geld.”, dann würde die Szene in jedem Fall sehr unschön werden, denke ich. Allemal würde er mir ohnehin nicht glauben.
    Die Moral von der Geschicht: Versuche der Aufforderung eines Räubers nicht differenziert nachzukommen. Antworte platt so, wie er es sich wünscht und du gleichzeitig klar machst, dass deine Antwort ehrlich ist. Er soll dann selbst aus dem Ergebnis etwas machen.

Soweit mal meine im Grunde doch einfache Lösung des Null-oder-nicht-Null-Problems. Im Zweifelsfall eher auf Null verzichten. Null ist eher “böse” so wie Goto “böse” ist. Und wer das nicht glauben mag, weil Null doch so einfach zu gebrauchen ist, der höre dazu den Erfinder von Null, Tony Hoare, mit seinem Vortrag “Null References: The Billion Dollar Mistake”.

image

Kommentare:

Ilker Cetinkaya hat gesagt…

Ralf,

ich teile Deine Ansichten bzgl. "Nullified Results" weitestgehend. Ich finde es auch gut, dass Du das aus mehreren Blickwinkeln durchleuchtet hast. Es ist leider oft so, dass NULL nicht mit genug bedacht angewendet wird.

Ich denke auch, dass es allgemein nicht von Schaden sein kann, NULL zu meiden. In der heutigen Softwareentwicklung ist das auch durchaus mit einer gewissen Stringenz machbar.

Dennoch möchte ich das *kategorische* "Nein" hier nicht stehen lassen. Es kann durchaus erwünscht und sinnvoll sein, NULL als Rückgabe oder Zuweisung anzuwenden. Nämlich genau dann, wenn es zu einer nicht mehr annehmbaren Ausnahmesituation kommt. Das ist die erste adequate Anwendung von NULL gewesen.

Man mag mir entgegnen, dass es heutzutage Exceptions gibt. Ja, gibt es. Aber bitte Ralf, NULL ist ein Konstrukt in C# und kann (wenn auch bitte, bitte nicht oft und unbedacht) angewendet werden.

Die Anwendung von NULL eignet sich z.B. für intensives traversieren von komplexeren Bäumen oder Abbilden volatiler Kompositionen.

Dennoch möchte ich nochmal betonen: Ja, du hast Recht, es sollte vermieden werden und es ist im Allgemeinen auch als "böse" einzustufen.

Ich durfte die Ausführungen von Sir Tony Hoare auf der QCon'9 erleben und denke, dass Null nicht der milliardenschwere Fehler gewesen ist, den Tony Hoare so plakativ und äußerst spannend in seiner Session erklärt hat. Doch das ist eine andere - längere - Geschichte.

Alles in Allem sehr gut zusammengefasst. Danke!

Ralf Westphal - One Man Think Tank hat gesagt…

@Ilker: Ich denke, wir meinen eigentlich dasselbe.

Aber:

-Ich habe mich nicht kategorisch gegen Null im Allgemeinen gewandt, sondern im konkreten Fall.

-Klar, auch ich verwende Null für nicht vorhandene Äste in einem Baum. Aber das tue ich eher aus alter Gewohnheit denn aus einer Notwendigkeit heraus. Es ist genauso einfach, statt Null einen well-known "EmptyNode" o.ä. dort einzutragen, wo kein anderer Knoten vorhanden ist. Auch das macht Code am Ende nicht komplizierter, sondern sogar in manchen Fällen einfacher. Denn so ein "EmptyNode" kann als echtes "neutrales Element" für Operationen auf einem Baum fungieren. (So wie 0 das neutrale Element für +/- ist.)

Um der Diskussion willen sage ich deshalb mal: In 80% der Fälle, wo mir jmd sagt, seine Verwendung von Null sei nun aber wirklich unumgänglich, kann ich zeigen, dass es ohne Null nicht schlechter, sondern eher besser ist.

-Ralf

Thomas hat gesagt…

Meine Antwort:

http://blog.thomasbandt.de/39/2333/de/blog/null-verstaendnis.html

Grüße

Dejan Siedle hat gesagt…

In F# gibt es einen sogenannten Option - Type. Diesen kann man sich in C# nachbilden. Das Arbeiten mit dem Option - Type ist deutlich angenehmer, auch wenn man die NULL nicht ganz vermeiden kann. Möchte diesen Typen nicht mehr missen :-)

Danke für die Gedanken.

Viele Grüße

Ralf Westphal - One Man Think Tank hat gesagt…

@Dejan: Ja, der Option-Typ in F# ist cool. Wäre eine Alternative zu speziellen "Empty-Objekten".

-Ralf

Ingo hat gesagt…

Ralf,

Wir haben zwar bei vielen Dingen bzgl. Architektur von großen und verteilten Systemen andere Ansichten, aber hier bei der Null-Thematik teile ich deine Sichtweise komplett. Vor allem der erste und der dritte Blickwinkel sind für mich die Hauptargumentationspunkte ...

-Ingo

Ralf Westphal - One Man Think Tank hat gesagt…

@Ingo: Freut mich, dass wir einer Meinung sind.

Und wenn wir in anderen Themen anderer Meinung sind, dann sollten wir das vielleicht mehr diskutieren? Öffentlich? So dass ein Diskurs ensteht? Ich geb ja "Angriffsfläche" hier in meinem Blog.

Das kann mir was bringen - oder auch dir ;-) Und allemal "den Umstehenden", weil sie zwei Seiten hören.

-Ralf

Gerhard hat gesagt…

Hallo Ralf,

als Erstes muss ich bemerken, dass NULL keine Kategorie ist, sondern einfach NICHTS (steht damit als Meta-Wert über den Kategorien??).

Jede "void function" liefert eigentlich NULL als Funktionswert. Eine NULL anstatt eines singulären Funktionswertes ist eigentlich ein klarer Bruch. Die zitierte Option versteckt das nur und ist auch nicht besser.

Selber entwickle ich über 30 Jahre Software und kenne das Problem. Traue keinem Parameter, er könnte NULL sein - dito für Zeiger.

Falls eine Funktion eine Collection zurückgibt, da kann ich die NULL elegent umschiffen, indem eine leere Collection geliefert wird.

Erst heute stand ich vor der alten Frage: Warum geht das mit C# 4.0nicht:

void Sub(string message = string.Empty) {}

Aber

void Sub(string message = null) {}

mag er.
Ich verstehe, warum das so ist :-) Müsste aber nicht sein.

Das zitierte Video streamt gerade zu mir herab, werde ich dann mal anschauen. Es ist mit NULL-References betitelt - wir reden hier über das NULL-Objekt (OK Whats the difference?).

zurück zum NICHTS

Software muss lesbar und wartbar sein. Exceptions sind teuerer als eine NULL im Funktionswert und arbeiten über Nebeneffekte. Eine dokumentierte NULL halte ich für sinnvoller als zusätzliche Klassenkonstrukte, nur um eine NULL zu zu vermeiden. Wo NICHTS ist, ist halt NICHTS.

Das war keine Argumentation für NULL/NICHTS, ev. nur ein nichtiger Denkanstoß.

Aber schön, dass dieses Theme diskutiert wird.