Ist es besser, null oder leere Sammlung zurückzugeben?

420

Das ist eine Art allgemeine Frage (aber ich verwende C #). Was ist der beste Weg (Best Practice)? Geben Sie für eine Methode, die eine Sammlung als Rückgabetyp hat, null oder leere Sammlung zurück?

Omu
quelle
5
Ähm, nicht ganz CPerkins. rads.stackoverflow.com/amzn/click/0321545613
3
@ CPerkins - Ja, das gibt es. Dies ist in den eigenen .NET Framework-Designrichtlinien von Microsoft klar angegeben. Einzelheiten finden Sie in der Antwort von RichardOD.
Greg Beech
51
NUR wenn die Bedeutung "Ich kann die Ergebnisse nicht berechnen" lautet, sollten Sie null zurückgeben. Null sollte niemals die Semantik "leer" haben, nur "fehlt" oder "unbekannt". Weitere Details in meinem Artikel zum Thema: blogs.msdn.com/ericlippert/archive/2009/05/14/…
Eric Lippert
3
Bozho: "any" ist eine Menge Sprachen. Was ist mit Common Lisp, wo die leere Liste genau dem Nullwert entspricht? :-)
Ken
9
Tatsächlich handelt es sich beim "Duplikat" um Methoden, die ein Objekt und keine Sammlung zurückgeben. Es ist ein anderes Szenario mit unterschiedlichen Antworten.
GalacticCowboy

Antworten:

499

Leere Sammlung. Immer.

Das ist scheiße:

if(myInstance.CollectionProperty != null)
{
  foreach(var item in myInstance.CollectionProperty)
    /* arrgh */
}

Es wird als bewährte Methode angesehen, NIEMALS zurückzukehren, nullwenn eine Sammlung oder eine Aufzählung zurückgegeben wird. Geben Sie IMMER eine leere Aufzählung / Sammlung zurück. Es verhindert den oben genannten Unsinn und verhindert, dass Ihr Auto von Mitarbeitern und Benutzern Ihrer Klassen angeregt wird.

Wenn Sie über Eigenschaften sprechen, stellen Sie Ihre Eigenschaft immer einmal ein und vergessen Sie sie

public List<Foo> Foos {public get; private set;}

public Bar() { Foos = new List<Foo>(); }

In .NET 4.6.1 können Sie dies ziemlich stark komprimieren:

public List<Foo> Foos { get; } = new List<Foo>();

Wenn Sie über Methoden sprechen, die Enumerables zurückgeben, können Sie einfach eine leere Enumerable anstelle von null... zurückgeben.

public IEnumerable<Foo> GetMyFoos()
{
  return InnerGetFoos() ?? Enumerable.Empty<Foo>();
}

Die Verwendung Enumerable.Empty<T>()kann als effizienter angesehen werden, als beispielsweise eine neue leere Sammlung oder ein neues Array zurückzugeben.

Ryan Lundy
quelle
24
Ich stimme Will zu, aber ich denke, dass "immer" etwas übertrieben ist. Während eine leere Sammlung "0 Elemente" bedeuten kann, kann die Rückgabe von Null "überhaupt keine Sammlung" bedeuten - z. Wenn Sie HTML analysieren und nach einem <ul> mit id = "foo" suchen, kann <ul id = "foo"> </ ul> eine leere Sammlung zurückgeben. Wenn es kein <ul> mit id = "foo" gibt, wäre eine Null-Rückgabe besser (es sei denn, Sie möchten diesen Fall mit einer Ausnahme behandeln)
Patonza
30
Es geht nicht immer darum, ob "Sie ein leeres Array leicht zurückgeben können" oder nicht, sondern ob ein leeres Array im aktuellen Kontext irreführend sein könnte. Ein leeres Array bedeutet tatsächlich etwas, ebenso wie null. Zu sagen, dass Sie immer ein leeres Array anstatt null zurückgeben sollten, ist fast so falsch wie zu sagen, dass eine boolesche Methode immer true zurückgeben sollte. Beide möglichen Werte vermitteln eine Bedeutung.
David Hedlund
31
Sie sollten eigentlich lieber System.Linq.Enumerable.Empty <Foo> () anstelle eines neuen Foo [0] zurückgeben. Es ist expliziter und spart Ihnen eine Speicherzuordnung (zumindest in meiner installierten .NET-Implementierung).
Trillian
4
@Will: OP: "Geben Sie eine null oder leere Sammlung für eine Methode zurück , die eine Sammlung als Rückgabetyp hat". Ich denke in diesem Fall, ob es so wichtig ist IEnumerableoder ICollectionnicht. Wie auch immer, wenn Sie etwas vom Typ auswählen, geben ICollectionsie auch zurück null... Ich möchte, dass sie eine leere Sammlung zurückgeben, aber ich bin auf sie gestoßen null, also dachte ich, ich würde es hier nur erwähnen. Ich würde sagen, der Standardwert einer Sammlung von Aufzählungszeichen ist leer und nicht null. Ich wusste nicht, dass es ein so sensibles Thema ist.
Matthijs Wessels
7
Verwandte Themen (CodeProject-Artikel): Ist es wirklich besser, eine leere Liste anstelle von null zurückzugeben?
Sampathsris
154

Aus den Framework Design Guidelines 2nd Edition (S. 256):

Geben Sie KEINE Nullwerte von Sammlungseigenschaften oder von Methoden zurück, die Sammlungen zurückgeben. Geben Sie stattdessen eine leere Sammlung oder ein leeres Array zurück.

Hier ist ein weiterer interessanter Artikel über die Vorteile der Nichtrückgabe von Nullen (ich habe versucht, etwas in Brad Abrams Blog zu finden, und er hat auf den Artikel verlinkt).

Bearbeiten - wie Eric Lippert jetzt die ursprüngliche Frage kommentiert hat, möchte ich auch auf seinen ausgezeichneten Artikel verlinken .

RichardOD
quelle
33
+1. Es wird immer empfohlen, die Richtlinien für das Framework-Design zu befolgen, es sei denn, Sie haben einen sehr guten Grund, dies nicht zu tun.
@ Will, absolut. Dem folge ich. Ich habe nie eine Notwendigkeit gefunden, etwas anderes zu tun
RichardOD
Ja, dem folgst du. Aber in Fällen, in denen APIs, auf die Sie sich verlassen, nicht folgen, sind Sie "gefangen";)
Bozho
@ Bozho-yeap. Ihre Antwort liefert einige nette Antworten auf diese Randfälle.
RichardOD
90

Hängt von Ihrem Vertrag und Ihrem konkreten Fall ab . Im Allgemeinen ist es am besten, leere Sammlungen zurückzugeben , aber manchmal ( selten) ):

  • null könnte etwas Spezifischeres bedeuten;
  • Ihre API (Vertrag) könnte Sie zur Rückkehr zwingen null .

Einige konkrete Beispiele:

  • Eine UI-Komponente (aus einer Bibliothek außerhalb Ihrer Kontrolle) rendert möglicherweise eine leere Tabelle, wenn eine leere Sammlung übergeben wird, oder überhaupt keine Tabelle, wenn null übergeben wird.
  • In einem Object-to-XML (JSON / was auch immer) nullwürde bedeuten, dass das Element fehlt, während eine leere Sammlung ein redundantes (und möglicherweise falsches) Element rendern würde.<collection />
  • Sie verwenden oder implementieren eine API, die explizit angibt, dass null zurückgegeben / übergeben werden soll
Bozho
quelle
4
@ Bozho- einige interessante Beispiele hier.
RichardOD
1
Ich würde Ihren Standpunkt irgendwie verstehen, obwohl ich nicht denke, dass Sie Ihren Code um einen anderen Fehler herum aufbauen sollten und per Definition 'null' in c # niemals etwas Bestimmtes bedeutet. Der Wert null ist als "keine Information" definiert und daher ist es ein Oximoron zu sagen, dass er spezifische Information enthält. Aus diesem Grund legen die .NET-Richtlinien fest, dass Sie einen leeren Satz zurückgeben sollten, wenn der Satz tatsächlich leer ist. Wenn Sie null zurückgeben, heißt das: "Ich weiß nicht, wohin der erwartete Satz gegangen ist"
Rune FS
13
nein, es bedeutet "es gibt keine Menge" anstatt "die Menge hat keine Elemente"
Bozho
Früher habe ich das geglaubt, dann musste ich viel TSQL schreiben und habe gelernt, dass das nicht immer der Fall ist, heheh.
1
Der Teil mit Object-To-Xml ist
genau richtig
36

Es gibt noch einen weiteren Punkt, der noch nicht erwähnt wurde. Betrachten Sie den folgenden Code:

    public static IEnumerable<string> GetFavoriteEmoSongs()
    {
        yield break;
    }

Die C # -Sprache gibt beim Aufrufen dieser Methode einen leeren Enumerator zurück. Um mit dem Sprachdesign (und damit den Erwartungen des Programmierers) übereinzustimmen, sollte daher eine leere Sammlung zurückgegeben werden.

Jeffrey L Whitledge
quelle
1
Ich glaube technisch nicht, dass dies eine leere Sammlung zurückgibt.
FryGuy
3
@FryGuy - Nein, tut es nicht. Es gibt ein Enumerable-Objekt zurück, dessen GetEnumerator () -Methode einen Enumerator zurückgibt, der (analog zu einer Sammlung) leer ist. Das heißt, die MoveNext () -Methode des Enumerators gibt immer false zurück. Das Aufrufen der Beispielmethode gibt weder null noch ein Enumerable-Objekt zurück, dessen GetEnumerator () -Methode null zurückgibt.
Jeffrey L Whitledge
31

Leer ist viel verbraucherfreundlicher.

Es gibt eine klare Methode, um eine leere Aufzählung zu erstellen:

Enumerable.Empty<Element>()
George Polevoy
quelle
2
Danke für den tollen Trick. Ich habe eine leere Liste <T> () wie ein Idiot instanziiert, aber das sieht viel sauberer aus und ist wahrscheinlich auch ein bisschen effizienter.
Jacobs Data Solutions
18

Es scheint mir, dass Sie den Wert zurückgeben sollten, der im Kontext semantisch korrekt ist, was auch immer das sein mag. Eine Regel mit der Aufschrift "Immer eine leere Sammlung zurückgeben" erscheint mir etwas simpel.

Angenommen, wir haben beispielsweise in einem System für ein Krankenhaus eine Funktion, die eine Liste aller früheren Krankenhausaufenthalte der letzten 5 Jahre zurückgeben soll. Wenn der Kunde nicht im Krankenhaus war, ist es sinnvoll, eine leere Liste zurückzugeben. Was aber, wenn der Kunde diesen Teil des Zulassungsformulars leer gelassen hat? Wir brauchen einen anderen Wert, um "leere Liste" von "keine Antwort" oder "weiß nicht" zu unterscheiden. Wir könnten eine Ausnahme auslösen, aber dies ist nicht unbedingt eine Fehlerbedingung und führt uns nicht unbedingt aus dem normalen Programmablauf heraus.

Ich war oft frustriert von Systemen, die nicht zwischen Null und Keine Antwort unterscheiden können. Ich wurde mehrmals von einem System aufgefordert, eine Zahl einzugeben. Ich gebe Null ein und erhalte die Fehlermeldung, dass ich einen Wert in dieses Feld eingeben muss. Ich habe es gerade getan: Ich habe Null eingegeben! Aber es akzeptiert keine Null, weil es es nicht von keiner Antwort unterscheiden kann.


Antwort an Saunders:

Ja, ich gehe davon aus, dass es einen Unterschied zwischen "Person hat die Frage nicht beantwortet" und "Die Antwort war Null" gibt. Das war der Punkt des letzten Absatzes meiner Antwort. Viele Programme sind nicht in der Lage, "Weiß nicht" von Leer oder Null zu unterscheiden, was mir als potenziell schwerwiegender Fehler erscheint. Zum Beispiel habe ich vor ungefähr einem Jahr ein Haus gekauft. Ich ging zu einer Immobilien-Website und es gab viele Häuser mit einem Preis von 0 US-Dollar. Hört sich für mich ziemlich gut an: Sie verschenken diese Häuser kostenlos! Aber ich bin sicher, die traurige Realität war, dass sie den Preis einfach nicht eingegeben hatten. In diesem Fall können Sie sagen: "Nun, offensichtlich Null bedeutet, dass sie den Preis nicht eingegeben haben - niemand wird ein Haus kostenlos verschenken." Die Website listete aber auch die durchschnittlichen Angebots- und Verkaufspreise von Häusern in verschiedenen Städten auf. Ich kann nicht anders, als mich zu fragen, ob der Durchschnitt die Nullen nicht enthielt, was für einige Orte einen falsch niedrigen Durchschnitt ergibt. dh was ist der Durchschnitt von 100.000 $; 120.000 US-Dollar; und "weiß nicht"? Technisch lautet die Antwort "Weiß nicht". Was wir wahrscheinlich wirklich sehen wollen, sind 110.000 US-Dollar. Aber was wir wahrscheinlich bekommen werden, sind 73.333 Dollar, was völlig falsch wäre. Was wäre, wenn wir dieses Problem auf einer Website hätten, auf der Benutzer online bestellen können? (Unwahrscheinlich für Immobilien, aber ich bin sicher, dass Sie dies bei vielen anderen Produkten gesehen haben.) Würden wir wirklich wollen, dass "Preis noch nicht angegeben" als "kostenlos" interpretiert wird? Dies ergibt für einige Orte einen falsch niedrigen Durchschnitt. dh was ist der Durchschnitt von 100.000 $; 120.000 US-Dollar; und "weiß nicht"? Technisch lautet die Antwort "Weiß nicht". Was wir wahrscheinlich wirklich sehen wollen, sind 110.000 US-Dollar. Aber was wir wahrscheinlich bekommen werden, sind 73.333 Dollar, was völlig falsch wäre. Was wäre, wenn wir dieses Problem auf einer Website hätten, auf der Benutzer online bestellen können? (Unwahrscheinlich für Immobilien, aber ich bin sicher, dass Sie dies bei vielen anderen Produkten gesehen haben.) Würden wir wirklich wollen, dass "Preis noch nicht angegeben" als "kostenlos" interpretiert wird? Dies ergibt für einige Orte einen falsch niedrigen Durchschnitt. dh was ist der Durchschnitt von 100.000 $; 120.000 US-Dollar; und "weiß nicht"? Technisch lautet die Antwort "Weiß nicht". Was wir wahrscheinlich wirklich sehen wollen, sind 110.000 US-Dollar. Aber was wir wahrscheinlich bekommen werden, sind 73.333 Dollar, was völlig falsch wäre. Was wäre, wenn wir dieses Problem auf einer Website hätten, auf der Benutzer online bestellen können? (Unwahrscheinlich für Immobilien, aber ich bin sicher, dass Sie dies bei vielen anderen Produkten gesehen haben.) Würden wir wirklich wollen, dass "Preis noch nicht angegeben" als "kostenlos" interpretiert wird? das wäre völlig falsch. Was wäre, wenn wir dieses Problem auf einer Website hätten, auf der Benutzer online bestellen können? (Unwahrscheinlich für Immobilien, aber ich bin sicher, dass Sie dies bei vielen anderen Produkten gesehen haben.) Würden wir wirklich wollen, dass "Preis noch nicht angegeben" als "kostenlos" interpretiert wird? das wäre völlig falsch. Was wäre, wenn wir dieses Problem auf einer Website hätten, auf der Benutzer online bestellen können? (Unwahrscheinlich für Immobilien, aber ich bin sicher, dass Sie dies bei vielen anderen Produkten gesehen haben.) Würden wir wirklich wollen, dass "Preis noch nicht angegeben" als "kostenlos" interpretiert wird?

RE hat zwei separate Funktionen, ein "Gibt es welche?" und ein "wenn ja, was ist es?" Ja, das könnten Sie sicherlich tun, aber warum sollten Sie das wollen? Jetzt muss das aufrufende Programm zwei Aufrufe anstelle von einem tätigen. Was passiert, wenn ein Programmierer das "any" nicht aufruft? und geht direkt zum "Was ist das?" ? Gibt das Programm eine falsch führende Null zurück? Eine Ausnahme werfen? Einen undefinierten Wert zurückgeben? Es schafft mehr Code, mehr Arbeit und mehr potenzielle Fehler.

Der einzige Vorteil, den ich sehe, ist, dass Sie eine beliebige Regel einhalten können. Gibt es einen Vorteil dieser Regel, der es sich lohnt, sie einzuhalten? Wenn nicht, warum dann?


Antwort auf Jammycakes:

Überlegen Sie, wie der eigentliche Code aussehen würde. Ich weiß, dass die Frage C # lautete, aber entschuldigen Sie, wenn ich Java schreibe. Mein C # ist nicht sehr scharf und das Prinzip ist das gleiche.

Mit einer Null-Rückgabe:

HospList list=patient.getHospitalizationList(patientId);
if (list==null)
{
   // ... handle missing list ...
}
else
{
  for (HospEntry entry : list)
   //  ... do whatever ...
}

Mit einer separaten Funktion:

if (patient.hasHospitalizationList(patientId))
{
   // ... handle missing list ...
}
else
{
  HospList=patient.getHospitalizationList(patientId))
  for (HospEntry entry : list)
   // ... do whatever ...
}

Es ist tatsächlich ein oder zwei Zeilen weniger Code mit der Null-Rückgabe, sodass der Anrufer nicht mehr belastet wird, sondern weniger.

Ich sehe nicht, wie es ein trockenes Problem schafft. Es ist nicht so, dass wir den Anruf zweimal ausführen müssen. Wenn wir immer das Gleiche tun wollten, wenn die Liste nicht vorhanden ist, könnten wir die Behandlung möglicherweise auf die Funktion "Get-List" beschränken, anstatt den Anrufer dies tun zu lassen. Daher wäre das Einfügen des Codes in den Anrufer eine DRY-Verletzung. Aber wir wollen mit ziemlicher Sicherheit nicht immer das Gleiche tun. In Funktionen, in denen die Liste verarbeitet werden muss, ist eine fehlende Liste ein Fehler, der die Verarbeitung möglicherweise anhält. Aber auf einem Bearbeitungsbildschirm möchten wir die Verarbeitung sicherlich nicht anhalten, wenn sie noch keine Daten eingegeben haben. Wir möchten, dass sie Daten eingeben. Die Behandlung von "keine Liste" muss also auf die Anrufer-Ebene auf die eine oder andere Weise erfolgen. Und ob wir das mit einer Null-Rückgabe oder einer separaten Funktion machen, spielt für das größere Prinzip keine Rolle.

Sicher, wenn der Aufrufer nicht nach null sucht, kann das Programm mit einer Nullzeigerausnahme fehlschlagen. Aber wenn es eine separate "got any" -Funktion gibt und der Aufrufer diese Funktion nicht aufruft, sondern blind die "get list" -Funktion aufruft, was passiert dann? Wenn es eine Ausnahme auslöst oder auf andere Weise fehlschlägt, ist das so ziemlich das Gleiche wie das, was passieren würde, wenn es null zurückgibt und nicht danach sucht. Wenn eine leere Liste zurückgegeben wird, ist das einfach falsch. Sie können nicht zwischen "Ich habe eine Liste mit null Elementen" und "Ich habe keine Liste" unterscheiden. Es ist, als würde man für den Preis Null zurückgeben, wenn der Benutzer keinen Preis eingegeben hat: Es ist einfach falsch.

Ich sehe nicht, wie das Anhängen eines zusätzlichen Attributs an die Sammlung hilft. Der Anrufer muss es noch überprüfen. Wie ist das besser als nach Null zu suchen? Das absolut Schlimmste, was passieren kann, ist, dass der Programmierer vergisst, es zu überprüfen, und falsche Ergebnisse liefert.

Eine Funktion, die null zurückgibt, ist keine Überraschung, wenn der Programmierer mit dem Konzept von null vertraut ist, das "keinen Wert haben" bedeutet. Ich denke, jeder kompetente Programmierer hätte davon hören sollen, ob er es für eine gute Idee hält oder nicht. Ich denke, eine separate Funktion zu haben, ist eher ein "Überraschungs" -Problem. Wenn ein Programmierer mit der API nicht vertraut ist und einen Test ohne Daten ausführt, stellt er schnell fest, dass er manchmal eine Null zurückerhält. Aber wie würde er die Existenz einer anderen Funktion entdecken, wenn ihm nicht in den Sinn käme, dass es eine solche Funktion geben könnte, und er die Dokumentation überprüft und die Dokumentation vollständig und verständlich ist? Ich hätte viel lieber eine Funktion, die mir immer eine aussagekräftige Antwort gibt, als zwei Funktionen, die ich kennen und daran denken muss, beide aufzurufen.

Jay
quelle
4
Warum ist es notwendig, die Antworten "keine Antwort" und "null" in demselben Rückgabewert zu kombinieren? Lassen Sie die Methode stattdessen "frühere Krankenhausaufenthalte in den letzten fünf Jahren" zurückgeben und eine separate Methode verwenden, die fragt: "Wurde die Liste der vorherigen Krankenhausaufenthalte jemals ausgefüllt?". Dies setzt voraus, dass es einen Unterschied zwischen einer Liste ohne vorherige Krankenhausaufenthalte und einer Liste ohne ausgefüllte Krankenhausaufenthalte gibt.
John Saunders
2
Wenn Sie jedoch null zurückgeben, belasten Sie den Anrufer ohnehin bereits zusätzlich! Der Anrufer muss jeden Rückgabewert auf null prüfen - eine schreckliche DRY-Verletzung. Wenn Sie angeben möchten , „Anrufer beantworten nicht die Frage“ getrennt, es ist einfacher , einen zusätzlichen Methodenaufruf zu erstellen um die Tatsache anzuzeigen. Entweder das, oder Sie verwenden einen abgeleiteten Auflistungstyp mit der zusätzlichen Eigenschaft DeclinedToAnswer. Die Regel, niemals null zurückzugeben, ist überhaupt nicht willkürlich, sondern das Prinzip der geringsten Überraschung. Außerdem sollte der Rückgabetyp Ihrer Methode bedeuten, was der Name sagt. Null mit ziemlicher Sicherheit nicht.
Jammycakes
2
Sie gehen davon aus, dass Ihre getHospitalizationList nur von einem Ort aus aufgerufen wird und / oder dass alle Anrufer zwischen den Fällen "keine Antwort" und "null" unterscheiden möchten. Es wird Fälle geben (mit ziemlicher Sicherheit die Mehrheit), in denen Ihr Anrufer diese Unterscheidung nicht treffen muss, und Sie ihn daher zwingen, an Stellen, an denen dies nicht erforderlich sein sollte, Nullprüfungen hinzuzufügen. Dies erhöht das Risiko Ihrer Codebasis erheblich, da die Benutzer dies nur allzu leicht vergessen können. Da es nur sehr wenige legitime Gründe gibt, anstelle einer Sammlung null zurückzugeben, ist dies viel wahrscheinlicher.
Jammycakes
3
RE the name: Kein Funktionsname kann vollständig beschreiben, was die Funktion tut, es sei denn, sie ist so lang wie die Funktion und daher äußerst unpraktisch. Aber wenn die Funktion eine leere Liste zurückgibt, wenn keine Antwort gegeben wurde, sollte sie aus denselben Gründen nicht "getHospitalizationListOrEmptyListIfNoAnswer" heißen? Aber würden Sie wirklich darauf bestehen, dass die Java Reader.read-Funktion in readCharacterOrReturnMinusOneOnEndOfStream umbenannt wird? Das ResultSet.getInt sollte wirklich "getIntOrZeroIfValueWasNull" sein? Usw.
Jay
4
RE Jeder Anruf möchte unterscheiden: Nun ja, ich gehe davon aus, dass der Autor eines Anrufers eine bewusste Entscheidung treffen sollte, die ihn nicht interessiert. Wenn die Funktion eine leere Liste für "Weiß nicht" zurückgibt und Anrufer diese "Keine" blind behandeln, kann dies zu ernsthaft ungenauen Ergebnissen führen. Stellen Sie sich vor, die Funktion wäre "getAllergicReactionToMedicationList". Ein Programm, das blind behandelt wurde, "Liste wurde nicht eingegeben", da "Patient hat keine bekannten allergischen Reaktionen", könnte buchstäblich dazu führen, dass ein Patient getötet wird. In vielen anderen Systemen würden Sie ähnliche, wenn auch weniger dramatische Ergebnisse erzielen. ...
Jay
10

Wenn eine leere Sammlung semantisch Sinn macht, kehre ich lieber zurück. Rückgabe einer leeren Sammlung für GetMessagesInMyInbox()Mitteilungen "Sie haben wirklich keine Nachrichten in Ihrem Posteingang", während die Rückgabe nullhilfreich sein kann, um mitzuteilen, dass nicht genügend Daten verfügbar sind, um zu sagen, wie die möglicherweise zurückgegebene Liste aussehen soll.

David Hedlund
quelle
6
In dem von Ihnen angegebenen Beispiel sollte die Methode wahrscheinlich eine Ausnahme auslösen, wenn sie die Anforderung nicht erfüllen kann, anstatt nur null zurückzugeben. Ausnahmen sind für die Diagnose von Problemen viel nützlicher als Nullen.
Greg Beech
Nun ja, im Posteingangsbeispiel erscheint ein nullWert sicherlich nicht vernünftig. Ich habe allgemeiner darüber nachgedacht. Ausnahmen sind auch großartig, um die Tatsache zu kommunizieren, dass etwas schief gelaufen ist, aber wenn die "unzureichenden Daten", auf die Bezug genommen wird, perfekt erwartet werden, dann würde das Auslösen einer Ausnahme ein schlechtes Design bedeuten. Ich denke eher an ein Szenario, in dem es durchaus möglich und überhaupt kein Fehler ist, dass die Methode manchmal nicht in der Lage ist, eine Antwort zu berechnen.
David Hedlund
6

Die Rückgabe von null könnte effizienter sein, da kein neues Objekt erstellt wird. Es würde jedoch häufig auch eine nullÜberprüfung (oder Ausnahmebehandlung) erfordern .

Semantisch gesehen null und eine leere Liste bedeuten nicht dasselbe. Die Unterschiede sind subtil und eine Wahl kann in bestimmten Fällen besser sein als die andere.

Dokumentieren Sie es unabhängig von Ihrer Wahl, um Verwirrung zu vermeiden.

Karmischer Codierer
quelle
8
Effizienz sollte fast nie ein Faktor sein, wenn die Richtigkeit des Entwurfs einer API berücksichtigt wird. In einigen sehr spezifischen Fällen, wie z. B. bei Grafikprimitiven, mag dies der Fall sein, aber wenn ich mich mit Listen und den meisten anderen Dingen auf hoher Ebene befasse, bezweifle ich dies sehr.
Greg Beech
Stimmen Sie Greg zu, insbesondere angesichts der Tatsache, dass der Code, den der API-Benutzer schreiben muss, um diese "Optimierung" zu kompensieren, ineffizienter sein kann, als wenn überhaupt ein besseres Design verwendet würde.
Craig Stuntz
Einverstanden, und in den meisten Fällen lohnt sich die Optimierung einfach nicht. Leere Listen sind mit moderner Speicherverwaltung praktisch kostenlos.
Jason Baker
6

Man könnte argumentieren, dass die Argumentation hinter Null Object Pattern ähnlich ist wie die für die Rückgabe der leeren Sammlung.

Dan
quelle
4

Es kommt auf die Situation an. Wenn es sich um einen Sonderfall handelt, geben Sie null zurück. Wenn die Funktion zufällig eine leere Sammlung zurückgibt, ist dies offensichtlich in Ordnung. Die Rückgabe einer leeren Sammlung als Sonderfall aufgrund ungültiger Parameter oder aus anderen Gründen ist jedoch KEINE gute Idee, da dadurch eine Sonderfallbedingung maskiert wird.

Eigentlich ziehe ich es in diesem Fall normalerweise vor, eine Ausnahme auszulösen, um sicherzustellen, dass sie WIRKLICH nicht ignoriert wird :)

Zu sagen, dass der Code dadurch robuster wird (indem eine leere Sammlung zurückgegeben wird), da sie die Nullbedingung nicht behandeln müssen, ist schlecht, da lediglich ein Problem maskiert wird, das vom aufrufenden Code behandelt werden sollte.

Larry Watanabe
quelle
4

Ich würde argumentieren, dass dies nullnicht dasselbe ist wie eine leere Sammlung, und Sie sollten auswählen, welche am besten für Ihre Rückgabe steht. In den meisten Fällennull ist nichts (außer in SQL). Eine leere Sammlung ist etwas, wenn auch ein leeres Etwas.

Wenn Sie sich für die eine oder andere entscheiden müssen, würde ich sagen, dass Sie eher zu einer leeren Sammlung als zu Null tendieren sollten. Es gibt jedoch Situationen, in denen eine leere Sammlung nicht mit einem Nullwert identisch ist.

Jason Baker
quelle
4

Denken Sie immer zugunsten Ihrer Kunden (die Ihre API verwenden):

Die Rückgabe von 'null' führt sehr häufig zu Problemen mit Clients, die Nullprüfungen nicht korrekt verarbeiten, was zur Laufzeit zu einer NullPointerException führt. Ich habe Fälle gesehen, in denen eine solche fehlende Nullprüfung ein vorrangiges Produktionsproblem erzwang (ein Client, der für jeden (...) einen Nullwert verwendet hat). Während des Tests trat das Problem nicht auf, da die bearbeiteten Daten geringfügig voneinander abweichen.

Manuel Aldana
quelle
3

Ich möchte hier mit einem geeigneten Beispiel erklären.

Betrachten Sie hier einen Fall.

int totalValue = MySession.ListCustomerAccounts()
                          .FindAll(ac => ac.AccountHead.AccountHeadID 
                                         == accountHead.AccountHeadID)
                          .Sum(account => account.AccountValue);

Hier Betrachten Sie die Funktionen, die ich benutze ..

1. ListCustomerAccounts() // User Defined
2. FindAll()              // Pre-defined Library Function

Ich kann leicht verwenden ListCustomerAccountund FindAllstatt.,.

int totalValue = 0; 
List<CustomerAccounts> custAccounts = ListCustomerAccounts();
if(custAccounts !=null ){
  List<CustomerAccounts> custAccountsFiltered = 
        custAccounts.FindAll(ac => ac.AccountHead.AccountHeadID 
                                   == accountHead.AccountHeadID );
   if(custAccountsFiltered != null)
      totalValue = custAccountsFiltered.Sum(account => 
                                            account.AccountValue).ToString();
}

HINWEIS: Da AccountValue nicht aktiviert ist null, wird die Funktion Sum () nicht zurückgegeben null. Daher kann ich sie direkt verwenden.

Muthu Ganapathy Nathan
quelle
2

Wir hatten diese Diskussion vor ungefähr einer Woche im Entwicklungsteam bei der Arbeit und haben uns fast einstimmig für eine leere Sammlung entschieden. Eine Person wollte aus demselben Grund, den Mike oben angegeben hatte, null zurückgeben.

Henric
quelle
2

Leere Sammlung. Wenn Sie C # verwenden, wird davon ausgegangen, dass die Maximierung der Systemressourcen nicht unbedingt erforderlich ist. Die Rückgabe der leeren Sammlung ist zwar weniger effizient, für die beteiligten Programmierer jedoch viel bequemer (aus dem oben beschriebenen Grund).

Motten
quelle
2

In den meisten Fällen ist es besser, eine leere Sammlung zurückzugeben.

Der Grund dafür ist die bequeme Implementierung des Anrufers, der konsistente Vertrag und die einfachere Implementierung.

Wenn eine Methode null zurückgibt, um ein leeres Ergebnis anzuzeigen, muss der Aufrufer zusätzlich zur Aufzählung einen Nullprüfadapter implementieren. Dieser Code wird dann in verschiedenen Aufrufern dupliziert. Warum also nicht diesen Adapter in die Methode einfügen, damit er wiederverwendet werden kann?

Eine gültige Verwendung von null für IEnumerable kann ein Hinweis auf ein fehlendes Ergebnis oder einen Operationsfehler sein. In diesem Fall sollten jedoch andere Techniken in Betracht gezogen werden, z. B. das Auslösen einer Ausnahme.

using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

namespace StackOverflow.EmptyCollectionUsageTests.Tests
{
    /// <summary>
    /// Demonstrates different approaches for empty collection results.
    /// </summary>
    class Container
    {
        /// <summary>
        /// Elements list.
        /// Not initialized to an empty collection here for the purpose of demonstration of usage along with <see cref="Populate"/> method.
        /// </summary>
        private List<Element> elements;

        /// <summary>
        /// Gets elements if any
        /// </summary>
        /// <returns>Returns elements or empty collection.</returns>
        public IEnumerable<Element> GetElements()
        {
            return elements ?? Enumerable.Empty<Element>();
        }

        /// <summary>
        /// Initializes the container with some results, if any.
        /// </summary>
        public void Populate()
        {
            elements = new List<Element>();
        }

        /// <summary>
        /// Gets elements. Throws <see cref="InvalidOperationException"/> if not populated.
        /// </summary>
        /// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>.</returns>
        public IEnumerable<Element> GetElementsStrict()
        {
            if (elements == null)
            {
                throw new InvalidOperationException("You must call Populate before calling this method.");
            }

            return elements;
        }

        /// <summary>
        /// Gets elements, empty collection or nothing.
        /// </summary>
        /// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>, with zero or more elements, or null in some cases.</returns>
        public IEnumerable<Element> GetElementsInconvenientCareless()
        {
            return elements;
        }

        /// <summary>
        /// Gets elements or nothing.
        /// </summary>
        /// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>, with elements, or null in case of empty collection.</returns>
        /// <remarks>We are lucky that elements is a List, otherwise enumeration would be needed.</remarks>
        public IEnumerable<Element> GetElementsInconvenientCarefull()
        {
            if (elements == null || elements.Count == 0)
            {
                return null;
            }
            return elements;
        }
    }

    class Element
    {
    }

    /// <summary>
    /// http://stackoverflow.com/questions/1969993/is-it-better-to-return-null-or-empty-collection/
    /// </summary>
    class EmptyCollectionTests
    {
        private Container container;

        [SetUp]
        public void SetUp()
        {
            container = new Container();
        }

        /// <summary>
        /// Forgiving contract - caller does not have to implement null check in addition to enumeration.
        /// </summary>
        [Test]
        public void UseGetElements()
        {
            Assert.AreEqual(0, container.GetElements().Count());
        }

        /// <summary>
        /// Forget to <see cref="Container.Populate"/> and use strict method.
        /// </summary>
        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void WrongUseOfStrictContract()
        {
            container.GetElementsStrict().Count();
        }

        /// <summary>
        /// Call <see cref="Container.Populate"/> and use strict method.
        /// </summary>
        [Test]
        public void CorrectUsaOfStrictContract()
        {
            container.Populate();
            Assert.AreEqual(0, container.GetElementsStrict().Count());
        }

        /// <summary>
        /// Inconvenient contract - needs a local variable.
        /// </summary>
        [Test]
        public void CarefulUseOfCarelessMethod()
        {
            var elements = container.GetElementsInconvenientCareless();
            Assert.AreEqual(0, elements == null ? 0 : elements.Count());
        }

        /// <summary>
        /// Inconvenient contract - duplicate call in order to use in context of an single expression.
        /// </summary>
        [Test]
        public void LameCarefulUseOfCarelessMethod()
        {
            Assert.AreEqual(0, container.GetElementsInconvenientCareless() == null ? 0 : container.GetElementsInconvenientCareless().Count());
        }

        [Test]
        public void LuckyCarelessUseOfCarelessMethod()
        {
            // INIT
            var praySomeoneCalledPopulateBefore = (Action)(()=>container.Populate());
            praySomeoneCalledPopulateBefore();

            // ACT //ASSERT
            Assert.AreEqual(0, container.GetElementsInconvenientCareless().Count());
        }

        /// <summary>
        /// Excercise <see cref="ArgumentNullException"/> because of null passed to <see cref="Enumerable.Count{TSource}(System.Collections.Generic.IEnumerable{TSource})"/>
        /// </summary>
        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void UnfortunateCarelessUseOfCarelessMethod()
        {
            Assert.AreEqual(0, container.GetElementsInconvenientCareless().Count());
        }

        /// <summary>
        /// Demonstrates the client code flow relying on returning null for empty collection.
        /// Exception is due to <see cref="Enumerable.First{TSource}(System.Collections.Generic.IEnumerable{TSource})"/> on an empty collection.
        /// </summary>
        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void UnfortunateEducatedUseOfCarelessMethod()
        {
            container.Populate();
            var elements = container.GetElementsInconvenientCareless();
            if (elements == null)
            {
                Assert.Inconclusive();
            }
            Assert.IsNotNull(elements.First());
        }

        /// <summary>
        /// Demonstrates the client code is bloated a bit, to compensate for implementation 'cleverness'.
        /// We can throw away the nullness result, because we don't know if the operation succeeded or not anyway.
        /// We are unfortunate to create a new instance of an empty collection.
        /// We might have already had one inside the implementation,
        /// but it have been discarded then in an effort to return null for empty collection.
        /// </summary>
        [Test]
        public void EducatedUseOfCarefullMethod()
        {
            Assert.AreEqual(0, (container.GetElementsInconvenientCarefull() ?? Enumerable.Empty<Element>()).Count());
        }
    }
}
George Polevoy
quelle
2

Ich nenne es meinen Milliarden-Dollar-Fehler… Zu dieser Zeit entwarf ich das erste umfassende Typensystem für Referenzen in einer objektorientierten Sprache. Mein Ziel war es sicherzustellen, dass jede Verwendung von Referenzen absolut sicher ist, wobei die Überprüfung automatisch vom Compiler durchgeführt wird. Aber ich konnte der Versuchung nicht widerstehen, eine Nullreferenz einzugeben, einfach weil es so einfach zu implementieren war. Dies hat zu unzähligen Fehlern, Schwachstellen und Systemabstürzen geführt, die in den letzten vierzig Jahren wahrscheinlich eine Milliarde Dollar an Schmerzen und Schäden verursacht haben. - Tony Hoare, Erfinder von ALGOL W.

Sehen Sie hier für eine aufwendige Scheiße Sturm über nullim Allgemeinen. Ich stimme der Aussage nicht zu, die undefinedeine andere ist null, aber es lohnt sich immer noch zu lesen. Und es erklärt, warum Sie überhaupt vermeiden sollten nullund nicht nur für den Fall, dass Sie gefragt haben. Das Wesentliche ist, dass nullin jeder Sprache ein Sonderfall vorliegt. Sie müssen nullals Ausnahme denken . undefinedunterscheidet sich in dieser Hinsicht, dass Code, der sich mit undefiniertem Verhalten befasst, in den meisten Fällen nur ein Fehler ist. C und die meisten anderen Sprachen haben ebenfalls ein undefiniertes Verhalten, aber die meisten von ihnen haben keine Kennung dafür in der Sprache.

ceving
quelle
1

Unter dem Gesichtspunkt des Komplexitätsmanagements, einem primären Ziel des Software-Engineerings, möchten wir eine unnötige Verbreitung vermeiden zyklomatische Komplexität auf die Clients einer API übertragen wird. Das Zurückgeben einer Null an den Client entspricht dem Zurückgeben der zyklomatischen Komplexitätskosten eines anderen Codezweigs.

(Dies entspricht einer Unit-Test-Belastung. Sie müssten zusätzlich zum leeren Collection-Return-Fall einen Test für den Null-Return-Fall schreiben.)

dthal
quelle