Sollte man auf null prüfen, wenn er nicht null erwartet?

113

Letzte Woche hatten wir einen heftigen Streit über den Umgang mit Nullen in der Serviceschicht unserer Anwendung. Die Frage ist im .NET-Kontext, wird aber in Java und vielen anderen Technologien gleich sein.

Die Frage war: Sollten Sie immer nach Nullen suchen und sicherstellen, dass Ihr Code funktioniert, egal was passiert, oder eine Ausnahme auslösen, wenn eine Null unerwartet empfangen wird?

Auf der einen Seite ist das Prüfen auf null, wenn Sie es nicht erwarten (dh wenn Sie keine Benutzeroberfläche haben, um damit umzugehen), meiner Meinung nach dasselbe wie das Schreiben eines try-Blocks mit leerem catch. Sie verstecken nur einen Fehler. Der Fehler könnte sein, dass sich etwas im Code geändert hat und null nun ein erwarteter Wert ist, oder es liegt ein anderer Fehler vor und die falsche ID wird an die Methode übergeben.

Andererseits kann die Überprüfung auf Nullen im Allgemeinen eine gute Gewohnheit sein. Darüber hinaus funktioniert die Anwendung bei einer Überprüfung möglicherweise weiterhin, wobei nur ein kleiner Teil der Funktionalität keine Auswirkung hat. Dann könnte der Kunde einen kleinen Fehler wie "Kommentar kann nicht gelöscht werden" melden, anstatt einen viel schwerwiegenderen Fehler wie "Seite X kann nicht geöffnet werden".

Welcher Praxis folgen Sie und was sind Ihre Argumente für oder gegen einen der beiden Ansätze?

Aktualisieren:

Ich möchte einige Details zu unserem speziellen Fall hinzufügen. Wir haben einige Objekte aus der Datenbank abgerufen und sie bearbeitet (z. B. eine Sammlung erstellen). Der Entwickler, der den Code geschrieben hat, hat nicht damit gerechnet, dass das Objekt null sein könnte, sodass er keine Überprüfungen vorgenommen hat. Beim Laden der Seite ist ein Fehler aufgetreten, und die gesamte Seite wurde nicht geladen.

In diesem Fall hätte natürlich eine Überprüfung durchgeführt werden müssen. Dann haben wir uns darüber gestritten, ob jedes Objekt, das verarbeitet wird, überprüft werden soll, auch wenn nicht erwartet wird, dass es fehlt, und ob die eventuelle Verarbeitung im Hintergrund abgebrochen werden soll.

Der hypothetische Vorteil wäre, dass die Seite weiterhin funktioniert. Überlegen Sie sich Suchergebnisse zu Stack Exchange in verschiedenen Gruppen (Benutzer, Kommentare, Fragen). Die Methode könnte nach null suchen und die Verarbeitung von Benutzern abbrechen (was aufgrund eines Fehlers null ist), aber die Abschnitte "Kommentare" und "Fragen" zurückgeben. Die Seite würde weiterhin funktionieren, außer dass der Abschnitt "Benutzer" fehlen wird (was ein Fehler ist). Sollten wir früh scheitern und die ganze Seite brechen oder weiterarbeiten und darauf warten, dass jemand merkt, dass der Abschnitt "Benutzer" fehlt?

Stilgar
quelle
62
Fox Mulder Prinzip: Vertraue niemandem.
Yannis
14
@YannisRizos: Dieses Prinzip ist gut - es ist so gut, dass die Entwickler von Java und C # die Laufzeitumgebung der Sprache dies automatisch ausführen lassen. Normalerweise müssen in diesen Sprachen in Ihrem Code keine expliziten Prüfungen auf Null durchgeführt werden.
Doc Brown
4
mögliches Duplikat von Sollte eine Methode ihre Parameter validieren?
Martin York
Ich bin einverstanden mit der Antwort des Herstellers. Ich glaube, die Überprüfung auf Null ist einfach falsch, weil Sie keine vernünftige Möglichkeit haben, damit umzugehen. In Ihrem Szenario können Sie jedoch einen Abschnitt behandeln, der fehlschlägt, z. B. die Validierung eines Benutzers (oder das Abrufen von Kommentaren oder was auch immer) und das Feld "Oh nein, es ist ein Fehler aufgetreten". Ich persönlich melde alle Ausnahmen in meiner App und filtere bestimmte Ausnahmen wie 404s aus und die Verbindung wird unerwartet geschlossen
2
assert(foo != null, "foo is web control within the repeater, there's no reason to expect it to be null, etc, etc...");
zzzzBov

Antworten:

105

Die Frage ist nicht so sehr, ob Sie nach nulleiner Ausnahme suchen oder diese zur Laufzeit auslösen sollen. So sollten Sie auf eine solche unerwartete Situation reagieren.

Ihre Optionen sind dann:

  • Wirf eine generische Ausnahme ( NullReferenceException) aus und lass sie explodieren. Wenn Sie die nullPrüfung nicht selbst durchführen, geschieht dies automatisch.
  • Eine benutzerdefinierte Ausnahme auslösen, die das Problem auf einer höheren Ebene beschreibt. Dies kann entweder durch Einwerfen des nullSchecks oder durch Abfangen einer NullReferenceExceptionund Auslösen der spezifischeren Ausnahme erreicht werden.
  • Stellen Sie dies wieder her, indem Sie einen geeigneten Standardwert eingeben.

Es gibt keine allgemeine Regel, welche die beste Lösung ist. Persönlich würde ich sagen:

  • Die generische Ausnahme ist die beste, wenn die Fehlerbedingung ein Anzeichen für einen schwerwiegenden Fehler in Ihrem Code ist, d. H., Der Wert Null sollte niemals als erstes zugelassen werden. Eine solche Ausnahme kann dann in der von Ihnen eingerichteten Fehlerprotokollierung auftauchen, sodass jemand benachrichtigt wird und den Fehler behebt.
  • Die Standardwert-Lösung ist gut, wenn die Bereitstellung von halbnützlichen Ausgaben wichtiger ist als die Korrektheit, z. B. ein Webbrowser, der technisch inkorrektes HTML akzeptiert und sich nach besten Kräften bemüht, es sinnvoll wiederzugeben.
  • Ansonsten würde ich mich mit der spezifischen Ausnahme an eine geeignete Stelle begeben.
tdammers
quelle
1
@Stilgar für den Endverbraucher gibt es keinen Unterschied. Für den Entwickler ist eine bestimmte Ausnahme wie "Datenbankserver ist nicht erreichbar" intuitiver als "Nullzeiger-Ausnahme"
k3b
32
Wenn Sie hart und früh scheitern, besteht ein geringeres Risiko, dass Dinge auf subtile Weise schief gehen, z. B. eine falsche Authentifizierung, beschädigte Daten, die dauerhaft gespeichert werden, Informationslecks und andere amüsante Dinge.
Lars Viklund
1
@Stilgar: Hier gibt es zwei Probleme: unbeaufsichtigtes Verhalten und Wartbarkeit. Während für das unbeaufsichtigte Verhalten nur ein geringer Unterschied zwischen einer bestimmten Ausnahme und einer generischen besteht, kann dies aus Gründen der Wartbarkeit den Unterschied zwischen "Oh, deshalb geht alles in die Luft" und einem mehrstündigen Debugathlon ausmachen.
tdammers
3
tdammers ist genau richtig. Der "Objektverweis, der nicht auf eine Instanz eines Objekts festgelegt ist" ist eine der ärgerlichsten Ausnahmen, die ich am häufigsten sehe. Ich würde viel lieber eine bestimmte Ausnahme sehen, ob dies eine ArgumentNullException mit dem Parameternamen ist oder eine benutzerdefinierte Ausnahme "Für den Benutzer TK421 ist kein Post-Datensatz vorhanden."
Sean Chase
1
@ k3b Auch für den Endbenutzer ist "Datenbankserver nicht erreichbar" sehr hilfreich. Zumindest für Endbenutzer, die ein wenig Ahnung von häufigen Problemen haben - es kann hilfreich sein, festzustellen, dass ein Kabel locker ist, der Router neu gestartet werden muss usw., anstatt sich über fehlerhafte Software zu ärgern.
Julia Hayward
58

IMHO, das versucht, Nullwerte zu behandeln, die Sie nicht erwarten, führt zu übermäßig kompliziertem Code. Wenn Sie keine Null erwarten, machen Sie dies durch Werfen deutlich ArgumentNullException. Ich bin sehr frustriert, wenn Leute prüfen, ob der Wert null ist, und dann versuchen, Code zu schreiben, der keinen Sinn ergibt. Das Gleiche gilt für die Verwendung SingleOrDefault(oder noch schlimmer für das Abrufen von Sammlungen), wenn jemand wirklich erwartet, Singleund für viele andere Fälle, in denen die Leute Angst haben (ich weiß nicht genau, was), ihre Logik klar zu formulieren.

empi
quelle
Stattdessen ArgumentNullExceptionsollte man Codeverträge verwenden (es sei denn, es handelt sich um einen Quellcode vor 2010, der Codeverträge nicht unterstützt).
Arseni Mourzenko
Ich stimme mit Ihnen ein. Wenn die Null nicht erwartet wird, sollte sie nicht auf ungewöhnliche Weise behandelt, sondern nur gemeldet werden.
Giorgio
9
Wenn ich die Frage richtig lese, ArgumentNullExceptionzählt das "Behandeln" des Nullwerts: Es verhindert eine spätere Nullreferenzausnahme.
KutuluMike
@MichaelEdenfield: Ich denke nicht, dass dies für die gegebene Frage als echte Handhabung gilt (unser Ziel ist es nicht, dass Ihr Code funktioniert, egal was passiert ). Um ehrlich zu sein, vergesse ich oft, einen geeigneten Block zu schreiben, der ausgelöst wird, wenn null übergeben wird. Ich halte das Auslösen von NullArgumentException jedoch für die beste und meiner Meinung nach für die schlechteste Methode, Code zu schreiben, der nicht mit Null umgehen kann, sondern statt Ausnahme z. B. eine andere Null als Methodenergebnis (oder leere Zeichenfolge oder leere Auflistung oder -1) zurückgibt , oder überspringt die Ausführung der Methode, wenn der Rückgabetyp ungültig ist usw.).
Empi
2
@ Giorgio, ArgumentNullExceptionmacht es sehr klar, was das Problem ist und wie man es behebt. C # neigt nicht dazu, "undefiniert" zu verwenden. Wenn Sie etwas auf eine Art und Weise aufrufen, die nicht definiert ist, ist die Art und Weise, wie Sie es aufrufen, nicht sinnvoll. Eine Ausnahme ist der richtige Weg, dies anzuzeigen. Jetzt mache ich manchmal Parsing-Methoden, die null zurückgeben, wenn das int(zum Beispiel) nicht analysiert werden kann. Aber in diesem Fall ist ein nicht analysierbares Ergebnis eher eine legitime Möglichkeit als ein Verwendungsfehler. Siehe auch diesen Artikel .
Kyralessa
35

Die Angewohnheit, nullnach meiner Erfahrung zu suchen, stammt von früheren C- oder C ++ - Entwicklern. In diesen Sprachen haben Sie gute Chancen, einen schwerwiegenden Fehler zu verbergen, wenn Sie nicht suchen NULL. Wie Sie wissen, ist es in Java oder C # anders, wenn eine Nullreferenz ohne Prüfung auf nulleine Ausnahme verwendet wird. Daher wird der Fehler nicht geheim ausgeblendet, solange Sie ihn auf der aufrufenden Seite nicht ignorieren. In den meisten Fällen macht das explizite Überprüfen auf nullnicht viel Sinn und verkompliziert den Code mehr als nötig. Aus diesem Grund halte ich Nullprüfungen in diesen Sprachen nicht für eine gute Gewohnheit (zumindest nicht im Allgemeinen).

Natürlich gibt es Ausnahmen von dieser Regel, hier sind die, an die ich denken kann:

  • Sie möchten eine besser lesbare Fehlermeldung erhalten, die dem Benutzer mitteilt, in welchem ​​Teil des Programms die falsche Nullreferenz aufgetreten ist. Ihr Test für null löst also nur eine andere Ausnahme mit einem anderen Fehlertext aus. Offensichtlich wird kein Fehler auf diese Weise maskiert.

  • Sie möchten, dass Ihr Programm "früher scheitert". Sie möchten beispielsweise nach einem Nullwert eines Konstruktorparameters suchen, bei dem die Objektreferenz ansonsten nur in einer Mitgliedsvariablen gespeichert und später verwendet wird.

  • Sie können die Situation einer Nullreferenz sicher behandeln, ohne das Risiko nachfolgender Fehler und ohne Maskierung eines schwerwiegenden Fehlers.

  • Sie möchten eine ganz andere Art der Fehlersignalisierung (z. B. die Rückgabe eines Fehlercodes) in Ihrem aktuellen Kontext

Doc Brown
quelle
3
"Sie wollen eine bessere, für Menschen lesbare Fehlermeldung" - das ist meiner Meinung nach der beste Grund, dies zu tun. "Objektreferenz nicht auf eine Instanz eines Objekts festgelegt" oder "Nullreferenzausnahme" ist niemals so hilfreich wie "Produkt nicht instanziiert".
pdr
@pdr: Ein weiterer Grund, dies zu überprüfen, besteht darin, sicherzustellen, dass es immer erkannt wird.
Giorgio
13
"Ehemalige C-Entwickler" vielleicht. "Ehemalige C ++ - Entwickler" nur, wenn diese selbst C-Entwickler wiederherstellen. Als moderner C ++ - Entwickler wäre ich sehr misstrauisch gegenüber Code, der nackte Zeiger oder rücksichtslose dynamische Zuweisungen verwendet. Tatsächlich wäre ich versucht, das Argument umzudrehen und zu sagen, dass C ++ nicht das vom OP beschriebene Problem hat, da seine Variablen nicht die zusätzliche spezielle Eigenschaft null-ness haben, die Java und C # haben.
Kerrek SB
7
Ihr erster Absatz ist nur die Hälfte der Geschichte: Ja, wenn Sie in C # eine Nullreferenz verwenden, erhalten Sie eine Ausnahme, während Sie in C ++ undefiniertes Verhalten erhalten. Aber in gut geschriebenem C # stecken Sie mit weit mehr nullbaren Referenzen fest als in gut geschriebenem C ++.
James McNellis
Je besser die Kapselung ist, desto besser ist diese Antwort.
Radarbob
15

Verwenden Sie Asserts , um Vor- / Nachbedingungen und Invarianten für Ihren Code zu testen und anzugeben.

Es macht es so viel einfacher zu verstehen, was der Code erwartet und was zu erwarten ist.

Eine Behauptung, IMO, ist eine geeignete Prüfung, da es sich um Folgendes handelt:

  • effizient (in der Regel nicht in Release verwendet),
  • kurz (normalerweise eine einzelne Zeile) und
  • klar (Voraussetzung verletzt, dies ist ein Programmierfehler).

Ich halte es für wichtig, dass sich der Code selbst dokumentiert, daher ist eine Überprüfung gut. Defensive, ausfallsichere Programmierung erleichtert die Wartung, in der normalerweise die meiste Zeit aufgewendet wird.

Aktualisieren

Schreiben Sie Ihre Ausarbeitung. In der Regel ist es gut, dem Endbenutzer eine Anwendung zu geben, die auch bei Fehlern funktioniert, dh so viel wie möglich anzeigt. Robustheit ist gut, denn der Himmel weiß nur, was in einem kritischen Moment kaputt geht.

Entwickler und Tester müssen sich jedoch der Bugs so schnell wie möglich bewusst sein. Daher möchten Sie wahrscheinlich ein Protokollierungsframework mit einem Hook, das dem Benutzer eine Warnung anzeigt, dass ein Problem aufgetreten ist. Die Warnung sollte wahrscheinlich allen Benutzern angezeigt werden, kann jedoch je nach Laufzeitumgebung möglicherweise unterschiedlich angepasst werden.

Macke
quelle
Es ist ein großer Nachteil für mich, dass in der Veröffentlichung keine Informationen vorhanden sind
. Die
1
Fühlen Sie sich frei, eine assert ^ h ^ h ^ h-bedingte Wurf-Funktion zu verwenden, die auch in release aktiv ist, wenn Sie das brauchen. Ich habe meine eigenen ziemlich oft gerollt.
Macke,
7

Früh scheitern, oft scheitern. nullist einer der besten unerwarteten Werte, die Sie erhalten können, da Sie schnell versagen können, sobald Sie versuchen, ihn zu verwenden. Andere unerwartete Werte sind nicht so einfach zu erkennen. Da nulldies bei jedem Versuch automatisch fehlschlägt, würde ich sagen, dass keine explizite Überprüfung erforderlich ist.

DeadMG
quelle
28
Ich hoffe, Sie meinten: Früh scheitern, schnell scheitern, nicht oft ...
empi
12
Das Problem besteht darin, dass sich ein Nullwert ohne explizite Überprüfungen manchmal ziemlich weit ausbreiten kann, bevor er eine Ausnahme auslöst, und dass es dann sehr schwierig sein kann, herauszufinden, woher er stammt.
Michael Borgwardt
@MichaelBorgwardt Ist das nicht was Stack-Traces sind?
R0MANARMY
6
@ R0MANARMY: Stack-Traces helfen nicht, wenn ein Nullwert in einem Feld gespeichert wird und die NullPointerException viel später ausgelöst wird.
Michael Borgwardt
@ R0MANARMY Auch wenn Sie der Zeilennummer im Stack-Trace vertrauen konnten (in vielen Fällen ist dies nicht möglich), enthalten einige Zeilen mehrere Variablen, die möglicherweise null sind.
Ohad Schneider
6
  • Das Überprüfen auf eine Null sollte Ihre zweite Natur sein

  • Ihre API wird von anderen Personen verwendet, und ihre Erwartungen unterscheiden sich wahrscheinlich von Ihren

  • Gründliche Komponententests machen normalerweise die Notwendigkeit einer Nullreferenzprüfung deutlich

  • Das Überprüfen auf Nullen impliziert keine bedingten Anweisungen. Wenn Sie befürchten, dass die Nullprüfung den Code weniger lesbar macht, sollten Sie .NET-Codeverträge in Betracht ziehen.

  • Erwägen Sie die Installation von ReSharper (oder ähnlichem), um Ihren Code nach fehlenden Nullreferenzprüfungen zu durchsuchen

CodeART
quelle
Das Verwenden von Codeverträgen für Vorbedingungen ist einfach eine verherrlichte bedingte Aussage.
ein Lebenslauf
Ja, ich bin damit einverstanden, aber ich denke auch, dass Code bei der Verwendung von Codeverträgen sauberer aussieht, dh seine Lesbarkeit wird verbessert.
CodeART
4

Ich würde die Frage aus semantischer Sicht betrachten, dh mich fragen, was ein Nullzeiger oder ein Nullreferenzargument darstellt.

Wenn eine Funktion oder Methode foo () ein Argument x vom Typ T hat, kann x entweder obligatorisch oder optional sein .

In C ++ können Sie ein obligatorisches Argument als übergeben

  • Ein Wert, zB void foo (T x)
  • Eine Referenz, zB void foo (T & x).

In beiden Fällen haben Sie nicht das Problem, einen Nullzeiger zu überprüfen. Also, in vielen Fällen brauchen Sie nicht einen Null - Zeiger - Check überhaupt für die obligatorischen Argumente.

Wenn Sie ein optionales Argument übergeben, können Sie einen Zeiger verwenden und mit dem NULL-Wert angeben, dass kein Wert angegeben wurde:

void foo(T* x)

Eine Alternative ist die Verwendung eines intelligenten Zeigers wie

void foo(shared_ptr<T> x)

In diesem Fall möchten Sie wahrscheinlich den Zeiger im Code überprüfen, da es für die Methode foo () von Bedeutung ist, ob x einen Wert oder gar keinen Wert enthält.

Der dritte und letzte Fall ist, dass Sie einen Zeiger für ein obligatorisches Argument verwenden. In diesem Fall ist der Aufruf von foo (x) mit x == NULL ein Fehler und Sie müssen entscheiden, wie Sie damit umgehen möchten (wie bei einem Out-of-Bound-Index oder einer ungültigen Eingabe):

  1. Keine Prüfung: Wenn es einen Fehler gibt, lassen Sie ihn abstürzen und hoffen, dass dieser Fehler früh genug auftritt (während des Testens). Wenn dies nicht der Fall ist (wenn Sie nicht früh genug ausfallen), hoffen Sie, dass es nicht in einem Produktionssystem angezeigt wird, da die Benutzererfahrung darin besteht, dass die Anwendung abstürzt.
  2. Überprüfen Sie alle Argumente am Anfang der Methode und melden Sie ungültige NULL-Argumente, z. B. werfen Sie eine Ausnahme von foo () und lassen Sie eine andere Methode damit umgehen. Wahrscheinlich ist es am besten, dem Benutzer ein Dialogfenster mit dem Hinweis anzuzeigen, dass die Anwendung einen internen Fehler festgestellt hat und geschlossen wird.

Abgesehen von einer besseren Möglichkeit, Fehler zu machen (dem Benutzer mehr Informationen zu geben), besteht ein Vorteil des zweiten Ansatzes darin, dass der Fehler mit größerer Wahrscheinlichkeit gefunden wird: Das Programm schlägt jedes Mal fehl, wenn die Methode mit den falschen Argumenten aufgerufen wird, wohingegen mit Ansatz 1 Der Fehler tritt nur auf, wenn eine Anweisung ausgeführt wird, die den Zeiger dereferenziert (z. B. wenn der rechte Zweig einer if-Anweisung eingegeben wird). Ansatz 2 bietet daher eine stärkere Form des "frühzeitigen Scheiterns".

In Java haben Sie weniger Auswahlmöglichkeiten, da alle Objekte mit Referenzen übergeben werden, die immer null sein können. Auch hier ist die Überprüfung der Null (wahrscheinlich) Teil der Implementierungslogik, wenn die Null einen NONE-Wert eines optionalen Arguments darstellt.

Wenn die Null eine ungültige Eingabe ist, liegt ein Fehler vor und ich würde ähnliche Überlegungen anstellen wie im Fall von C ++ - Zeigern. Da Sie in Java Nullzeiger-Ausnahmen abfangen können, entspricht der obige Ansatz 1 dem Ansatz 2, wenn die Methode foo () alle Eingabeargumente in allen Ausführungspfaden dereferenziert (was bei kleinen Methoden wahrscheinlich sehr häufig vorkommt).

Zusammenfassend

  • Sowohl in C ++ als auch in Java ist die Überprüfung, ob optionale Argumente null sind, Teil der Programmlogik.
  • In C ++ überprüfe ich immer die obligatorischen Argumente, um sicherzustellen, dass eine ordnungsgemäße Ausnahme ausgelöst wird, damit das Programm auf robuste Weise damit umgehen kann.
  • In Java (oder C #) würde ich obligatorische Argumente prüfen , wenn es Ausführungspfade gibt, die nicht ausgelöst werden, um eine stärkere Form von "frühzeitig fehlschlagen" zu haben.

BEARBEITEN

Vielen Dank an Stilgar für weitere Details.

In Ihrem Fall scheint es, dass Sie als Ergebnis einer Methode null haben. Ich denke, Sie sollten zunächst die Semantik Ihrer Methoden klären (und korrigieren), bevor Sie eine Entscheidung treffen können.

Sie haben also die Methode m1 (), die die Methode m2 () aufruft, und m2 () gibt eine Referenz (in Java) oder einen Zeiger (in C ++) auf ein Objekt zurück.

Was ist die Semantik von m2 ()? Soll m2 () immer ein Ergebnis ungleich Null zurückgeben? In diesem Fall ist ein Null-Ergebnis ein interner Fehler (in m2 ()). Wenn Sie den Rückgabewert in m1 () überprüfen, können Sie eine robustere Fehlerbehandlung haben. Wenn Sie dies nicht tun, werden Sie wahrscheinlich früher oder später eine Ausnahme haben. Vielleicht nicht. Hoffe, dass die Ausnahme während des Testens und nicht nach der Bereitstellung ausgelöst wird. Frage : Wenn m2 () niemals null zurückgeben sollte, warum gibt es null zurück? Vielleicht sollte es stattdessen eine Ausnahme auslösen? m2 () ist wahrscheinlich fehlerhaft.

Die Alternative besteht darin, dass die Rückgabe von null Teil der Semantik der Methode m2 () ist, dh ein optionales Ergebnis hat, das in der Javadoc-Dokumentation der Methode dokumentiert werden sollte. In diesem Fall ist es in Ordnung, einen Nullwert zu haben. Die Methode m1 () sollte es wie jeden anderen Wert prüfen: Hier liegt kein Fehler vor, aber wahrscheinlich ist die Prüfung, ob das Ergebnis null ist, nur ein Teil der Programmlogik.

Giorgio
quelle
In jedem Fall war das Argument nicht null. Wir haben einen Datenbankaufruf für etwas durchgeführt, von dem erwartet wurde, dass es vorhanden ist, aber in der Praxis war es nicht vorhanden. Die Frage ist, ob wir prüfen sollen, ob jedes Objekt existiert oder ob wir nur erwarten, dass es fehlt.
Stilgar
Das von einer Methode zurückgegebene Ergebnis war also eine Referenz auf ein Objekt, und null war der Wert, der zur Darstellung des Ergebnisses verwendet wurde: NOT FOUND. Verstehe ich richtig?
Giorgio
Ich habe die Frage mit zusätzlichen Details aktualisiert.
Stilgar
3

Mein allgemeiner Ansatz ist es, niemals auf Fehlerzustände zu testen, bei denen Sie nicht wissen, wie Sie damit umgehen sollen . Dann lautet die Frage, die in jedem einzelnen Fall beantwortet werden muss: Können Sie angesichts des unerwarteten nullWerts etwas Vernünftiges tun ?

Wenn Sie sicher fortfahren können, indem Sie einen anderen Wert (z. B. eine leere Sammlung oder einen Standardwert oder eine leere Instanz) ersetzen, sollten Sie dies unbedingt tun. @ Shahbaz 'Beispiel aus World of Warcraft, ein Bild durch ein anderes zu ersetzen, fällt in diese Kategorie. Das Bild selbst ist wahrscheinlich nur eine Dekoration und hat keine funktionale Auswirkung. (Bei einem Debugbuild würde ich eine schreiende Farbe verwenden, um die Aufmerksamkeit auf die Tatsache zu lenken, dass eine Substitution stattgefunden hat, dies hängt jedoch stark von der Art der Anwendung ab.) Protokollieren Sie Details zu dem Fehler, damit Sie wissen, dass er auftritt. Andernfalls wird ein Fehler ausgeblendet, über den Sie wahrscheinlich etwas wissen möchten. Es ist eine Sache, es vor dem Benutzer zu verbergen (wiederum unter der Annahme, dass die Verarbeitung sicher fortgesetzt werden kann); es vor dem Entwickler zu verstecken ist etwas ganz anderes.

Wenn Sie bei einem Nullwert nicht sicher fortfahren können, schlagen Sie mit einem vernünftigen Fehler fehl. Im Allgemeinen versage ich lieber so schnell wie möglich, wenn es offensichtlich ist, dass die Operation nicht erfolgreich ist, was die Überprüfung der Voraussetzungen impliziert. Es gibt Zeiten, in denen Sie nicht sofort ausfallen möchten, sondern den Ausfall so lange wie möglich verschieben möchten. Diese Überlegung taucht beispielsweise in kryptografiebezogenen Anwendungen auf, in denen Seitenkanalangriffe möglicherweise Informationen preisgeben, die Sie nicht kennen möchten. Lassen Sie den Fehler am Ende zu einer allgemeinen Fehlerbehandlungsroutine aufsteigen, die relevante Details protokolliert und dem Benutzer eine nette (wie nett sie auch sein mögen) Fehlermeldung anzeigt.

Es ist auch erwähnenswert, dass die richtige Reaktion zwischen Test- / QA- / Debug-Builds und Produktions- / Release-Builds sehr unterschiedlich sein kann. Bei Debug-Builds würde ich mich bemühen, mit sehr detaillierten Fehlermeldungen frühzeitig und hart zu scheitern, um zu verdeutlichen, dass es ein Problem gibt, und einem Post-Mortem zu ermöglichen, zu bestimmen, was getan werden muss, um es zu beheben. Produktions-Builds sollten, wenn sie mit sicher behebbaren Fehlern konfrontiert werden, wahrscheinlich die Wiederherstellung begünstigen.

ein CVn
quelle
Natürlich gibt es einen allgemeinen Fehlerbehandler in der Kette. Das Problem bei Protokollen ist, dass es möglicherweise niemanden gibt, der sie sehr lange überprüft.
Stilgar
@Stilgar, wenn es niemanden gibt, der die Protokolle überprüft, ist dies kein Problem mit der Existenz oder Abwesenheit von Nullprüfungen.
ein Lebenslauf
2
Es gibt ein Problem. Die Endbenutzer bemerken möglicherweise nicht, dass das System lange Zeit nicht richtig funktioniert, wenn Nullprüfungen durchgeführt werden, durch die der Fehler einfach ausgeblendet und in die Protokolle geschrieben wird.
Stilgar
@Stilgar, das hängt ganz von der Art des Fehlers ab. Wie ich in der Post gesagt habe, sollte die unerwartete Null nur dann ersetzt / ignoriert werden , wenn Sie sicher weitermachen können . Die Verwendung eines Images anstelle eines anderen in einem Release-Build (bei Debug-Builds sollte der Fehler so früh wie möglich und so schwer wie möglich behoben werden, damit das Problem offensichtlich wird) ist eine ganz andere Sache als beispielsweise die Rückgabe ungültiger Ergebnisse für eine Berechnung. (Bearbeitete Antwort, um das
einzuschließen
1
Selbst in der Produktion würde ich schnell und früh scheitern, wenn Sie den Fehler nicht irgendwie protokollieren und melden können (ohne sich zu sehr auf geschickte Benutzer zu verlassen). Es ist eine Sache, die bereitgestellten Bugs vor Benutzern zu verbergen - sie vor sich selbst zu verstecken, ist gefährlich. Wenn Sie dem Benutzer erlauben, mit subtilen Fehlern zu leben, kann dies das Vertrauen in Ihre Anwendung beeinträchtigen. Manchmal ist es besser, die Kugel zu beißen und sie wissen zu lassen, dass es einen Fehler gab und dass Sie ihn schnell behoben haben.
Merlyn Morgan-Graham
2

Klar NULList das nicht zu erwarten , aber es gibt immer Bugs!

Ich glaube, Sie sollten überprüfen, NULLob Sie es nicht erwarten. Lassen Sie mich Ihnen Beispiele geben (obwohl sie nicht in Java sind).

  • In World of Warcraft erschien aufgrund eines Fehlers das Bild eines der Entwickler für viele Objekte auf einem Würfel. Stellen Sie sich vor, wenn die Grafik-Engine keine Zeiger erwartet NULL hätte, hätte es einen Absturz gegeben, während dies für den Benutzer zu einer nicht so tödlichen (sogar lustigen) Erfahrung wurde. Zumindest hätten Sie nicht unerwartet Ihre Verbindung oder einen Ihrer Speicherpunkte verloren.

    Dies ist ein Beispiel, in dem die Überprüfung auf NULL einen Absturz verhinderte und der Fall unbemerkt behandelt wurde.

  • Stellen Sie sich ein Programm für Bankangestellte vor. Die Schnittstelle führt einige Überprüfungen durch und sendet Eingabedaten an einen zugrunde liegenden Teil, um die Geldüberweisungen durchzuführen. Wenn der Kernteil erwartet, dass er nicht empfangen wird NULL, kann ein Fehler in der Schnittstelle ein Problem bei der Transaktion verursachen, möglicherweise geht etwas Geld in der Mitte verloren (oder schlimmer noch, es wird dupliziert).

    Mit "einem Problem" meine ich einen Absturz (wie bei einem Absturz (sagen wir, das Programm ist in C ++ geschrieben), keine Nullzeiger-Ausnahme). In diesem Fall muss die Transaktion zurückgesetzt werden, wenn NULL festgestellt wird (was natürlich nicht möglich wäre, wenn Sie die Variablen nicht mit NULL abgleichen würden!).

Ich bin mir sicher, dass Sie auf die Idee kommen.

Wenn Sie die Gültigkeit von Argumenten für Ihre Funktionen überprüfen, wird Ihr Code nicht überladen, da dies ein paar Überprüfungen am oberen Rand Ihrer Funktion darstellt (nicht in der Mitte verteilt) und die Robustheit erheblich erhöht.

Wenn Sie wählen müssen zwischen "Das Programm teilt dem Benutzer mit, dass ein Fehler aufgetreten ist, und wird ordnungsgemäß heruntergefahren oder in einen konsistenten Zustand zurückgesetzt, wodurch möglicherweise ein Fehlerprotokoll erstellt wird" und "Zugriffsverletzung", wählen Sie nicht das erste aus? Das bedeutet, dass jede Funktion darauf vorbereitet sein sollte, von einem Buggy-Code aufgerufen zu werden, anstatt zu erwarten, dass alles korrekt ist, einfach weil es immer Bugs gibt.

Shahbaz
quelle
Ihr zweites Beispiel widerlegt Ihren Standpunkt. Wenn bei einer Banktransaktion etwas schief geht, sollte die Transaktion abgebrochen werden. Genau dann, wenn Sie den Fehler abdecken, kann Geld verloren gehen oder dupliziert werden. Das Überprüfen auf Null wäre wie "Der Kunde schickt Nullgeld ... nun, ich schicke nur 10 US-Dollar (das Äquivalent zum Bild des Entwicklers)"
Stilgar
@Stilgar, das Gegenteil! Die Überprüfung auf NULL wird mit der Meldung abgebrochen. Nicht auf NULL prüfen wäre Absturz . Jetzt Absturz zu Beginn der Transaktion kann nicht schlecht sein, aber in der Mitte kann katastrophal sein. (Ich habe nicht gesagt , die Banküberweisung die Fehler wie das Spiel behandeln sollte einfach die Tatsache , dass es! Sollte damit umgehen)
Shahbaz
2
Der springende Punkt bei einer "Transaktion" ist, dass sie nicht zur Hälfte abgeschlossen werden kann :) Wenn ein Fehler auftritt, wird ein Rollback durchgeführt.
Stilgar
Dies ist ein schrecklicher Rat für alles, wo tatsächlich Transaktionssemantik erforderlich ist. Sie sollten dafür sorgen, dass alle Ihre Transaktionen atomar und idempotent sind. Andernfalls garantiert jeder Fehler, den Sie haben, verlorene oder duplizierte Ressourcen (Geld). Wenn Sie mit nicht-atomaren oder nicht-idempotenten Operationen umgehen müssen, sollten Sie sie sehr sorgfältig in Schichten einbetten, die atomares und idempotentes Verhalten gewährleisten (auch wenn Sie diese Schichten selbst schreiben müssen). Denken Sie immer daran: Was passiert, wenn ein Meteor zum Zeitpunkt der Transaktion auf den Webserver trifft?
Merlyn Morgan-Graham
1
@ MerlynMorgan-Graham, ich habe es getan. Hoffe das klärt die Verwirrung.
Shahbaz
2

Wie die anderen Antworten zeigten Wenn Sie nicht erwarten NULL, machen Sie es klar, indem Sie werfen ArgumentNullException. Meiner Ansicht nach hilft es Ihnen beim Debuggen des Projekts , die Fehler in der Logik Ihres Programms früher zu erkennen.

Sie werden also Ihre Software freigeben. Wenn Sie diese NullRefrencesÜberprüfungen wiederherstellen , verpassen Sie nichts an der Logik Ihrer Software. Sie stellen nur doppelt sicher, dass kein schwerwiegender Fehler auftritt.

Ein weiterer Punkt ist, dass Sie bei der NULLÜberprüfung der Parameter einer Funktion entscheiden können, ob die Funktion der richtige Ort ist oder nicht. Wenn nicht, suchen Sie alle Verweise auf die Funktion und übergeben Sie dann einen Parameter an die Funktion. Überprüfen Sie mögliche Szenarien, die zu einem Null-Verweis führen können.

Ahmad
quelle
1

Jeder nullin Ihrem System ist eine tickende Zeitbombe, die darauf wartet, entdeckt zu werden. Suchen Sie nullan den Grenzen, an denen Ihr Code mit Code interagiert, den Sie nicht kontrollieren, und verbieten Sie nullinnerhalb Ihres eigenen Codes. Dies befreit Sie von dem Problem, ohne naiv Fremdcode zu vertrauen. Verwenden Sie stattdessen Ausnahmen oder eine Art Maybe/ NothingTyp.

Doval
quelle
0

Während eine Nullzeigerausnahme schließlich ausgelöst wird, wird sie häufig weit von dem Punkt entfernt ausgelöst, an dem Sie den Nullzeiger erhalten haben. Tun Sie sich selbst einen Gefallen und verkürzen Sie die Zeit, die zur Behebung des Fehlers benötigt wird, indem Sie zumindest protokollieren, dass Sie so schnell wie möglich einen Nullzeiger setzen.

Selbst wenn Sie nicht wissen, was Sie jetzt tun sollen, können Sie durch Protokollieren des Ereignisses später Zeit sparen. Und vielleicht kann derjenige, der das Ticket erhält, die eingesparte Zeit nutzen, um die richtige Antwort zu finden.

Jon Strayer
quelle
-1

Da Fail early, fail fastwie von @DeadMG (@empi) angegeben nicht möglich ist, weil die Webanwendung bei Fehlern gut funktionieren muss, sollten Sie die Regel durchsetzen

keine ausnahme fangen ohne protokollierung

Sie als Entwickler sind sich potenzieller Probleme bewusst, indem Sie die Protokolle überprüfen.

Wenn Sie ein Protokollierungsframework wie log4net oder log4j verwenden, können Sie eine zusätzliche spezielle Protokollierungsausgabe (auch als Appender bezeichnet) einrichten, die Ihnen Fehler und schwerwiegende Fehler sendet. So bleiben Sie auf dem Laufenden .

[aktualisieren]

Sollte dem Endbenutzer der Seite der Fehler nicht bekannt sein, können Sie einen Appender einrichten, der Sie per E-Mail über Fehler und schwerwiegende Fehler informiert.

Wenn es in Ordnung ist, dass dem Endbenutzer der Seite kryptische Fehlermeldungen angezeigt werden, können Sie einen Appender einrichten, der das Fehlerprotokoll für die Seitenverarbeitung am Ende der Seite platziert.

Unabhängig davon, wie Sie die Protokollierung verwenden, wenn Sie die Ausnahme verschlucken, fährt das Programm mit Daten fort, die sinnvoll sind, und das Verschlucken ist nicht mehr still.

k3b
quelle
1
Die Frage ist, was passiert mit der Seite?
Stilgar
Woher wissen wir, dass die Daten sinnvoll sind und sich das System in einem konsistenten Zustand befindet? Immerhin war der Fehler unerwartet, sodass wir nicht wissen, wie er sich ausgewirkt hat.
Stilgar
"Da Fail early, fail fast, wie von @DeadMG (@empi) angegeben, nicht möglich ist, da die Webanwendung bei Fehlern gut funktionieren muss, sollten Sie die Regel durchsetzen": Man könnte immer alle Ausnahmen abfangen (catch (Throwable t)) und Weiterleitung zu einer Fehlerseite. Wäre das nicht möglich?
Giorgio
-1

Es gibt bereits gute Antworten auf diese Frage, vielleicht eine Ergänzung: Ich bevorzuge Behauptungen in meinem Code. Wenn Ihr Code nur mit gültigen Objekten, aber nicht mit Null arbeiten kann, sollten Sie den Wert Null angeben.

Aussagen in dieser Art von Situation würden dazu führen, dass alle Einheiten- und / oder Integrationstests mit der genauen Meldung fehlschlagen, sodass Sie das Problem vor der Produktion ziemlich schnell beheben können. Wenn ein solcher Fehler die Tests durchläuft und in die Produktion geht (wo Behauptungen deaktiviert werden sollten), erhalten Sie NpEx und einen schweren Fehler, aber es ist besser als ein kleinerer Fehler, der viel unscharfer ist und irgendwo anders in der Datenbank auftaucht Code, und wäre teurer zu beheben.

Wenn jemand mit Ihren Code-Behauptungen arbeiten muss, teilt er ihm außerdem Ihre Annahmen mit, die Sie beim Entwerfen / Schreiben Ihres Codes getroffen haben (in diesem Fall: Hey Mann, dieses Objekt muss präsentiert werden!), Was die Wartung erleichtert.

Bekommen
quelle
Könnten Sie bitte etwas zur Abstimmung schreiben? Ich würde mich über eine Diskussion freuen. Thanx
HolEn
Ich habe nicht abgestimmt und stimme Ihrer allgemeinen Idee zu. Die Sprache braucht allerdings etwas Arbeit. Ihre Behauptungen definieren den Vertrag Ihrer API und Sie scheitern frühzeitig / schnell, wenn diese Verträge verletzt werden. Wenn Sie dies auf der Oberfläche Ihrer Anwendung tun, werden Benutzer Ihres Codes darüber informiert, dass sie einen Fehler in ihrem Code gemacht haben. Wenn sie einen Stapel in den Aussparungen Ihres Codes finden, denken sie wahrscheinlich, dass Ihr Code fehlerhaft ist - und Sie denken dies möglicherweise auch, bis Sie einen soliden Repro erhalten und ihn debuggen.
Merlyn Morgan-Graham
-1

Normalerweise prüfe ich auf Nullen, aber ich "behandle" unerwartete Nullen, indem ich eine Ausnahme auslöse, die etwas detaillierter angibt, wo genau die Null aufgetreten ist.

Bearbeiten: Wenn Sie eine Null erhalten, wenn Sie nicht erwarten, dass etwas nicht stimmt, sollte das Programm sterben.

Loren Pechtel
quelle
-1

Dies hängt von der Sprachimplementierung der Ausnahmen ab. Mit Ausnahmen können Sie Maßnahmen ergreifen, um Datenschäden zu vermeiden oder Ressourcen sauber freizugeben, falls Ihr System in einen unerwarteten Zustand übergeht. Dies unterscheidet sich von der Fehlerbehandlung, bei der es sich um einen zu erwartenden Zustand handelt. Meine Philosophie ist, dass Sie so wenig Ausnahmebehandlung wie möglich haben sollten. Es ist Lärm. Kann Ihre Methode durch die Behandlung der Ausnahme etwas Vernünftiges bewirken? Kann es sich erholen? Kann es weitere Schäden reparieren oder verhindern? Wenn Sie nur die Ausnahme abfangen und dann von der Methode zurückkehren oder auf eine andere harmlose Weise versagen, war es wirklich sinnlos, die Ausnahme abzufangen. Sie hätten Ihrer Methode nur Rauschen und Komplexität hinzugefügt, und Sie könnten die Ursache eines Fehlers in Ihrem Entwurf verbergen. In diesem Fall würde ich den Fehler in die Luft sprudeln lassen und von einer äußeren Schleife erfasst werden. Wenn Sie beispielsweise ein Objekt reparieren oder als beschädigt markieren oder eine kritische Ressource wie eine Sperrdatei freigeben oder einen Socket sauber schließen können, sind Ausnahmen sinnvoll. Wenn Sie tatsächlich erwarten, dass der NULL-Wert häufig als gültiger Flankenfall angezeigt wird, sollten Sie dies im normalen logischen Ablauf mit if-the-else- oder switch-case-Anweisungen oder was auch immer behandeln, nicht mit einer Ausnahmebehandlung. Beispielsweise kann ein in einem Formular deaktiviertes Feld auf NULL gesetzt werden, was sich von einer leeren Zeichenfolge unterscheidet, die ein Feld darstellt, das leer gelassen wird. Dies ist ein Bereich, in dem Sie ein gutes Urteilsvermögen und gesunden Menschenverstand anwenden müssen. Ich habe noch nie von soliden Faustregeln gehört, wie man mit diesem Problem in jeder Situation umgeht. Wenn Sie beispielsweise ein Objekt reparieren oder als beschädigt markieren oder eine kritische Ressource wie eine Sperrdatei freigeben oder einen Socket sauber schließen können, sind Ausnahmen sinnvoll. Wenn Sie tatsächlich erwarten, dass der NULL-Wert häufig als gültiger Flankenfall angezeigt wird, sollten Sie dies im normalen logischen Ablauf mit if-the-else- oder switch-case-Anweisungen oder was auch immer behandeln, nicht mit einer Ausnahmebehandlung. Beispielsweise kann ein in einem Formular deaktiviertes Feld auf NULL gesetzt werden, was sich von einer leeren Zeichenfolge unterscheidet, die ein Feld darstellt, das leer gelassen wird. Dies ist ein Bereich, in dem Sie ein gutes Urteilsvermögen und gesunden Menschenverstand anwenden müssen. Ich habe noch nie von soliden Faustregeln gehört, wie man mit diesem Problem in jeder Situation umgeht. Wenn Sie beispielsweise ein Objekt reparieren oder als beschädigt markieren oder eine kritische Ressource wie eine Sperrdatei freigeben oder einen Socket sauber schließen können, sind Ausnahmen sinnvoll. Wenn Sie tatsächlich erwarten, dass NULL häufig als gültiger Randfall angezeigt wird, sollten Sie dies im normalen logischen Ablauf mit if-the-else- oder switch-case-Anweisungen oder was auch immer behandeln, nicht mit einer Ausnahmebehandlung. Beispielsweise kann ein in einem Formular deaktiviertes Feld auf NULL gesetzt werden, was sich von einer leeren Zeichenfolge unterscheidet, die ein Feld darstellt, das leer gelassen wird. Dies ist ein Bereich, in dem Sie ein gutes Urteilsvermögen und gesunden Menschenverstand anwenden müssen. Ich habe noch nie von soliden Faustregeln gehört, wie man mit diesem Problem in jeder Situation umgeht.

Java schlägt bei der Implementierung der Ausnahmebehandlung mit "überprüften Ausnahmen" fehl, bei der jede mögliche Ausnahme, die von in einer Methode verwendeten Objekten ausgelöst werden kann, in der "throws" -Klausel der Methode abgefangen oder deklariert werden muss. Das ist das Gegenteil von C ++ und Python, wo Sie Ausnahmen behandeln können, wie Sie es für richtig halten. Wenn Sie in Java den Hauptteil einer Methode so ändern, dass er einen Aufruf enthält, der möglicherweise eine Ausnahme auslöst, stehen Sie vor der Wahl, eine explizite Behandlung für eine Ausnahme hinzuzufügen, die Sie möglicherweise nicht interessieren und die Ihrem Code Rauschen und Komplexität hinzufügt, oder Sie müssen Fügen Sie diese Ausnahme zur "throws" -Klausel der zu ändernden Methode hinzu, die nicht nur Rauschen und Unordnung verursacht, sondern auch die Signatur Ihrer Methode ändert. Sie müssen dann einen Änderungscode eingeben, wo immer Ihre Methode verwendet wird, um entweder die neue Ausnahme zu behandeln, die Ihre Methode jetzt auslöst, oder die Ausnahme der "throws" -Klausel dieser Methoden hinzufügen, wodurch ein Dominoeffekt von Codeänderungen ausgelöst wird. Die "Catch or Specify Requirement" muss einer der nervigsten Aspekte von Java sein. Die Ausnahme Ausnahme für RuntimeExceptions und die offizielle Java-Rationalisierung für diesen Entwurfsfehler ist lahm.

Dieser letzte Absatz hatte wenig mit der Beantwortung Ihrer Frage zu tun. Ich hasse Java einfach.

- Noah

Noah Spurrier
quelle
Dieser Beitrag ist ziemlich schwer zu lesen (Textwand). Hätten Sie etwas dagegen bearbeiten sie in eine bessere Form ing?
gnat