Ich frage mich, ob ich mich gegen den Rückgabewert eines Methodenaufrufs verteidigen soll, indem ich überprüfe, ob er meine Erwartungen erfüllt, 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());
}
ODER
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 auf. 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 immer noch 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):
Bestätigen Sie, wann- Die Methode hat in der Vergangenheit gezeigt, dass sie sich schlecht verhält
- Die Methode stammt aus einer nicht vertrauenswürdigen Quelle
- Die Methode wird von anderen Stellen aus verwendet und gibt die Nachbedingungen nicht explizit an
- Die Methode lebt eng mit Ihrer (siehe gewählte Antwort für Details)
- Die Methode definiert explizit ihren Vertrag mit so etwas wie einem ordnungsgemäßen Dokument, Typensicherheit, einem Komponententest oder einer Überprüfung nach dem Zustand
- Die Leistung ist kritisch (in diesem Fall kann ein Debug-Modus als hybrider Ansatz fungieren).
quelle
Null
)? Es ist wahrscheinlich genauso problematisch, wenn der Name""
(nicht null, sondern die leere Zeichenfolge) oder ist"N/A"
. Entweder können Sie dem Ergebnis vertrauen, oder Sie müssen angemessen paranoid sein.Antworten:
Dies hängt davon ab, wie wahrscheinlich sich getUser und myMethod ändern, und noch wichtiger, wie wahrscheinlich es ist, dass sie sich unabhängig voneinander ändern .
Wenn Sie irgendwie sicher sind, dass getUser sich in Zukunft nie mehr ändern wird, dann ist es eine Zeitverschwendung, es zu validieren, genauso wie es Zeitverschwendung ist, es zu validieren,
i
das unmittelbar nach eineri = 3;
Anweisung einen Wert von 3 hat . In Wirklichkeit weißt du das nicht. Aber es gibt einige Dinge, die Sie kennen:Leben diese beiden Funktionen "zusammen"? Mit anderen Worten, haben sie die gleichen Leute, die sie pflegen, sind sie Teil derselben Datei und werden daher wahrscheinlich für sich alleine "synchron" bleiben? In diesem Fall ist es wahrscheinlich übertrieben, Validierungsprüfungen hinzuzufügen, da dies lediglich bedeutet, dass jedes Mal, wenn sich die beiden Funktionen ändern oder in eine andere Anzahl von Funktionen umgestaltet werden, mehr Code geändert werden muss (und möglicherweise Fehler auftreten).
Ist die Funktion getUser Teil einer dokumentierten API mit einem bestimmten Vertrag und myMethod lediglich ein Client dieser API in einer anderen Codebasis? In diesem Fall können Sie in dieser Dokumentation nachlesen, ob Sie Rückgabewerte validieren (oder Eingabeparameter vorvalidieren!) Oder ob es wirklich sicher ist, blindlings dem Pfad der Freude zu folgen. Wenn die Dokumentation dies nicht deutlich macht, bitten Sie den Betreuer, dies zu beheben.
Wenn diese bestimmte Funktion in der Vergangenheit plötzlich und unerwartet ihr Verhalten geändert hat und Ihren Code beschädigt hat, haben Sie das Recht, paranoid zu sein. Bugs neigen dazu, sich zu häufen.
Beachten Sie, dass alle oben genannten Punkte auch dann zutreffen, 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 in älteren Versionen irgendwie in den Fuß geschossen haben Versionen von getUser. Aber Sie können wahrscheinlich eine ziemlich gute Vermutung anstellen.
quelle
getUser
vornehmen würden, damit diese Null zurückgibt, würde die explizite Prüfung Ihnen Informationen liefern, die Sie mit "NullPointerException" und der Zeilennummer nicht erhalten konnten?Die Antwort von Ixrec ist gut, aber ich werde einen anderen Ansatz verfolgen, weil ich der Meinung bin, dass es sich lohnt, darüber nachzudenken. Zum Zweck dieser Diskussion werde ich über Behauptungen sprechen, da dies der Name ist, unter dem Sie
Preconditions.checkNotNull()
traditionell bekannt sind.Was ich vorschlagen möchte, ist, dass Programmierer häufig den Grad überschätzen, um den sie sicher sind, dass sich etwas auf eine bestimmte Weise verhält, und die Konsequenzen unterschätzen, wenn sich etwas nicht auf diese Weise verhält. Außerdem erkennen Programmierer die Aussagekraft von Behauptungen häufig nicht als Dokumentation. In der Folge behaupten Programmierer meiner Erfahrung nach die Dinge weitaus seltener, als sie sollten.
Mein Motto ist:
Wenn Sie sich absolut sicher sind , dass sich etwas auf eine bestimmte Weise verhält , werden Sie natürlich nicht behaupten, dass es sich tatsächlich so verhalten hat, und das ist größtenteils vernünftig. Es ist nicht wirklich möglich, Software zu schreiben, wenn Sie nichts vertrauen können.
Aber auch von dieser Regel gibt es Ausnahmen. Seltsamerweise gibt es eine Art von Situation, in der es durchaus wünschenswert ist,
int i = 3;
mit einer Behauptung zu folgen ,i
die tatsächlich innerhalb eines bestimmten Bereichs liegt. Dies ist die Situation, in deri
es wahrscheinlich ist, dass jemand, der verschiedene Was-wäre-wenn-Szenarien ausprobiert, Änderungen vornimmt, und der folgende Code davon abhängt,i
dass ein Wert innerhalb eines bestimmten Bereichs liegt der akzeptable Bereich ist. Dasint i = 3; assert i >= 0 && i < 5;
mag zunächst unsinnig erscheinen, aber wenn Sie darüber nachdenken, erfahren Sie, dass Sie damit spieleni
können und in welchem Bereich Sie bleiben müssen. Eine Behauptung ist die beste Art der Dokumentation, weiles wird von der Maschine erzwungen , ist also eine perfekte Garantie, und es wird zusammen mit dem Code überarbeitet , so dass es immer relevant bleibt.Ihr
getUser(int id)
Beispiel ist kein sehr entfernter konzeptueller Verwandter desassign-constant-and-assert-in-range
Beispiels. Sie sehen per definitionem, dass Fehler auftreten, wenn Dinge ein Verhalten aufweisen, das sich von dem Verhalten unterscheidet, das wir erwartet haben. Es stimmt, wir können nicht alles behaupten, daher wird es manchmal zu einem Debugging kommen. In der Branche ist es jedoch allgemein bekannt, dass die Kosten umso geringer sind, je schneller wir einen Fehler erkennen. Die Fragen, die Sie stellen sollten, lauten nicht nur, wie sicher Sie sind, dassgetUser()
niemals Null zurückgegeben wird (und niemals ein Benutzer mit einem Nullnamen zurückgegeben wird), sondern auch, welche Arten von schlechten Dingen mit dem Rest des Codes passieren, wenn er in ausgeführt wird Tatsache, eines Tages etwas Unerwartetes zurückzugeben, und wie einfach es ist, schnell auf den Rest des Codes zu schauen, um genau zu wissen, was von ihm erwartet wurdegetUser()
.Wenn das unerwartete Verhalten dazu führen würde, dass Ihre Datenbank beschädigt wird, sollten Sie dies möglicherweise auch dann bestätigen, wenn Sie zu 101% sicher sind, dass dies nicht der Fall ist. Wenn ein
null
Benutzername Tausende von Zeilen weiter unten und Milliarden von Taktzyklen später einen seltsamen, kryptischen und nicht nachvollziehbaren Fehler verursachen würde, ist es vielleicht am besten, so früh wie möglich zu versagen.Obwohl ich der Antwort von Ixrec nicht widerspreche, würde ich vorschlagen, dass Sie die Tatsache ernsthaft in Betracht ziehen, dass Behauptungen nichts kosten, sodass wirklich nichts verloren geht, nur gewonnen werden kann, indem Sie sie großzügig verwenden.
quelle
... && sureness < 1)
.Ein definitives Nein.
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 unpraktisch und möglicherweise sogar falsch, die Logik zur Überprüfung der Ergebnisse anderer Funktionen für jeden einzelnen Anrufer zu duplizieren.
Wenn irgendwelche Prüfungen durchgeführt werden sollen, sollte jede Funktion ihre eigenen Nachbedingungen prü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 überwinden müssen.
quelle
Wenn null ein gültiger Rückgabewert der getUser-Methode ist, sollte Ihr Code dies mit einem If und nicht mit einer Exception 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 Falle einer Null-Rückgabe Ausnahmen ausgelöst werden, brechen Sie die Klasse und die Methode zusammen, um die Prüfungen durchzuführen und die Ausnahme für jede Instanz von auszulösen Der Aufruf von getUser ist in Ihrem Code konsistent.
quelle
Ich würde argumentieren, dass die Prämisse Ihrer Frage nicht stimmt: Warum sollte man 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 ist inaktiv, hat einen leeren Namen und andere Werte ungleich Null, die "hier nichts" angeben. Der Hauptvorteil besteht darin, dass Sie nicht verunreinigen müssen, wenn Ihr Programm Nullprüfungen, Protokollierungen usw. durchführt. 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 Codeüberprüfung auf Null:
Hier ist ein Beispiel für Code, der ein Null-Objekt verwendet:
Was ist klarer? Ich glaube der zweite ist.
Während die Frage Java markiert ist , ist es erwähnenswert, dass das Null- Objektmuster in C ++ bei der Verwendung von Referenzen wirklich nützlich ist. Java-Referenzen ähneln eher C ++ - Zeigern und lassen null zu. C ++ Referenzen können nicht enthalten
nullptr
. Wenn man etwas analog zu einer Nullreferenz in C ++ haben möchte, ist das Nullobjektmuster der einzige Weg, den ich kenne, um dies zu erreichen.quelle
getCount()
Dokumente garantieren, dass sie jetzt nicht 0 zurückgeben, und dies wird auch in Zukunft nicht der Fall sein, es sei denn, jemand entscheidet sich für eine grundlegende Änderung und versucht, alles zu aktualisieren, was dies erfordert die neue Schnittstelle. Zu sehen, was der Code derzeit tut, ist keine Hilfe, aber wenn Sie Code überprüfen möchten, ist der zu überprüfende Code der KomponententestgetCount()
. Wenn sie testen, dass keine 0 zurückgegeben wird, wissen Sie, dass jede künftige Änderung, die 0 zurückgibt, als eine brechende Änderung angesehen wird, da die Tests fehlschlagen.Im Allgemeinen würde ich sagen, dass dies hauptsächlich von den folgenden drei Aspekten abhängt:
Robustheit: Kann die aufrufende Methode mit dem
null
Wert umgehen ? Wenn einnull
Wert zu a führen könnteRuntimeException
einer Überprüfung führen würde ich eine Überprüfung empfehlen - es sei denn, die Komplexität ist gering und aufgerufene / aufrufende Methoden werden vom selben Autor erstellt undnull
nicht erwartet (z. B. beideprivate
im selben Paket).Verantwortung für den Code: Wenn Entwickler A für den Code verantwortlich ist
getUser()
Methode verantwortlich ist und Entwickler B sie verwendet (z. B. als Teil einer Bibliothek), würde ich dringend empfehlen, ihren Wert zu validieren. Nur weil Entwickler B möglicherweise nichts über eine Änderung weiß, die zu einem potenziellennull
Rückgabewert führt.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. Auch wenn Sie sich sicher sind, kann es nicht sein
null
im Kontext der aufrufenden Methode heute sein , müssen Sie möglicherweise ÄnderungengetUser()
für einen anderen Anwendungsfall vornehmen. Wenn einige Monate oder Jahre vergangen sind und einige tausend Codezeilen hinzugefügt wurden, kann dies eine ziemliche Falle sein.Außerdem würde ich empfehlen, mögliche
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 Benutzer seingetUser()
.quelle
Das musst du definitiv nicht.
Wenn Sie beispielsweise bereits wissen, dass eine Rückgabe niemals null sein kann - warum sollten Sie eine Nullprüfung hinzufügen? Das Hinzufügen eines Null-Checks wird nichts kaputt machen, aber es ist nur überflüssig. Und besser nicht redundante Logik codieren, solange Sie dies vermeiden können.
quelle