Ich weiß also nicht, ob dies ein gutes oder ein schlechtes Code-Design ist, also dachte ich, ich frage besser.
Ich erstelle häufig Methoden für die Datenverarbeitung, die Klassen einbeziehen, und überprüfe die Methoden häufig sehr genau, um sicherzustellen, dass ich nicht vorher Nullreferenzen oder andere Fehler erhalte.
Für ein sehr einfaches Beispiel:
// fields and properties
private Entity _someEntity;
public Entity SomeEntity => _someEntity;
public void AssignEntity(Entity entity){
_someEntity = entity;
}
public void SetName(string name)
{
if (_someEntity == null) return; //check to avoid null ref
_someEntity.Name = name;
label.SetText(_someEntity.Name);
}
Wie Sie sehen können, prüft ich jedes Mal auf Null. Aber sollte die Methode diese Prüfung nicht haben?
Sollte zum Beispiel externer Code die Daten vorher bereinigen, damit die Methoden nicht wie folgt validiert werden müssen:
if(entity != null) // this makes the null checks redundant in the methods
{
Manager.AssignEntity(entity);
Manager.SetName("Test");
}
Zusammenfassend lässt sich festhalten, ob Methoden "Daten validieren" und dann ihre Verarbeitung für die Daten durchführen oder ob dies garantiert ist, bevor Sie die Methode aufrufen. Wenn Sie vor dem Aufrufen der Methode keine Validierung durchführen, sollte ein Fehler ausgegeben werden (oder der Fehler abgefangen werden Error)?
quelle
Antworten:
Das Problem mit Ihrem grundlegenden Beispiel ist nicht die Nullprüfung, sondern der stille Fehler .
Nullzeiger- / Referenzfehler sind häufig Programmiererfehler. Programmierfehler werden häufig am besten dadurch behoben, dass sie sofort und laut fehlschlagen. Sie haben drei allgemeine Möglichkeiten, um mit dem Problem in dieser Situation umzugehen:
Eine dritte Lösung ist aufwändiger, aber wesentlich robuster:
_someEntity
, sich jemals in einem ungültigen Zustand zu befinden. Eine Möglichkeit, dies zu tun, besteht darin,AssignEntity
es als Parameter für die Instanziierung zu entfernen und zu fordern. Andere Abhängigkeitsinjektionstechniken können ebenfalls nützlich sein.In einigen Situationen ist es sinnvoll, alle Funktionsargumente auf Gültigkeit zu prüfen, bevor Sie Arbeiten ausführen. In anderen Situationen ist es sinnvoll, dem Aufrufer mitzuteilen, dass er für die Gültigkeit seiner Eingaben verantwortlich ist, und nicht zu überprüfen. Auf welchem Ende des Spektrums Sie sich befinden, hängt von Ihrer Problemdomäne ab. Die dritte Option hat den entscheidenden Vorteil, dass Sie den Compiler in gewissem Umfang dazu bringen können, dass der Aufrufer alles richtig macht.
Wenn die dritte Option keine Option ist, dann ist es meiner Meinung nach wirklich egal, solange es nicht stillschweigend versagt. Wenn das Programm nicht nach null sucht, wird es sofort explodieren, aber wenn es stattdessen die Daten stillschweigend beschädigt, um später Probleme zu verursachen, ist es am besten, sie sofort zu überprüfen und zu beheben.
quelle
null
falsch oder ungültig, weil ich in meiner hypothetischen Bibliothek die Datentypen definiert habe und definiert habe, was gültig ist und was nicht. Sie nennen das "erzwingen von Annahmen", aber raten Sie mal, was jede existierende Softwarebibliothek tut. Es gibt Zeiten, in denen null ein gültiger Wert ist, aber das ist nicht "immer".null
richtig funktioniert" - Dies ist ein Missverständnis dessen, was die richtige Handhabung bedeutet. Für die meisten Java-Programmierer bedeutet dies, dass bei ungültigen Eingaben eine Ausnahme ausgelöst wird. Verwenden@ParametersAreNonnullByDefault
, bedeutet es auch für jedennull
, es sei denn, das Argument ist als markiert@Nullable
. Etwas anderes zu tun bedeutet, den Pfad zu verlängern, bis er schließlich an anderer Stelle durchbrennt, und dadurch auch die Zeit zu verlängern, die für das Debuggen verschwendet wird. Wenn Sie Probleme ignorieren, können Sie zwar erreichen, dass sie niemals durchbrennen, aber ein falsches Verhalten ist dann garantiert.Exception
sind völlig unabhängige Debatten. (Ich stimme zu, dass beide Probleme bereiten.) In diesem speziellen Beispielnull
ergibt dies offensichtlich keinen Sinn, wenn der Zweck der Klasse darin besteht, Methoden für das Objekt aufzurufen.Da
_someEntity
es jederzeit geändert werden kann, ist es sinnvoll, es jedes Mal zu testen, wennSetName
es aufgerufen wird. Schließlich könnte es sich seit dem letzten Aufruf dieser Methode geändert haben. Beachten Sie jedoch, dass der Code in einemSetName
Thread nicht threadsicher ist, sodass Sie diesen Check in einem Thread durchführen können,_someEntity
dernull
von einem anderen Thread festgelegt wurde, und der Code dannNullReferenceException
trotzdem gesperrt wird.Ein Ansatz für dieses Problem ist es daher, noch defensiver zu werden und einen der folgenden Schritte auszuführen:
_someEntity
dieser Kopie anfertigen und diese durch die Methode referenzieren,_someEntity
sicherzustellen, dass sie sich nicht ändert, wenn die Methode ausgeführt wird.try/catch
und führen Sie dieselbe Aktion für alleNullReferenceException
innerhalb der Methode auftretenden Aktionen aus , die Sie bei der anfänglichen Nullprüfung ausführen würden (in diesem Fall einenop
Aktion).Aber was Sie wirklich tun sollten, ist anzuhalten und sich die Frage zu stellen: Müssen Sie tatsächlich erlauben
_someEntity
, überschrieben zu werden? Warum setzen Sie es nicht einmal über den Konstruktor. Auf diese Weise müssen Sie immer nur einmal das Null-Häkchen setzen:Wenn möglich, ist dies der Ansatz, den ich in solchen Situationen empfehlen würde. Wenn Sie die Thread-Sicherheit nicht berücksichtigen können, wenn Sie festlegen, wie mit möglichen Nullen umgegangen werden soll.
Als Bonuskommentar empfinde ich Ihren Code als seltsam und könnte verbessert werden. Alle:
kann ersetzt werden durch:
Die gleiche Funktionalität hat, außer, dass das Feld über den Eigenschaftssetzer festgelegt werden kann, anstatt über eine Methode mit einem anderen Namen.
quelle
Das ist deine Wahl.
Indem Sie eine
public
Methode erstellen , bieten Sie der Öffentlichkeit die Möglichkeit, sie aufzurufen. Dies geht immer mit einem impliziten Vertrag darüber einher, wie diese Methode aufgerufen werden soll und was dabei zu erwarten ist. Dieser Vertrag kann beinhalten (oder auch nicht) " Ja, ähm, wenn Sie ihnnull
als Parameterwert übergeben, wird er direkt in Ihrem Gesicht explodieren. " Andere Teile dieses Vertrages könnten sein: " Übrigens: Jedes Mal, wenn zwei Threads diese Methode gleichzeitig ausführen, stirbt ein Kätzchen. "Welche Art von Vertrag Sie gestalten möchten, hängt von Ihnen ab. Die wichtigen Dinge sind zu
Erstellen Sie eine API, die nützlich und konsistent ist und
es gut zu dokumentieren, vor allem für diese Randfälle.
In welchen Fällen wird Ihre Methode wahrscheinlich aufgerufen?
quelle
Die anderen Antworten sind gut; Ich möchte sie durch einige Kommentare zu Eingabehilfen erweitern. Was ist zu tun, wenn
null
es nie gültig ist?Öffentliche und geschützte Methoden sind solche, bei denen Sie den Aufrufer nicht steuern. Sie sollten werfen, wenn sie als null übergeben werden. Auf diese Weise trainieren Sie Ihre Anrufer, Ihnen niemals eine Null zu übergeben, da sie möchten, dass ihr Programm nicht abstürzt.
interne und privaten Methoden sind diejenigen , in denen Sie haben den Anrufer steuern. Sie sollten behaupten, wenn sie als null übergeben werden. Sie werden dann wahrscheinlich später abstürzen, aber zumindest haben Sie zuerst die Behauptung und die Gelegenheit, in den Debugger einzusteigen. Auch hier möchten Sie Ihre Anrufer darin schulen, Sie korrekt anzurufen, indem Sie sie verletzen, wenn dies nicht der Fall ist.
Nun, was tun Sie , wenn es vielleicht gültig für eine Null in weitergegeben werden? Ich würde diese Situation mit dem Nullobjektmuster vermeiden . Das heißt: Erstellen Sie eine spezielle Instanz des Typs und verlangen Sie, dass sie immer dann verwendet wird, wenn ein ungültiges Objekt benötigt wird . Jetzt sind Sie zurück in der angenehmen Welt des Werfens / Durchsetzens bei jedem Gebrauch von Null.
Zum Beispiel möchten Sie nicht in dieser Situation sein:
und an der Rufstelle:
Weil (1) Sie Ihren Anrufern beibringen, dass Null ein guter Wert ist, und (2) es unklar ist, welche Semantik dieser Wert hat. Tun Sie stattdessen Folgendes:
Und jetzt sieht die Anrufseite so aus:
und jetzt weiß der Leser des Codes, was es bedeutet.
quelle
if
s für die Null-Objekte, sondern verwenden Sie Polymorphismus, damit sie sich richtig verhalten.EmptyQueue
Typ erstelle, der ausgelöst wird, wenn er sich in der Warteschlange befindet, und so weiter.Person
Klasse unveränderlich ist und keinen Konstruktor mit Nullargumenten enthält, wie würden Sie IhreApproverNotRequired
oderApproverUnknown
Instanzen konstruieren ? Mit anderen Worten, welche Werte würden Sie dem Konstruktor übergeben?In den anderen Antworten wird darauf hingewiesen, dass Ihr Code bereinigt werden kann, sodass keine Nullprüfung erforderlich ist, sofern vorhanden. Eine allgemeine Antwort auf die Frage, was eine nützliche Nullprüfung für den folgenden Beispielcode sein kann, lautet jedoch:
Dies gibt Ihnen einen Vorteil für die Wartung. Wenn in Ihrem Code jemals ein Fehler auftritt, bei dem ein Objekt null an die Methode übergeben wird, erhalten Sie von der Methode nützliche Informationen darüber, welcher Parameter null war. Ohne die Prüfungen erhalten Sie eine NullReferenceException in der Zeile:
Und (vorausgesetzt, die Something-Eigenschaft ist ein Werttyp) entweder a oder b könnten null sein, und Sie haben keine Möglichkeit zu wissen, welche aus dem Stack-Trace. Mit den Überprüfungen wissen Sie genau, welches Objekt null war, was beim Auffinden eines Fehlers von großem Nutzen sein kann.
Ich würde feststellen, dass das Muster in Ihrem ursprünglichen Code:
Kann unter bestimmten Umständen nützlich sein, kann aber auch (möglicherweise häufiger) eine sehr gute Möglichkeit bieten, zugrunde liegende Probleme in Ihrer Codebasis zu maskieren.
quelle
Nein überhaupt nicht, aber es kommt darauf an.
Sie führen eine Nullreferenzprüfung nur durch, wenn Sie darauf reagieren möchten.
Wenn Sie eine Nullreferenzprüfung durchführen und eine geprüfte Ausnahme auslösen , zwingen Sie den Benutzer der Methode, darauf zu reagieren und ihm die Wiederherstellung zu ermöglichen. Das Programm funktioniert weiterhin wie erwartet.
Wenn Sie keine Nullreferenzprüfung durchführen (oder eine ungeprüfte Ausnahme auslösen), wird möglicherweise eine ungeprüfte Prüfung ausgelöst
NullReferenceException
, die normalerweise nicht vom Benutzer der Methode verarbeitet wird und die Anwendung möglicherweise sogar beendet. Dies bedeutet, dass die Funktion offensichtlich völlig missverstanden wird und daher die Anwendung fehlerhaft ist.quelle
Etwas wie eine Erweiterung der Antwort von @ null, aber nach meiner Erfahrung überprüft null, egal was passiert.
Gute defensive Programmierung ist die Einstellung, dass Sie die aktuelle Routine isoliert codieren, unter der Annahme, dass der gesamte Rest des Programms versucht, sie zum Absturz zu bringen. Wenn Sie unternehmenskritischen Code schreiben, bei dem ein Fehlschlagen nicht in Frage kommt, ist dies so ziemlich der einzige Weg.
Nun, wie Sie tatsächlich mit einem
null
Wert umgehen , hängt stark von Ihrem Anwendungsfall ab.Wenn
null
es sich um einen akzeptablen Wert handelt, z. B. einen der zweiten bis fünften Parameter für die MSVC-Routine _splitpath (), ist die stille Verarbeitung das richtige Verhalten.Sollte
null
dies beim Aufrufen der fraglichen Routine nie der Fall sein, dh im Fall des OP muss der API-VertragAssignEntity()
mit einem gültigenEntity
Code aufgerufen worden sein , und es muss eine Ausnahme ausgelöst werden, oder es kann nicht sichergestellt werden, dass Sie dabei viel Lärm verursachen.Machen Sie sich keine Sorgen über die Kosten eines Null-Checks, sie sind spottbillig, wenn sie auftreten, und mit modernen Compilern kann und wird die statische Code-Analyse sie vollständig eliminieren, wenn der Compiler feststellt, dass dies nicht der Fall sein wird.
quelle