Sollten Funktionen null oder ein leeres Objekt zurückgeben?


209

Was ist die beste Vorgehensweise bei der Rückgabe von Daten aus Funktionen ? Ist es besser, eine Null oder ein leeres Objekt zurückzugeben? Und warum sollte man eins über das andere tun?

Bedenken Sie:

public UserEntity GetUserById(Guid userId)
{
     //Imagine some code here to access database.....

     //Check if data was returned and return a null if none found
     if (!DataExists)
        return null; 
        //Should I be doing this here instead? 
        //return new UserEntity();  
     else
        return existingUserEntity;
}

Stellen wir uns vor, dass es in diesem Programm gültige Fälle gibt, in denen keine Benutzerinformationen mit dieser GUID in der Datenbank vorhanden sind. Ich würde mir vorstellen, dass es in diesem Fall nicht angebracht wäre, eine Ausnahme zu machen? Ich habe auch den Eindruck, dass die Behandlung von Ausnahmen die Leistung beeinträchtigen kann.


4
Ich denke du meinst if (!DataExists).
Sarah Vessels

107
Dies ist eine architektonische Frage und durchaus angemessen. Die Frage des OP ist unabhängig von dem Geschäftsproblem gültig, das es zu lösen versucht.
Joseph Ferris

2
Diese Frage wurde bereits ausreichend beantwortet. Ich denke, das ist eine sehr interessante Frage.
John Scipione

'getUser ()' sollte null zurückgeben. 'getCurrentUserInfo ()' oder 'getCurrentPermissions ()', OTOH, wären aufschlussreichere Fragen - sie sollten ein Nicht-Null- Antwortobjekt zurückgeben, unabhängig davon, wer / oder ob jemand angemeldet ist.
Thomas W

2
Nein @Bergi, der andere ist ein Duplikat. Meins wurde zuerst gefragt, im Oktober, das andere wurde 3 Monate später im Dezember gefragt. Außerdem spricht der andere von einer etwas anderen Sammlung.
7wp

Antworten:


207

Die Rückgabe von null ist normalerweise die beste Idee, wenn Sie angeben möchten, dass keine Daten verfügbar sind.

Ein leeres Objekt impliziert, dass Daten zurückgegeben wurden, während die Rückgabe von null eindeutig anzeigt, dass nichts zurückgegeben wurde.

Darüber hinaus führt die Rückgabe einer Null zu einer Null-Ausnahme, wenn Sie versuchen, auf Mitglieder im Objekt zuzugreifen. Dies kann hilfreich sein, um fehlerhaften Code hervorzuheben. Der Versuch, auf ein Mitglied von nichts zuzugreifen, macht keinen Sinn. Der Zugriff auf Mitglieder eines leeren Objekts schlägt nicht fehl, was bedeutet, dass Fehler unentdeckt bleiben können.


21
Sie sollten eine Ausnahme auslösen, das Problem nicht verschlucken und null zurückgeben. Sie sollten dies mindestens protokollieren und dann fortfahren.
Chris Ballance

130
@ Chris: Ich bin anderer Meinung. Wenn der Code eindeutig dokumentiert, dass der Rückgabewert null ist, ist es durchaus akzeptabel, null zurückzugeben, wenn kein Ergebnis gefunden wird, das Ihren Kriterien entspricht. Das Auslösen einer Ausnahme sollte Ihre LETZTE Wahl sein, nicht Ihre ERSTE.
Mike Hofer

12
@ Chris: Auf welcher Basis entscheidest du das? Das Hinzufügen von Protokollierung zur Gleichung scheint sicherlich übertrieben. Lassen Sie den konsumierenden Code entscheiden, was - wenn überhaupt - getan werden soll, wenn kein Benutzer anwesend ist. Wie bei meinem vorherigen Kommentar gibt es absolut kein Problem bei der Rückgabe eines Werts, der allgemein als "keine Daten" definiert ist.
Adam Robinson

17
Ich bin ein wenig verblüfft darüber, dass ein Microsoft-Entwickler glaubt, dass "Null zurückgeben" gleichbedeutend ist mit "Verschlucken des Problems". Wenn Speicher bereitgestellt wird, gibt es im Framework Tonnenmethoden, bei denen Methoden null zurückgegeben werden, wenn nichts mit der Anforderung des Aufrufers übereinstimmt. Ist das "das Problem verschlucken"?
Mike Hofer

5
Last but not least würde es einen geben, bool GetUserById(Guid userId, out UserEntity result)den ich dem Rückgabewert "null" vorziehen würde und der nicht so extrem ist wie das Auslösen einer Ausnahme. Es erlaubt schön- nullfreien Code wie if(GetUserById(x,u)) { ... }.
Marcel Jackwerth

44

Es kommt darauf an, was für Ihren Fall am sinnvollsten ist.

Ist es sinnvoll, null zurückzugeben, z. B. "kein solcher Benutzer existiert"?

Oder ist es sinnvoll, einen Standardbenutzer zu erstellen? Dies ist am sinnvollsten, wenn Sie davon ausgehen können, dass der aufrufende Code, wenn ein Benutzer NICHT existiert, beabsichtigt, dass einer existiert, wenn er danach fragt.

Oder ist es sinnvoll, eine Ausnahme (a la "FileNotFound") auszulösen, wenn der aufrufende Code einen Benutzer mit einer ungültigen ID fordert?

Unter dem Gesichtspunkt der Trennung von Bedenken / SRP sind die ersten beiden jedoch korrekter. Und technisch gesehen ist die erste die korrekteste (aber nur durch ein Haar) - GetUserById sollte nur für eine Sache verantwortlich sein - den Benutzer zu bekommen. Die Behandlung eines eigenen Falls "Benutzer existiert nicht" durch Rückgabe von etwas anderem könnte eine Verletzung von SRP darstellen. Trennung in eine andere Prüfung - bool DoesUserExist(id)wäre angemessen, wenn Sie eine Ausnahme auslösen möchten.

Basierend auf ausführlichen Kommentaren unten : Wenn es sich um eine Entwurfsfrage auf API-Ebene handelt, kann diese Methode analog zu "OpenFile" oder "ReadEntireFile" sein. Wir "öffnen" einen Benutzer aus einem Repository und hydratisieren das Objekt aus den resultierenden Daten. In diesem Fall könnte eine Ausnahme angebracht sein. Es könnte nicht sein, aber es könnte sein.

Alle Ansätze sind akzeptabel - es kommt nur darauf an, basierend auf dem größeren Kontext der API / Anwendung.


Jemand hat dich abgewählt und ich habe dich wieder hochgestimmt, da dies für mich keine schlechte Antwort zu sein scheint. Ausnahme: Ich würde niemals eine Ausnahme auslösen, wenn ich keinen Benutzer in einer Methode wie der auf dem Poster angegebenen finde. Wenn das Finden eines Benutzers eine ungültige ID oder ein solches ausnahmefähiges Problem impliziert, sollte dies weiter oben geschehen - die Wurfmethode muss mehr darüber wissen, woher diese ID stammt usw.
Jacob Mattison

(Ich vermute, die Ablehnung war ein Einwand gegen die Idee, unter solchen Umständen eine Ausnahme auszulösen.)
Jacob Mattison

1
Einverstanden bis zu Ihrem letzten Punkt. Es liegt keine Verletzung von SRP vor, wenn ein Wert zurückgegeben wird, der allgemein als "keine Daten" definiert ist. Das ist so, als würde man angeben, dass eine SQL-Datenbank einen Fehler zurückgeben sollte, wenn eine where-Klausel keine Ergebnisse liefert. Während eine Ausnahme eine gültige Designauswahl ist (obwohl sie mich als Verbraucher irritieren würde), ist sie nicht "korrekter" als die Rückgabe von null. Und nein, ich bin nicht der DV.
Adam Robinson

@JacobM Wir lösen Ausnahmen aus, wenn wir einen Dateisystempfad anfordern, der nicht existiert, nicht null zurückgeben, sondern nicht aus Datenbanken. Es ist also klar, dass beide angemessen sind, worauf ich mich einlasse - es kommt nur darauf an.
Rex M

2
@Charles: Sie beantworten die Frage "Sollte irgendwann eine Ausnahme ausgelöst werden", aber die Frage lautet "Sollte diese Funktion die Ausnahme auslösen". Die richtige Antwort lautet "vielleicht", nicht "ja".
Adam Robinson

30

Persönlich benutze ich NULL. Es wird deutlich, dass keine Daten zurückgegeben werden müssen. Es gibt jedoch Fälle, in denen ein Nullobjekt nützlich sein kann.


Ich möchte dies gerade als Antwort hinzufügen. NullObjectPattern oder Sonderfallmuster. Dann könnten Sie für jeden Fall eine implementieren, NoUserEntitiesFound, NullUserEntities usw.
David Swindells

27

Wenn Ihr Rückgabetyp ein Array ist, geben Sie ein leeres Array zurück, andernfalls geben Sie null zurück.


Entsprechen 0 Elemente in einer Liste der Liste, die derzeit nicht zugewiesen ist?
AnthonyWJones

3
0 Elemente in einer Liste sind nicht dasselbe wie null. Sie können es in foreachAnweisungen und Linq-Abfragen verwenden, ohne sich Sorgen machen zu müssen NullReferenceException.
Darin Dimitrov

5
Ich bin überrascht, dass dies nicht mehr positiv bewertet wurde. Dies scheint mir eine ziemlich vernünftige Richtlinie zu sein.
Shashi

Ein leerer Container ist nur eine bestimmte Instanz des Nullobjektmusters. Was angemessen sein könnte, können wir nicht sagen.
Deduplikator

Das Zurückgeben eines leeren Arrays, wenn die Daten nicht verfügbar sind, ist einfach falsch . Es gibt einen Unterschied zwischen den Daten, die verfügbar sind und keine Elemente enthalten, und den Daten, die nicht verfügbar sind. Die Rückgabe eines leeren Arrays in beiden Fällen macht es unmöglich zu wissen, was der Fall ist. Dies nur zu tun, damit Sie ein foreach verwenden können, ohne zu überprüfen, ob die Daten überhaupt vorhanden sind, ist mehr als albern - der Anrufer sollte überprüfen müssen, ob die Daten vorhanden sind, und eine NullReferenceException, wenn der Anrufer nicht prüft, ist gut, weil sie einen Fehler aufdeckt .
Stellen Sie Monica

12

Sie sollten (nur) eine Ausnahme auslösen, wenn ein bestimmter Vertrag gebrochen ist.
Wenn Sie in Ihrem speziellen Beispiel nach einer UserEntity fragen, die auf einer bekannten ID basiert, hängt dies davon ab, ob fehlende (gelöschte) Benutzer erwartet werden. Wenn ja, kehren Sie zurück, nullaber wenn dies kein erwarteter Fall ist, lösen Sie eine Ausnahme aus.
Beachten Sie, dass die aufgerufene Funktion UserEntity GetUserByName(string name)wahrscheinlich nicht werfen, sondern null zurückgeben würde. In beiden Fällen wäre die Rückgabe einer leeren UserEntity nicht hilfreich.

Bei Strings, Arrays und Sammlungen ist die Situation normalerweise anders. Ich erinnere mich an einige Richtlinien aus MS, die Methoden nullals "leere" Liste akzeptieren sollten, aber Sammlungen mit der Länge Null zurückgeben sollten null. Das gleiche gilt für Strings. Beachten Sie, dass Sie leere Arrays deklarieren können:int[] arr = new int[0];


Ich bin froh, dass Sie erwähnt haben, dass Zeichenfolgen unterschiedlich sind, da Google mir dies gezeigt hat, als ich über die Rückgabe einer leeren Zeichenfolge entschieden habe.
Noumenon

Zeichenfolgen, Sammlungen und Arrays unterscheiden sich nicht . Wenn MS dies sagt, ist MS falsch. Es gibt einen Unterschied zwischen einer leeren Zeichenfolge und null sowie zwischen einer leeren Sammlung und null. In beiden Fällen stehen die ersteren für vorhandene Daten (Größe 0) und die letzteren für Datenmangel. In einigen Fällen ist die Unterscheidung sehr wichtig. Wenn Sie beispielsweise einen Eintrag in einem Cache nachschlagen, möchten Sie den Unterschied zwischen den zwischengespeicherten, aber leeren Daten und den nicht zwischengespeicherten Daten kennen, sodass Sie sie aus der zugrunde liegenden Datenquelle abrufen müssen, wo dies möglicherweise nicht der Fall ist leer.
Setzen Sie Monica

1
Sie scheinen den Punkt und den Kontext zu verfehlen. .Wher(p => p.Lastname == "qwerty")sollte eine leere Sammlung zurückgeben, nicht null.
Henk Holterman

@HenkHolterman Wenn Sie auf die gesamte Sammlung zugreifen und einen Filter anwenden können, der keine Elemente in der Sammlung akzeptiert, ist eine leere Sammlung das richtige Ergebnis. Wenn jedoch nicht die vollständige Sammlung vorhanden ist, ist eine leere Sammlung äußerst irreführend - null oder werfen wäre richtig, je nachdem, ob die Situation normal oder außergewöhnlich ist. Da Ihr Beitrag nicht qualifiziert hat, über welche Situation Sie gesprochen haben (und jetzt klarstellen, dass Sie über die erstere Situation sprechen) und das OP über die letztere Situation gesprochen hat, muss ich Ihnen nicht zustimmen.
Setzen Sie Monica

11

Dies ist eine Geschäftsfrage, die davon abhängt, ob die Existenz eines Benutzers mit einer bestimmten Guid-ID ein erwarteter normaler Anwendungsfall für diese Funktion ist oder ob es sich um eine Anomalie handelt, die verhindert, dass die Anwendung die Funktion, die diese Methode dem Benutzer bereitstellt, erfolgreich ausführt Objekt zu ...

Wenn es sich um eine "Ausnahme" handelt, verhindert die Abwesenheit eines Benutzers mit dieser ID, dass die Anwendung die von ihr ausgeführte Funktion erfolgreich ausführt. (Angenommen, wir erstellen eine Rechnung für einen Kunden, an den wir das Produkt versendet haben ... ), dann sollte diese Situation eine ArgumentException (oder eine andere benutzerdefinierte Ausnahme) auslösen.

Wenn ein fehlender Benutzer in Ordnung ist (eines der möglichen normalen Ergebnisse beim Aufrufen dieser Funktion), geben Sie eine Null zurück ....

EDIT: (um den Kommentar von Adam in einer anderen Antwort anzusprechen)

Wenn die Anwendung mehrere Geschäftsprozesse enthält, von denen einer oder mehrere einen Benutzer benötigen, um erfolgreich abgeschlossen zu werden, und einer oder mehrere ohne einen Benutzer erfolgreich abgeschlossen werden können, sollte die Ausnahme weiter oben im Aufrufstapel ausgelöst werden, näher an der Stelle Die Geschäftsprozesse, für die ein Benutzer erforderlich ist, rufen diesen Ausführungsthread auf. Methoden zwischen dieser Methode und dem Punkt (an dem die Ausnahme ausgelöst wird) sollten lediglich mitteilen, dass kein Benutzer vorhanden ist (null, boolesch, was auch immer - dies ist ein Implementierungsdetail).

Aber wenn alle Prozesse in der Anwendung einen Benutzer erfordern , würde ich trotzdem die Ausnahme in dieser Methode auslösen ...


-1 an den Downvoter, +1 an Charles - es ist eine reine Geschäftsfrage und es gibt keine Best Practice dafür.
Austin Salonen

Es überquert die Bäche. Ob es sich um eine "Fehlerbedingung" handelt oder nicht, hängt von der Geschäftslogik ab. Wie damit umzugehen ist, ist eine Entscheidung über die Anwendungsarchitektur. Die Geschäftslogik schreibt nicht vor, dass eine Null zurückgegeben wird, sondern nur, dass die Anforderungen erfüllt sind. Wenn das Unternehmen Methodenrückgabetypen entscheidet, sind diese zu stark in den technischen Aspekt der Implementierung involviert.
Joseph Ferris

@joseph, das Kernprinzip der "strukturierten Ausnahmebehandlung" ist, dass Ausnahmen ausgelöst werden sollten, wenn Methoden nicht die Funktion ausführen können, für deren Implementierung sie codiert wurden. Sie haben Recht, wenn die Geschäftsfunktion, für deren Implementierung diese Methode codiert wurde, "erfolgreich abgeschlossen" werden kann (was auch immer dies im Domänenmodell bedeutet), müssen Sie keine Ausnahme auslösen, sondern können eine Null zurückgeben , oder eine boolesche "FoundUser" -Variable oder was auch immer ... Wie Sie mit der aufrufenden Methode kommunizieren, dass kein Benutzer gefunden wurde, wird dann zu einem technischen Implementierungsdetail.
Charles Bretana

10

Ich persönlich würde null zurückgeben, da ich so erwarten würde, dass die DAL / Repository-Schicht funktioniert.

Wenn es nicht existiert, geben Sie nichts zurück, was als erfolgreiches Abrufen eines Objekts ausgelegt werden könnte. null hier wunderbar.

Das Wichtigste ist, dass Sie über Ihre DAL / Repos-Ebene hinweg konsistent sind, damit Sie nicht verwirrt sind, wie Sie sie verwenden.


7

Ich neige dazu

  • return nullwenn die Objekt-ID nicht existiert, wenn vorher nicht bekannt ist, ob dies der Fall sein soll vorhanden sein.
  • throwWenn die Objekt-ID nicht vorhanden ist, sollte sie vorhanden sein.

Ich unterscheide diese beiden Szenarien mit diesen drei Arten von Methoden. Zuerst:

Boolean TryGetSomeObjectById(Int32 id, out SomeObject o)
{
    if (InternalIdExists(id))
    {
        o = InternalGetSomeObject(id);

        return true;
    }
    else
    {
        return false;
    }
}

Zweite:

SomeObject FindSomeObjectById(Int32 id)
{
    SomeObject o;

    return TryGetObjectById(id, out o) ? o : null;
}

Dritte:

SomeObject GetSomeObjectById(Int32 id)
{
    SomeObject o;

    if (!TryGetObjectById(id, out o))
    {
        throw new SomeAppropriateException();
    }

    return o;
}

Du meinst outnichtref
Matt Ellen

@ Matt: Ja, Sir, das tue ich mit Sicherheit! Fest.
Johann Gerell

2
Wirklich, dies ist die einzige einheitliche Antwort und daher die ganz und gar wahre Wahrheit! :) Ja, es hängt von den Annahmen ab , auf denen die Methode aufgerufen wird ... Klären Sie also zuerst diese Annahmen und wählen Sie dann die richtige Kombination der oben genannten aus. Musste zu viel nach unten scrollen, um hierher zu gelangen :) +100
yair

Dies scheint das zu verwendende Muster zu sein, außer dass es keine asynchronen Methoden unterstützt. Ich verwies auf diese Antwort und fügte eine asynchrone Lösung mit Tuple Literals hinzu -> here
ttugates

6

Ein weiterer Ansatz besteht darin, ein Rückrufobjekt oder einen Delegaten zu übergeben, der den Wert verarbeitet. Wird kein Wert gefunden, wird der Rückruf nicht aufgerufen.

public void GetUserById(Guid id, UserCallback callback)
{
    // Lookup user
    if (userFound)
        callback(userEntity);  // or callback.Call(userEntity);
}

Dies funktioniert gut, wenn Sie Nullprüfungen im gesamten Code vermeiden möchten und wenn das Nichtfinden eines Werts kein Fehler ist. Sie können auch einen Rückruf bereitstellen, wenn keine Objekte gefunden werden, wenn Sie eine spezielle Verarbeitung benötigen.

public void GetUserById(Guid id, UserCallback callback, NotFoundCallback notFound)
{
    // Lookup user
    if (userFound)
        callback(userEntity);  // or callback.Call(userEntity);
    else
        notFound(); // or notFound.Call();
}

Der gleiche Ansatz mit einem einzelnen Objekt könnte folgendermaßen aussehen:

public void GetUserById(Guid id, UserCallback callback)
{
    // Lookup user
    if (userFound)
        callback.Found(userEntity);
    else
        callback.NotFound();
}

Aus gestalterischer Sicht gefällt mir dieser Ansatz sehr gut, aber er hat den Nachteil, dass die Anrufstelle in Sprachen, die erstklassige Funktionen nicht ohne weiteres unterstützen, umfangreicher wird.


Interessant. Als Sie über Delegierte sprachen, fragte ich mich sofort, ob hier Lambda-Ausdrücke verwendet werden könnten.
7wp

Jep! Soweit ich weiß, ist die Lambda-Syntax von C # 3.0 und höher im Grunde genommen syntaktischer Zucker für anonyme Delegierte. In Java können Sie ohne die nette Lambda-Syntax oder die anonyme Delegatensyntax einfach eine anonyme Klasse erstellen. Es ist etwas hässlicher, kann aber sehr praktisch sein. Ich nehme an, dass mein C # -Beispiel heutzutage Func <UserEntity> oder ähnliches anstelle eines benannten Delegaten hätte verwenden können, aber das letzte C # -Projekt, an dem ich beteiligt war, verwendete noch Version 2.
Marc

+1 Ich mag diesen Ansatz. Ein Problem ist jedoch, dass es nicht konventionell ist und die Eintrittsbarriere für eine Codebasis geringfügig erhöht.
Timoxley

4

Wir verwenden CSLA.NET und es wird die Ansicht vertreten, dass ein fehlgeschlagener Datenabruf ein "leeres" Objekt zurückgeben sollte. Dies ist eigentlich ziemlich ärgerlich, da es die Konvention obj.IsNewerfordert, zu prüfen, ob eher alsobj == null .

Wie bereits erwähnt, Null-Rückgabewerte dazu, dass der Code sofort fehlschlägt, wodurch die Wahrscheinlichkeit von Stealth-Problemen durch leere Objekte verringert wird.

Ich persönlich denke null eleganter.

Es ist ein sehr häufiger Fall, und ich bin überrascht, dass die Leute hier davon überrascht zu sein scheinen: In jeder Webanwendung werden Daten häufig mithilfe eines Querystring-Parameters abgerufen, der offensichtlich entstellt werden kann, sodass der Entwickler Vorfälle von "nicht gefunden" verarbeiten muss ".

Sie könnten damit umgehen, indem Sie:

if (User.Exists (id)) {
  this.User = User.Fetch (id);
} else {
  Response.Redirect ("~ / notfound.aspx");
}}

... aber das ist jedes Mal ein zusätzlicher Aufruf der Datenbank, was auf stark frequentierten Seiten ein Problem sein kann. Wohingegen:

this.User = User.Fetch (id);

if (this.User == null) {
  Response.Redirect ("~ / notfound.aspx");
}}

... erfordert nur einen Anruf.


4

Ich bevorzuge null, da es mit dem Null-Coalescing-Operator ( ??) kompatibel ist .


4

Ich würde sagen, return null anstelle eines leeren Objekts.

Bei der hier erwähnten spezifischen Instanz suchen Sie nach einem Benutzer anhand der Benutzer-ID. Dies ist eine Art Schlüssel für diesen Benutzer. In diesem Fall möchte ich wahrscheinlich eine Ausnahme auslösen, wenn keine Benutzerinstanzinstanz gefunden wird .

Dies ist die Regel, die ich im Allgemeinen befolge:

  • Wenn bei einer Suche nach Primärschlüssel keine Ergebnisse gefunden werden, lösen Sie ObjectNotFoundException aus.
  • Wenn bei einem Fund nach anderen Kriterien kein Ergebnis gefunden wurde, geben Sie null zurück.
  • Wenn bei einer Suche nach einem Nicht-Schlüsselkriterium kein Ergebnis gefunden wird, das möglicherweise mehrere Objekte zurückgibt, wird eine leere Sammlung zurückgegeben.

Warum würden Sie in einem dieser Fälle eine Ausnahme auslösen? Manchmal sind keine Benutzer in der Datenbank vorhanden, und wir gehen davon aus, dass dies möglicherweise nicht der Fall ist. Es ist kein außergewöhnliches Verhalten.
Siride

3

Es hängt vom Kontext ab, aber ich gebe im Allgemeinen null zurück, wenn ich nach einem bestimmten Objekt suche (wie in Ihrem Beispiel), und eine leere Sammlung zurück, wenn ich nach einer Reihe von Objekten suche, aber keine.

Wenn Sie einen Fehler in Ihrem Code gemacht haben und die Rückgabe von null zu Nullzeigerausnahmen führt, ist es umso besser, je früher Sie dies erkennen. Wenn Sie ein leeres Objekt zurückgeben, funktioniert die erstmalige Verwendung möglicherweise, später werden jedoch möglicherweise Fehler angezeigt.


+1 Ich habe die gleiche Logik in Frage gestellt, die Sie hier sagen, weshalb ich die Frage gestellt habe, um zu sehen, wie andere Meinungen dazu lauten würden
7wp

3

Die besten in diesem Fall geben "null" zurück, wenn es keinen solchen Benutzer gibt. Machen Sie Ihre Methode auch statisch.

Bearbeiten:

Normalerweise sind solche Methoden Mitglieder einer "Benutzer" -Klasse und haben keinen Zugriff auf ihre Instanzmitglieder. In diesem Fall sollte die Methode statisch sein, andernfalls müssen Sie eine Instanz von "User" erstellen und dann die GetUserById-Methode aufrufen, die eine weitere "User" -Instanz zurückgibt. Stimmen Sie zu, dass dies verwirrend ist. Wenn die GetUserById-Methode jedoch Mitglied einer "DatabaseFactory" -Klasse ist, ist es kein Problem, sie als Instanzmitglied zu belassen.


Kann ich fragen, warum ich meine Methode statisch machen möchte? Was ist, wenn ich Dependency Injection verwenden möchte?
7wp

Ok, jetzt verstehe ich deine Logik. Aber ich halte mich an das Repository-Muster und verwende gerne die Abhängigkeitsinjektion für meine Repositorys, daher kann ich keine statischen Methoden verwenden. Aber +1 für den Vorschlag, null zurückzugeben :)
7wp

3

Ich persönlich gebe eine Standardinstanz des Objekts zurück. Der Grund ist, dass ich erwarte, dass die Methode Null zu Viele oder Null zu Eins zurückgibt (abhängig vom Zweck der Methode). Der einzige Grund, warum es sich bei diesem Ansatz um einen Fehlerzustand jeglicher Art handelt, besteht darin, dass die Methode keine Objekte zurückgegeben hat und dies immer erwartet wurde (in Form einer Eins-zu-Viele-Rückgabe oder einer einzelnen Rückgabe).

Die Annahme, dass dies eine Frage der Geschäftsdomäne ist, sehe ich von dieser Seite der Gleichung aus einfach nicht. Die Normalisierung von Rückgabetypen ist eine gültige Frage zur Anwendungsarchitektur. Zumindest unterliegt es einer Standardisierung der Codierungspraktiken. Ich bezweifle, dass es einen Geschäftsbenutzer gibt, der sagen wird "Geben Sie ihm in Szenario X einfach eine Null".


+1 Ich mag die alternative Sicht auf das Problem. Sie sagen also im Grunde, welcher Ansatz ich wählen sollte, sollte in Ordnung sein, solange die Methode in der gesamten Anwendung konsistent ist?
7wp

1
Das ist mein Glaube. Ich denke, Konsistenz ist extrem wichtig. Wenn Sie Dinge auf mehrere Arten an mehreren Orten ausführen, besteht ein höheres Risiko für neue Fehler. Wir haben uns persönlich für den Standardobjektansatz entschieden, da er gut mit dem Essenzmuster zusammenarbeitet, das wir in unserem Domänenmodell verwenden. Wir haben eine einzige generische Erweiterungsmethode, die wir anhand aller Domänenobjekte testen können, um festzustellen, ob sie ausgefüllt sind oder nicht, sodass wir wissen, dass jede DO mit einem Aufruf von objectname.IsDefault () getestet werden kann, wodurch Gleichheitsprüfungen direkt vermieden werden .
Joseph Ferris

3

In unseren Business Objects haben wir zwei Haupt-Get-Methoden:

Um die Dinge im Kontext einfach zu halten oder Sie fragen, wären sie:

// Returns null if user does not exist
public UserEntity GetUserById(Guid userId)
{
}

// Returns a New User if user does not exist
public UserEntity GetNewOrExistingUserById(Guid userId)
{
}

Die erste Methode wird beim Abrufen bestimmter Entitäten verwendet, die zweite Methode wird speziell beim Hinzufügen oder Bearbeiten von Entitäten auf Webseiten verwendet.

Dies ermöglicht es uns, das Beste aus beiden Welten in dem Kontext zu haben, in dem sie verwendet werden.


3

Ich bin ein französischer IT-Student, also entschuldigen Sie mein schlechtes Englisch. In unseren Klassen wird uns gesagt, dass eine solche Methode niemals null oder ein leeres Objekt zurückgeben sollte. Der Benutzer dieser Methode muss zuerst überprüfen, ob das gesuchte Objekt vorhanden ist, bevor er versucht, es abzurufen.

Mit Java werden wir gebeten, a hinzuzufügen assert exists(object) : "You shouldn't try to access an object that doesn't exist"; am Anfang jeder Methode, die null zurückgeben könnte , um die "Vorbedingung" auszudrücken (ich weiß nicht, was das Wort auf Englisch ist).

IMO ist das wirklich nicht einfach zu bedienen, aber das ist es, was ich benutze und auf etwas Besseres warte.


1
Vielen Dank für Ihre Antwort. Aber ich mag die Idee nicht, zuerst zu überprüfen, ob es existiert. Der Grund dafür ist, dass eine zusätzliche Abfrage an die Datenbank generiert wird. In einer Anwendung, auf die täglich Millionen von Menschen zugreifen, kann dies zu dramatischen Leistungseinbußen führen.
7wp

1
Ein Vorteil ist, dass die Überprüfung auf Existenz angemessen abstrakt ist: if (userExists) ist etwas besser lesbar, näher an der Problemdomäne und weniger "computery" als: if (user == null)
timoxley

Und ich würde argumentieren, dass 'if (x == null)' ein jahrzehntealtes Muster ist, bei dem Sie, wenn Sie es noch nicht gesehen haben, nicht lange Code geschrieben haben (und Sie sollten sich daran gewöhnen, wie es ist Millionen von Codezeilen). "Computer"? Wir sprechen über Datenbankzugriff ...
Lloyd Sargent

3

Wenn der Fall, dass der Benutzer nicht gefunden wird, häufig genug auftritt und Sie je nach den Umständen auf verschiedene Weise damit umgehen möchten (manchmal eine Ausnahme auslösen, manchmal einen leeren Benutzer ersetzen), können Sie auch etwas verwenden, Optiondas dem MaybeTyp von F # oder Haskell nahe kommt , der den Fall "kein Wert" explizit von "etwas gefunden!" trennt. Der Datenbankzugriffscode könnte folgendermaßen aussehen:

public Option<UserEntity> GetUserById(Guid userId)
{
 //Imagine some code here to access database.....

 //Check if data was returned and return a null if none found
 if (!DataExists)
    return Option<UserEntity>.Nothing; 
 else
    return Option.Just(existingUserEntity);
}

Und so verwendet werden:

Option<UserEntity> result = GetUserById(...);
if (result.IsNothing()) {
    // deal with it
} else {
    UserEntity value = result.GetValue();
}

Leider scheint jeder einen eigenen Typ zu rollen.


2

Ich gebe normalerweise null zurück. Es bietet einen schnellen und einfachen Mechanismus, um zu erkennen, ob etwas schief gelaufen ist, ohne Ausnahmen zu werfen und überall Tonnen von Versuchen / Fangen zu verwenden.


2

Für Sammlungstypen würde ich eine leere Sammlung zurückgeben, für alle anderen Typen bevorzuge ich die Verwendung der NullObject-Muster für die Rückgabe eines Objekts, das dieselbe Schnittstelle wie die des zurückgebenden Typs implementiert. Einzelheiten zum Muster finden Sie im Linktext

Bei Verwendung des NullObject-Musters wäre dies: -

public UserEntity GetUserById(Guid userId)

{// Stellen Sie sich hier Code vor, um auf die Datenbank zuzugreifen .....

 //Check if data was returned and return a null if none found
 if (!DataExists)
    return new NullUserEntity(); //Should I be doing this here instead? return new UserEntity();  
 else
    return existingUserEntity;

}}

class NullUserEntity: IUserEntity { public string getFirstName(){ return ""; } ...} 

2

Um das, was andere gesagt haben, prägnanter auszudrücken ...

Ausnahmen gelten für außergewöhnliche Umstände

Wenn es sich bei dieser Methode um eine reine Datenzugriffsschicht handelt, würde ich sagen, dass bei einem Parameter, der in eine select-Anweisung aufgenommen wird, möglicherweise keine Zeilen gefunden werden, aus denen ein Objekt erstellt werden kann. Daher wäre die Rückgabe von null akzeptabel ist Datenzugriffslogik.

Wenn ich dagegen erwarten würde, dass mein Parameter einen Primärschlüssel widerspiegelt und ich nur eine Zeile zurückbekomme, würde ich eine Ausnahme auslösen, wenn ich mehr als eine zurückbekomme. 0 ist in Ordnung, um null zurückzugeben, 2 nicht.

Wenn ich einen Anmeldecode hätte, der gegen einen LDAP-Anbieter und dann gegen eine Datenbank geprüft wurde, um weitere Details zu erhalten, und ich erwartete, dass diese jederzeit synchron sein sollten, könnte ich dann die Ausnahme auslösen. Wie andere sagten, sind es Geschäftsregeln.

Jetzt werde ich sagen, dass dies eine allgemeine Regel ist. Es gibt Zeiten, in denen Sie das vielleicht brechen möchten. Meine Erfahrungen und Experimente mit C # (viel davon) und Java (ein bisschen davon) haben mich jedoch gelehrt, dass es in Bezug auf die Leistung viel teurer ist, Ausnahmen zu behandeln, als vorhersehbare Probleme über bedingte Logik zu behandeln. Ich spreche von 2 oder 3 Größenordnungen, die in einigen Fällen teurer sind. Wenn es also möglich ist, dass Ihr Code in einer Schleife endet, würde ich empfehlen, null zurückzugeben und darauf zu testen.


2

Vergib mir meinen Pseudo-PHP / Code.

Ich denke, es hängt wirklich von der beabsichtigten Verwendung des Ergebnisses ab.

Wenn Sie den Rückgabewert bearbeiten / ändern und speichern möchten, geben Sie ein leeres Objekt zurück. Auf diese Weise können Sie dieselbe Funktion verwenden, um Daten in ein neues oder vorhandenes Objekt einzufügen.

Angenommen, ich habe eine Funktion, die einen Primärschlüssel und ein Datenarray verwendet, die Zeile mit Daten füllt und dann den resultierenden Datensatz in der Datenbank speichert. Da ich beabsichtige, das Objekt so oder so mit meinen Daten zu füllen, kann es ein großer Vorteil sein, ein leeres Objekt vom Getter zurückzubekommen. Auf diese Weise kann ich in beiden Fällen identische Operationen ausführen. Sie verwenden das Ergebnis der Getter-Funktion, egal was passiert.

Beispiel:

function saveTheRow($prim_key, $data) {
    $row = getRowByPrimKey($prim_key);

    // Populate the data here

    $row->save();
}

Hier können wir sehen, dass dieselbe Reihe von Operationen alle Datensätze dieses Typs manipuliert.

Wenn die endgültige Absicht des Rückgabewerts jedoch darin besteht, etwas mit den Daten zu lesen und zu tun, würde ich null zurückgeben. Auf diese Weise kann ich sehr schnell feststellen, ob keine Daten zurückgegeben wurden, und dem Benutzer die entsprechende Nachricht anzeigen.

Normalerweise fange ich Ausnahmen in meiner Funktion ab, die die Daten abrufen (damit ich Fehlermeldungen usw. protokollieren kann) und gebe dann direkt vom Fang null zurück. Für den Endbenutzer spielt es im Allgemeinen keine Rolle, wo das Problem liegt. Daher finde ich es am besten, meine Fehlerprotokollierung / -verarbeitung direkt in der Funktion zu kapseln, die die Daten abruft. Wenn Sie in einem großen Unternehmen eine gemeinsam genutzte Codebasis verwalten, ist dies besonders vorteilhaft, da Sie selbst dem faulsten Programmierer die ordnungsgemäße Fehlerprotokollierung / -behandlung aufzwingen können.

Beispiel:

function displayData($row_id) {
    // Logging of the error would happen in this function
    $row = getRow($row_id);
    if($row === null) {
        // Handle the error here
    }

    // Do stuff here with data
}

function getRow($row_id) {
 $row = null;
 try{
     if(!$db->connected()) {
   throw excpetion("Couldn't Connect");
  }

  $result = $db->query($some_query_using_row_id);

  if(count($result) == 0 ) {
   throw new exception("Couldn't find a record!");
  }

  $row = $db->nextRow();

 } catch (db_exception) {
  //Log db conn error, alert admin, etc...
  return null; // This way I know that null means an error occurred
 }
 return $row;
}

Das ist meine allgemeine Regel. Bisher hat es gut funktioniert.


2

Interessante Frage und ich denke, es gibt keine "richtige" Antwort, da es immer von der Verantwortung Ihres Codes abhängt. Weiß Ihre Methode, ob keine gefundenen Daten ein Problem darstellen oder nicht? In den meisten Fällen lautet die Antwort "Nein". Deshalb ist es perfekt, Null zurückzugeben und den Anrufer die Situation bearbeiten zu lassen.

Vielleicht besteht ein guter Ansatz zur Unterscheidung von Wurfmethoden von Null-Rückgabemethoden darin, eine Konvention in Ihrem Team zu finden: Methoden, die besagen, dass sie etwas "bekommen", sollten eine Ausnahme auslösen, wenn nichts zu bekommen ist. Methoden, die möglicherweise null zurückgeben, können anders benannt werden, möglicherweise stattdessen "Suchen ...".


+1 Ich mag die Idee, eine einheitliche Namenskonvention zu verwenden, um dem Programmierer zu signalisieren, wie diese Funktion genutzt werden soll.
7wp

1
Plötzlich erkenne ich, dass dies das ist, was LINQ tut: Betrachten Sie First (...) vs. FirstOrDefault (...)
Marc Wittke

2

Wenn das zurückgegebene Objekt wiederholt werden kann, würde ich ein leeres Objekt zurückgeben, damit ich nicht zuerst auf null testen muss.

Beispiel:

bool IsAdministrator(User user)
{
    var groupsOfUser = GetGroupsOfUser(user);

    // This foreach would cause a run time exception if groupsOfUser is null.
    foreach (var groupOfUser in groupsOfUser) 
    {
        if (groupOfUser.Name == "Administrators")
        {
            return true;
        }
    }

    return false;
}

2

Ich möchte von keiner Methode null zurückgeben, sondern stattdessen den Funktionstyp Option verwenden. Methoden, die kein Ergebnis zurückgeben können, geben eine leere Option anstelle von null zurück.

Solche Methoden, die kein Ergebnis zurückgeben können, sollten dies auch durch ihren Namen anzeigen. Normalerweise setze ich Try oder TryGet oder TryFind an den Anfang des Methodennamens, um anzuzeigen, dass möglicherweise ein leeres Ergebnis zurückgegeben wird (z. B. TryFindCustomer, TryLoadFile usw.).

Das lässt den Anrufer verschiedene Techniken anwenden, wie Sammlung Pipelining (siehe Martin Fowler Sammlung Pipeline ) auf das Ergebnis.

Hier ist ein weiteres Beispiel, in dem die Rückgabe von Option anstelle von Null verwendet wird, um die Codekomplexität zu verringern: Reduzieren der zyklomatischen Komplexität: Funktionstyp der Option


1
Ich habe eine Antwort geschrieben, ich kann sehen, dass sie Ihrer ähnlich ist, als ich nach oben gescrollt habe, und ich stimme zu, dass Sie einen Optionstyp mit einer generischen Sammlung mit 0 oder 1 Elementen implementieren können. Danke für die zusätzlichen Links.
Gabriel P.

1

Mehr Fleisch zum Mahlen: Nehmen wir an, mein DAL gibt eine NULL für GetPersonByID zurück, wie von einigen empfohlen. Was soll meine (ziemlich dünne) BLL tun, wenn sie eine NULL erhält? Geben Sie NULL weiter und lassen Sie den Endverbraucher sich darum kümmern (in diesem Fall eine ASP.Net-Seite)? Wie wäre es, wenn die BLL eine Ausnahme auslöst?

Die BLL wird möglicherweise von ASP.Net und Win App oder einer anderen Klassenbibliothek verwendet. Ich denke, es ist unfair zu erwarten, dass der Endverbraucher von sich aus "weiß", dass die Methode GetPersonByID eine Null zurückgibt (es sei denn, es werden Nulltypen verwendet, denke ich ).

Mein Ansatz (für das, was es wert ist) ist, dass mein DAL NULL zurückgibt, wenn nichts gefunden wird. FÜR EINIGE OBJEKTE ist das in Ordnung - es könnte eine 0: viele Liste von Dingen sein, also ist es in Ordnung, keine Dinge zu haben (z. B. eine Liste von Lieblingsbüchern). In diesem Fall gibt meine BLL eine leere Liste zurück. Wenn ich für die meisten Dinge einer einzelnen Entität (z. B. Benutzer, Konto, Rechnung) keine habe, ist dies definitiv ein Problem und eine kostspielige Ausnahme. Da das Abrufen eines Benutzers durch eine eindeutige Kennung, die zuvor von der Anwendung angegeben wurde, immer einen Benutzer zurückgeben sollte, ist die Ausnahme eine "richtige" Ausnahme, wie in ihrer Ausnahme. Der Endverbraucher der BLL (ASP.Net, f'rinstance) erwartet immer nur, dass die Dinge gut laufen, sodass ein nicht behandelter Ausnahmebehandler verwendet wird, anstatt jeden einzelnen Aufruf von GetPersonByID in einen try-catch-Block zu packen.

Wenn mein Ansatz ein eklatantes Problem aufweist, lassen Sie es mich bitte wissen, da ich immer lernbegierig bin. Wie andere Plakate bereits sagten, sind Ausnahmen kostspielige Dinge, und der Ansatz "Zuerst prüfen" ist gut, aber Ausnahmen sollten genau das sein - außergewöhnlich.

Ich genieße diesen Beitrag, viele gute Vorschläge für "es kommt darauf an" -Szenarien :-)


Und natürlich bin ich heute auf ein Szenario gestoßen, in dem ich NULL von meiner BLL zurückgeben werde ;-) Das heißt, ich könnte immer noch eine Ausnahme auslösen und try / catch in meiner konsumierenden Klasse verwenden, ABER ich habe immer noch ein Problem : Woher weiß meine konsumierende Klasse, wie man einen Versuch / Fang verwendet, ähnlich wie weiß sie, ob sie nach NULL sucht?
Mike Kingscott

Sie können dokumentieren, dass eine Methode eine Ausnahme über das Doctag @throws auslöst, und Sie würden dokumentieren, dass sie im Doctag @return null zurückgeben kann.
Timoxley

1

Ich denke, Funktionen sollten für den Zustand Ihrer Codebasis nicht null zurückgeben. Ich kann mir einige Gründe vorstellen:

Es wird eine große Anzahl von Schutzklauseln geben, die Nullreferenzen behandeln if (f() != null) .

Was ist null, ist es eine akzeptierte Antwort oder ein Problem? Ist null ein gültiger Status für ein bestimmtes Objekt? (Stellen Sie sich vor, Sie sind ein Client für den Code). Ich meine, alle Referenztypen können null sein, aber sollten sie?

Haben null herumhängen, werden von Zeit zu Zeit fast immer einige unerwartete NullRef-Ausnahmen auftreten, wenn Ihre Codebasis wächst.

Es gibt einige Lösungen tester-doer patternoder die Implementierung der option typefunktionalen Programmierung.


0

Ich bin ratlos über die Anzahl der Antworten (im gesamten Web), die besagen, dass Sie zwei Methoden benötigen: eine "IsItThere ()" - Methode und eine "GetItForMe ()" - Methode, was zu einer Racebedingung führt. Was ist falsch an einer Funktion, die null zurückgibt, einer Variablen zuweist und die Variable in einem Test auf Null überprüft? Mein früherer C-Code war gespickt mit

if (NULL! = (Variable = Funktion (Argumente ...))) {

Sie erhalten also den Wert (oder Null) in einer Variablen und das Ergebnis auf einmal. Wurde diese Redewendung vergessen? Warum?


0

Ich stimme den meisten Posts hier zu, die dazu neigen null.

Meine Argumentation ist, dass das Generieren eines leeren Objekts mit nicht nullbaren Eigenschaften Fehler verursachen kann. Beispielsweise hätte eine Entität mit einer int IDEigenschaft einen Anfangswert von ID = 0, was ein vollständig gültiger Wert ist. Sollte dieses Objekt unter bestimmten Umständen in einer Datenbank gespeichert werden, wäre dies eine schlechte Sache.

Für alles mit einem Iterator würde ich immer die leere Sammlung verwenden. Etwas wie

foreach (var eachValue in collection ?? new List<Type>(0))

ist meiner Meinung nach Codegeruch. Sammlungseigenschaften sollten niemals null sein.

Ein Randfall ist String. Viele Leute sagen, String.IsNullOrEmptyist nicht wirklich notwendig, aber man kann nicht immer zwischen einer leeren Zeichenfolge und null unterscheiden. Darüber hinaus unterscheiden einige Datenbanksysteme (Oracle) überhaupt nicht zwischen ihnen ( ''werden als gespeichert DBNULL), sodass Sie gezwungen sind, sie gleich zu behandeln. Der Grund dafür ist, dass die meisten Zeichenfolgenwerte entweder von Benutzereingaben oder von externen Systemen stammen, während weder Textfelder noch die meisten Austauschformate unterschiedliche Darstellungen für ''und haben null. Selbst wenn der Benutzer einen Wert entfernen möchte, kann er nur die Eingabesteuerung löschen. Auch die Unterscheidung von nullbar und nicht nullbarnvarchar Datenbankfeldern ist mehr als fraglich, wenn Ihr DBMS kein Orakel ist - ein Pflichtfeld, das dies zulässt''ist seltsam, Ihre Benutzeroberfläche würde dies niemals zulassen, sodass Ihre Einschränkungen nicht zugeordnet werden. Die Antwort hier lautet meiner Meinung nach, immer gleich damit umzugehen.

Zu Ihrer Frage zu Ausnahmen und Leistung: Wenn Sie eine Ausnahme auslösen, die Sie in Ihrer Programmlogik nicht vollständig behandeln können, müssen Sie irgendwann abbrechen, was auch immer Ihr Programm tut, und den Benutzer bitten, alles zu wiederholen, was er gerade getan hat. In diesem Fall ist die Leistungseinbuße von a catchwirklich die geringste Ihrer Sorgen - wenn Sie den Benutzer fragen müssen, ist dies der Elefant im Raum (was bedeutet, dass die gesamte Benutzeroberfläche neu gerendert oder HTML über das Internet gesendet wird). Wenn Sie also nicht dem Anti-Muster von " Programmablauf mit Ausnahmen " folgen, stören Sie sich nicht, werfen Sie einfach einen, wenn es Sinn macht. Selbst in Grenzfällen wie "Validierungsausnahme" ist die Leistung kein Problem, da Sie den Benutzer auf jeden Fall erneut fragen müssen.


0

Ein asynchrones TryGet-Muster:

Für synchrone Methoden, glaube ich @Johann Gerell die Antwort ist das Muster in allen Fällen zu verwenden.

Das TryGet-Muster mit dem outParameter funktioniert jedoch nicht mit Async-Methoden.

Mit den Tupel-Literalen von C # 7 können Sie jetzt Folgendes tun:

async Task<(bool success, SomeObject o)> TryGetSomeObjectByIdAsync(Int32 id)
{
    if (InternalIdExists(id))
    {
        o = await InternalGetSomeObjectAsync(id);

        return (true, o);
    }
    else
    {
        return (false, default(SomeObject));
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.