it-swarm.com.de

Sollte ich den Rückgabewert eines Methodenaufrufs überprüfen, auch wenn ich weiß, dass die Methode keine fehlerhaften Eingaben zurückgeben kann?

Ich frage mich, ob ich mich gegen den Rückgabewert eines Methodenaufrufs verteidigen soll, indem ich bestätige, dass sie meinen Erwartungen entsprechen, auch wenn ich weiß, dass die von mir aufgerufene Methode diese Erwartungen erfüllt.

GEGEBEN

User getUser(Int id)
{
    User temp = new User(id);
    temp.setName("John");

    return temp;
}

SOLL ICH TUN

void myMethod()
{
    User user = getUser(1234);
    System.out.println(user.getName());
}

OR

void myMethod()
{
    User user = getUser(1234);

    // Validating
    Preconditions.checkNotNull(user, "User can not be null.");
    Preconditions.checkNotNull(user.getName(), "User's name can not be null.");

    System.out.println(user.getName());
}

Ich frage dies auf konzeptioneller Ebene. Wenn ich das Innenleben der Methode kenne, rufe ich an. Entweder weil ich es geschrieben habe oder weil ich es inspiziert habe. Und die Logik der möglichen Werte, die es zurückgibt, erfüllt meine Voraussetzungen. Ist es "besser" oder "angemessener", die Validierung zu überspringen, oder sollte ich mich trotzdem gegen falsche Werte verteidigen, bevor ich mit der Methode fortfahre, die ich gerade implementiere, auch wenn sie immer erfolgreich sein sollte.


Mein Fazit aus allen Antworten (zögern Sie nicht, zu Ihren eigenen zu kommen):

  • Die Methode hat sich in der Vergangenheit als schlecht verhalten
  • Die Methode stammt aus einer nicht vertrauenswürdigen Quelle
  • Die Methode wird von anderen Orten aus verwendet und gibt die Nachbedingungen nicht explizit an
  • Die Methode entspricht genau Ihrer (Details siehe ausgewählte Antwort)
  • Die Methode definiert ihren Vertrag explizit mit einem ordnungsgemäßen Dokument, einer Typensicherheit, einem Komponententest oder einer Überprüfung nach dem Zustand
  • Die Leistung ist kritisch (in diesem Fall kann eine Bestätigung im Debug-Modus als hybrider Ansatz funktionieren).
30
Didier A.

Das hängt davon ab, wie wahrscheinlich es ist, dass sich getUser und myMethod ändern, und was noch wichtiger ist, wie wahrscheinlich es ist, dass sie sich unabhängig voneinander ändern.

Wenn Sie irgendwie sicher wissen, dass sich getUser in Zukunft niemals ändern wird, dann ist es eine Zeitverschwendung, es zu validieren, genauso wie es Zeitverschwendung ist, zu validieren, dass i einen Wert von 3 hat unmittelbar nach einem i = 3; Aussage. In Wirklichkeit weißt du das nicht. Aber es gibt einige Dinge, die Sie wissen:

  • Leben diese beiden Funktionen "zusammen"? Mit anderen Worten, haben sie dieselben Leute, die sie verwalten, sind sie Teil derselben Datei und bleiben sie daher wahrscheinlich alleine "synchron"? In diesem Fall ist es wahrscheinlich übertrieben, Validierungsprüfungen hinzuzufügen, da dies lediglich mehr Code bedeutet, der sich jedes Mal ändern muss (und möglicherweise Fehler hervorruft), wenn sich die beiden Funktionen ändern oder in eine andere Anzahl von Funktionen umgestaltet werden.

  • Ist die getUser-Funktion Teil einer dokumentierten API mit einem bestimmten Vertrag und myMethod lediglich ein Client dieser API in einer anderen Codebasis? Wenn ja, können Sie diese Dokumentation lesen, um herauszufinden, ob Sie Rückgabewerte validieren sollten (oder Eingabeparameter vorvalidieren!) Oder ob es wirklich sicher ist, blind dem glücklichen Pfad zu folgen. Wenn die Dokumentation dies nicht klar macht, bitten Sie den Betreuer, dies zu beheben.

  • Wenn diese bestimmte Funktion in der Vergangenheit plötzlich und unerwartet ihr Verhalten so geändert hat, dass Ihr Code beschädigt wurde, haben Sie das Recht, paranoid zu sein. Fehler neigen dazu, sich zu sammeln.

Beachten Sie, dass alle oben genannten Punkte auch dann gelten, wenn Sie der ursprüngliche Autor beider Funktionen sind. Wir wissen nicht, ob von diesen beiden Funktionen erwartet wird, dass sie für den Rest ihres Lebens "zusammenleben", oder ob sie langsam in separate Module zerfallen oder ob Sie sich mit einem Fehler im Alter irgendwie in den Fuß geschossen haben Versionen von getUser. Aber Sie können wahrscheinlich eine ziemlich anständige Vermutung anstellen.

42
Ixrec

Die Antwort von Ixrec ist gut, aber ich werde einen anderen Ansatz wählen, weil ich glaube, dass es sich lohnt, darüber nachzudenken. Für die Zwecke dieser Diskussion werde ich über Behauptungen sprechen, da dies der Name ist, unter dem Ihre Preconditions.checkNotNull() traditionell bekannt ist.

Was ich vorschlagen möchte, ist, dass Programmierer oft den Grad überschätzen, in dem sie sicher sind, dass sich etwas auf eine bestimmte Weise verhält, und die Konsequenzen unterschätzen, wenn es sich nicht so verhält. Außerdem erkennen Programmierer die Aussagekraft von Behauptungen häufig nicht als Dokumentation. Nach meiner Erfahrung behaupten Programmierer die Dinge viel seltener als sie sollten.

Mein Motto ist:

Die Frage, die Sie sich immer stellen sollten, lautet nicht "soll ich das behaupten?" aber "gibt es etwas, das ich vergessen habe zu behaupten?"

Wenn Sie absolut sicher sind , dass sich etwas auf eine bestimmte Weise verhält, werden Sie natürlich nicht behaupten, dass es tatsächlich so war benimm dich so, und das ist größtenteils vernünftig. Es ist nicht wirklich möglich, Software zu schreiben, wenn Sie nichts vertrauen können.

Aber es gibt auch Ausnahmen von dieser Regel. Seltsamerweise gibt es, um Ixrecs Beispiel zu nehmen, eine Art Situation, in der es durchaus wünschenswert ist, int i = 3; Mit der Behauptung zu folgen, dass i tatsächlich innerhalb eines bestimmten Bereichs liegt. Dies ist die Situation, in der i wahrscheinlich von jemandem geändert wird, der verschiedene Was-wäre-wenn-Szenarien ausprobiert, und der folgende Code davon abhängt, dass i einen Wert innerhalb eines bestimmten Bereichs hat, und zwar ist nicht sofort ersichtlich, wenn man sich schnell den folgenden Code ansieht, was der akzeptable Bereich ist. Also, int i = 3; assert i >= 0 && i < 5; Mag zunächst unsinnig erscheinen, aber wenn Sie darüber nachdenken, sagt es Ihnen, dass Sie mit i spielen können, und es sagt Ihnen auch, in welchem ​​Bereich Sie bleiben müssen. Eine Behauptung ist die beste Art der Dokumentation, da sie von der Maschine erzwungen wird , also eine perfekte Garantie ist, und es wird zusammen mit dem Code überarbeitet, so dass es immer relevant bleibt.

Ihr Beispiel getUser(int id) ist kein sehr weit entfernter konzeptioneller Verwandter des Beispiels assign-constant-and-assert-in-range. Sie sehen per Definition, dass Fehler auftreten, wenn Dinge ein Verhalten aufweisen, das sich von dem erwarteten Verhalten unterscheidet. Es stimmt, wir können nicht alles behaupten, daher wird es manchmal zu Debugging kommen. In der Branche ist es jedoch allgemein bekannt, dass je schneller wir einen Fehler erkennen, desto weniger Kosten entstehen. Die Fragen, die Sie stellen sollten, sind nicht nur, wie sicher Sie sind, dass getUser() niemals null zurückgibt (und niemals einen Benutzer mit einem Nullnamen zurückgibt), sondern auch, welche Arten von schlechten Dingen mit dem Rest passieren werden des Codes, wenn er tatsächlich eines Tages etwas Unerwartetes zurückgibt, und wie einfach es ist, schnell auf den Rest des Codes zu schauen, um genau zu wissen, was von getUser() erwartet wurde.

Wenn das unerwartete Verhalten dazu führen würde, dass Ihre Datenbank beschädigt wird, sollten Sie dies möglicherweise auch dann behaupten, wenn Sie zu 101% sicher sind, dass dies nicht der Fall ist. Wenn ein null Benutzername einen seltsamen kryptischen, nicht nachvollziehbaren Fehler verursachen würde, der Tausende von Zeilen weiter unten und Milliarden von Taktzyklen später liegt, ist es vielleicht am besten, so früh wie möglich zu scheitern.

Auch wenn ich der Antwort von Ixrec nicht widerspreche, würde ich vorschlagen, dass Sie ernsthaft darüber nachdenken, dass Behauptungen nichts kosten. Es gibt also wirklich nichts zu verlieren, nur um gewonnen zu werden, wenn Sie sie großzügig verwenden.

22
Mike Nakis

Eine bestimmte Nr.

Ein Anrufer sollte niemals prüfen, ob die von ihm aufgerufene Funktion seinem Vertrag entspricht. Der Grund ist einfach: Es gibt möglicherweise sehr viele Anrufer, und es ist aus konzeptioneller Sicht für jeden einzelnen Anrufer unpraktisch und sogar falsch, die Logik zur Überprüfung der Ergebnisse anderer Funktionen zu duplizieren.

Wenn Überprüfungen durchgeführt werden sollen, sollte jede Funktion ihre eigenen Nachbedingungen überprüfen. Die Nachbedingungen geben Ihnen die Gewissheit, dass Sie die Funktion korrekt implementiert haben und die Benutzer sich auf den Vertrag Ihrer Funktion verlassen können.

Wenn eine bestimmte Funktion niemals null zurückgeben soll, sollte dies dokumentiert werden und Teil des Vertrags dieser Funktion werden. Dies ist der Punkt dieser Verträge: Bieten Sie Benutzern einige Invarianten, auf die sie sich verlassen können, damit sie beim Schreiben ihrer eigenen Funktionen weniger Hürden haben.

8
Paul

Ich würde argumentieren, dass die Prämisse Ihrer Frage falsch ist: Warum sollten Sie zunächst eine Nullreferenz verwenden?

Das Null-Objektmuster ist eine Möglichkeit, Objekte zurückzugeben, die im Wesentlichen nichts bewirken: Geben Sie anstelle von Null für Ihren Benutzer ein Objekt zurück, das "kein Benutzer" darstellt.

Dieser Benutzer wäre inaktiv, hätte einen leeren Namen und andere Nicht-Null-Werte, die "hier nichts" anzeigen. Der Hauptvorteil ist, dass Sie Ihr Programm nicht verunreinigen müssen, um Nullprüfungen, Protokollierung usw. durchzuführen. Sie verwenden nur Ihre Objekte.

Warum sollte sich ein Algorithmus um einen Nullwert kümmern? Die Schritte sollten gleich sein. Es ist genauso einfach und viel klarer, ein Null-Objekt zu haben, das Null, eine leere Zeichenfolge usw. zurückgibt.

Hier ist ein Beispiel für die Codeprüfung auf Null:

User u = getUser();
String displayName = "";
if (u != null) {
  displayName = u.getName();
}
return displayName;

Hier ist ein Beispiel für Code, der ein Nullobjekt verwendet:

return getUser().getName();

Welches ist klarer? Ich glaube der zweite ist.


Während die Frage mit Java markiert ist, ist es erwähnenswert, dass das Nullobjektmuster in C++ wirklich nützlich ist , wenn Referenzen verwendet werden. Java Referenzen ähneln eher C++ - Zeigern und erlauben Null. C++ - Referenzen können nicht nullptr enthalten. Wenn man etwas analog zu einer Null haben möchte In C++ ist das Nullobjektmuster der einzige mir bekannte Weg, dies zu erreichen.

5
user22815

Wenn null ein gültiger Rückgabewert der getUser-Methode ist, sollte Ihr Code dies mit einem If und nicht mit einer Ausnahme behandeln - dies ist kein Ausnahmefall.

Wenn null kein gültiger Rückgabewert der getUser-Methode ist, liegt ein Fehler in getUser vor. Die getUser-Methode sollte eine Ausnahme auslösen oder auf andere Weise behoben werden.

Wenn die getUser-Methode Teil einer externen Bibliothek ist oder auf andere Weise nicht geändert werden kann und Sie möchten, dass im Fall einer Null-Rückgabe Ausnahmen ausgelöst werden, schließen Sie die Klasse und die Methode ein, um die Überprüfungen durchzuführen, und lösen Sie die Ausnahme so aus, dass jede Instanz von Der Aufruf von getUser ist in Ihrem Code konsistent.

5
MatthewC

Im Allgemeinen würde ich sagen, dass dies hauptsächlich von den folgenden drei Aspekten abhängt:

  1. robustheit: Kann die aufrufende Methode mit dem Wert null umgehen? Wenn ein null -Wert zu einem RuntimeException führen könnte, würde ich eine Überprüfung empfehlen - es sei denn, die Komplexität ist gering und aufgerufene/aufrufende Methoden werden vom selben Autor erstellt, und null ist nicht erwartet (zB beide private im selben Paket).

  2. codeverantwortung: Wenn Entwickler A für die Methode getUser() verantwortlich ist und Entwickler B sie verwendet (z. B. als Teil einer Bibliothek), würde ich dringend empfehlen, ihren Wert zu überprüfen. Nur weil Entwickler B möglicherweise nichts über eine Änderung weiß, die zu einem potenziellen null Rückgabewert führt.

  3. komplexität: Je höher die Gesamtkomplexität des Programms oder der Umgebung ist, desto mehr würde ich empfehlen, den Rückgabewert zu validieren. Selbst wenn Sie sich sicher sind, dass es heute im Kontext der aufrufenden Methode nicht null sein kann, müssen Sie möglicherweise getUser() für einen anderen Anwendungsfall ändern. Sobald einige Monate oder Jahre vergangen sind und einige tausend Codezeilen hinzugefügt wurden, kann dies eine ziemliche Falle sein.

Darüber hinaus würde ich empfehlen, potenzielle null Rückgabewerte in einem JavaDoc-Kommentar zu dokumentieren. Aufgrund der Hervorhebung von JavaDoc-Beschreibungen in den meisten IDEs kann dies eine hilfreiche Warnung für jeden sein, der getUser() verwendet.

/** Returns ....
  * @param: id id of the user
  * @return: a User instance related to the id;
  *          null, if no user with identifier id exists
 **/
User getUser(Int id)
{
    ...
}
1
André