it-swarm.com.de

Sollte man nach null suchen, wenn man nicht null erwartet?

Letzte Woche hatten wir einen heftigen Streit über den Umgang mit Nullen in der Serviceschicht unserer Anwendung. Die Frage befindet sich im .NET-Kontext, ist jedoch in Java und vielen anderen Technologien) dieselbe.

Die Frage war: Sollten Sie immer nach Nullen suchen und Ihren Code funktionieren lassen, egal was passiert, oder eine Ausnahme aufblasen lassen, wenn eine Null unerwartet empfangen wird?

Auf der einen Seite ist das Überprüfen auf Null, wo Sie es nicht erwarten (d. H. Keine Benutzeroberfläche, 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 das Überprüfen auf Nullen im Allgemeinen eine gute Angewohnheit sein. Wenn eine Überprüfung durchgeführt wird, funktioniert die Anwendung möglicherweise weiter, wobei nur ein kleiner Teil der Funktionalität keine Auswirkungen hat. Dann meldet der Kunde möglicherweise einen kleinen Fehler wie "Kommentar kann nicht gelöscht werden" anstelle eines viel schwerwiegenderen Fehlers wie "Seite X kann nicht geöffnet werden".

Welche Praxis verfolgen 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 verarbeitet (sagen wir, wir erstellen eine Sammlung). Der Entwickler, der den Code geschrieben hat, hat nicht erwartet, dass das Objekt null sein kann, 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 es natürlich eine Überprüfung geben müssen. Dann gerieten wir in einen Streit darüber, ob jedes verarbeitete Objekt überprüft werden sollte, auch wenn nicht erwartet wird, dass es fehlt, und ob die eventuelle Verarbeitung stillschweigend abgebrochen werden sollte.

Der hypothetische Vorteil wäre, dass die Seite weiter funktioniert. Stellen Sie sich Suchergebnisse in Stack Exchange in verschiedenen Gruppen vor (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 weiter funktionieren, außer dass der Abschnitt "Benutzer" fehlt (was ein Fehler ist). Sollten wir früh scheitern und die ganze Seite brechen oder weiterarbeiten und darauf warten, dass jemand bemerkt, dass der Abschnitt "Benutzer" fehlt?

119
Stilgar

Die Frage ist nicht so sehr, ob Sie nach null suchen oder die Laufzeit eine Ausnahme auslösen lassen sollten; So sollten Sie auf eine solche unerwartete Situation reagieren.

Ihre Optionen sind also:

  • Wirf eine generische Ausnahme (NullReferenceException) und lass sie aufblasen. Wenn Sie den null-Check nicht selbst durchführen, geschieht dies automatisch.
  • Lösen Sie eine benutzerdefinierte Ausnahme aus, die das Problem auf einer höheren Ebene beschreibt. Dies kann entweder durch Einwerfen des Checks null oder durch Abfangen eines NullReferenceException und Auslösen der spezifischeren Ausnahme erreicht werden.
  • Wiederherstellen durch Ersetzen eines geeigneten Standardwerts.

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

  • Die generische Ausnahme ist am besten, wenn die Fehlerbedingung ein Zeichen für einen schwerwiegenden Fehler in Ihrem Code ist, dh, der Wert null sollte niemals überhaupt dorthin gelangen dürfen. Eine solche Ausnahme kann dann bis zu der von Ihnen eingerichteten Fehlerprotokollierung aufsteigen, sodass jemand benachrichtigt wird und den Fehler behebt.
  • Die Standardwertlösung ist gut, wenn die Bereitstellung einer semi-nützlichen Ausgabe 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.
  • Andernfalls würde ich mit der spezifischen Ausnahme gehen und sie an einem geeigneten Ort behandeln.
109
tdammers

Meiner Meinung nach führt der Versuch, mit Nullwerten umzugehen, die Sie nicht erwarten, zu übermäßig kompliziertem Code. Wenn Sie keine Null erwarten, machen Sie dies deutlich, indem Sie ArgumentNullException werfen. Ich bin sehr frustriert, wenn Leute prüfen, ob der Wert null ist, und dann versuchen, Code zu schreiben, der keinen Sinn ergibt. Gleiches gilt für die Verwendung von SingleOrDefault (oder noch schlimmer für das Abrufen von Sammlungen), wenn jemand wirklich Single erwartet, und für viele andere Fälle, in denen Menschen Angst haben (ich weiß nicht wirklich, was), ihre Logik anzugeben deutlich.

60
empi

Die Gewohnheit, nach meiner Erfahrung nach null zu suchen, stammt von ehemaligen C- oder C++ - Entwicklern. In diesen Sprachen besteht eine gute Chance, dass Sie einen schwerwiegenden Fehler verbergen, wenn nicht nach NULL. Wie Sie wissen, ist es in Java oder C #) anders, wenn Sie eine Nullreferenz verwenden, ohne nach null zu suchen, wird eine Ausnahme ausgelöst, sodass der Fehler nicht heimlich ausgeblendet wird, solange Sie Ignorieren Sie es nicht auf der aufrufenden Seite. In den meisten Fällen ist es also nicht sinnvoll, explizit nach null zu suchen, und der Code wird mehr als nötig überkompliziert. Deshalb überlege ich nicht Nullprüfung ist in diesen Sprachen eine gute Angewohnheit (zumindest nicht im Allgemeinen).

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

  • sie möchten eine bessere, für Menschen lesbare Fehlermeldung, die dem Benutzer mitteilt, in welchem ​​Teil des Programms die falsche Nullreferenz aufgetreten ist. Ihr Test auf 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 fehlschlägt". Sie möchten beispielsweise nach einem Nullwert eines Konstruktorparameters suchen, wobei die Objektreferenz ansonsten nur in einer Mitgliedsvariablen gespeichert und später verwendet würde.

  • sie können sicher mit der Situation einer Nullreferenz umgehen, ohne das Risiko nachfolgender Fehler und ohne einen schwerwiegenden Fehler zu maskieren.

  • sie möchten in Ihrem aktuellen Kontext eine völlig andere Art der Fehlersignalisierung (z. B. Rückgabe eines Fehlercodes)

37
Doc Brown

Verwenden Sie behauptet, 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 damit zu tun ist.

Eine Behauptung, IMO, ist eine geeignete Prüfung, weil es ist:

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

Ich denke, selbstdokumentierender Code ist wichtig, daher ist eine Überprüfung gut. Defensive, ausfallsichere Programmierung erleichtert die Wartung, wo normalerweise die meiste Zeit verbracht wird.

pdate

Wrt Ihre Ausarbeitung. Es ist normalerweise gut, dem Endbenutzer eine Anwendung zu geben, die unter Fehlern gut funktioniert, d. H. So viel wie möglich anzeigt. Robustheit ist gut, denn der Himmel weiß nur, was in einem kritischen Moment brechen wird.

Entwickler und Tester müssen sich jedoch so schnell wie möglich über Fehler im Klaren 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 unterschiedlich angepasst werden.

15
Macke

Früh scheitern, oft scheitern. null ist einer der besten unerwarteten Werte, die Sie erhalten können, da Sie schnell ausfallen können, sobald Sie versuchen, ihn zu verwenden. Andere unerwartete Werte sind nicht so leicht zu erkennen. Da null für Sie automatisch fehlschlägt, wenn Sie versuchen, es zu verwenden, würde ich sagen, dass es keiner expliziten Prüfung bedarf.

8
DeadMG
  • 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 zeigen normalerweise, dass eine Nullreferenzprüfung erforderlich ist

  • Das Überprüfen auf Nullen impliziert keine bedingten Anweisungen. Wenn Sie befürchten, dass die Nullprüfung Ihren 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

6
CodeART

Ich würde die Frage unter dem Gesichtspunkt der Semantik betrachten, d. H. Mich fragen, was ein NULL-Zeiger 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, z.B. void foo (T x)
  • Eine Referenz, z.B. void foo (T & x).

In beiden Fällen haben Sie nicht das Problem, einen NULL-Zeiger zu überprüfen. In vielen Fällen benötigen Sie daher keine Nullzeigerprüfung überhaupt für obligatorische Argumente.

Wenn Sie ein optional Argument übergeben, können Sie einen Zeiger verwenden und den NULL-Wert verwenden, um anzugeben, 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 () einen Unterschied macht, ob x einen Wert enthält oder überhaupt keinen Wert.

Der dritte und letzte Fall ist, dass Sie ein Zeiger für ein obligatorisches Argument verwenden. In diesem Fall ist das Aufrufen von foo (x) mit x == NULL ein Fehler und Sie müssen entscheiden, wie damit umgegangen werden soll (dasselbe wie bei einem Index außerhalb der Grenzen oder einer ungültigen Eingabe). ::

  1. Keine Überprüfung: Wenn es einen Fehler gibt, lassen Sie ihn abstürzen und hoffen Sie, dass dieser Fehler früh genug auftritt (während des Tests). Wenn dies nicht der Fall ist (wenn Sie nicht früh genug fehlschlagen), 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. Wirf eine Ausnahme von foo () und lass eine andere Methode damit umgehen. Wahrscheinlich ist es am besten, dem Benutzer ein Dialogfenster anzuzeigen, das besagt, dass die Anwendung auf einen internen Fehler gestoßen ist und geschlossen wird.

Abgesehen von einer besseren Art des Fehlschlags (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, während mit Bei Annäherung 1 tritt der Fehler 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 also eine stärkere Form von "früh scheitern".

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

Wenn die Null eine ungültige Eingabe ist, haben Sie einen Fehler und ich würde ähnliche Überlegungen anwenden wie im Fall von C++ - Zeigern. Da in Java Sie Nullzeigerausnahmen abfangen können, entspricht Ansatz 1 oben 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++ würde ich immer obligatorische Argumente überprüfen, um sicherzustellen, dass eine ordnungsgemäße Ausnahme ausgelöst wird, damit das Programm sie auf robuste Weise verarbeiten kann.
  • In Java (oder C #) würde ich obligatorische Argumente prüfen, ob es Ausführungspfade gibt, die nicht geworfen werden, um eine stärkere Form von "früh fehlschlagen" zu haben. .

EDIT

Vielen Dank an Stilgar für weitere Details.

In Ihrem Fall scheint es, dass Sie als Ergebnis einer Methode null haben. Auch hier denke ich, dass Sie zuerst die Semantik Ihrer Methoden klären (und korrigieren) sollten, 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 ()? Sollte m2 () immer ein Ergebnis ungleich Null zurückgeben? Wenn dies der Fall ist, ist ein Nullergebnis ein internes Fehler (in m2 ()). Wenn Sie den Rückgabewert in m1 () überprüfen, können Sie eine robustere Fehlerbehandlung durchführen. 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 Tests und nicht nach der Bereitstellung ausgelöst wird. Frage: Wenn m2 () niemals null zurückgeben sollte, warum gibt es dann 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, d. H. 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 sie wie jeden anderen Wert überprüfen: Hier liegt kein Fehler vor, aber wahrscheinlich ist die Überprüfung, ob das Ergebnis null ist, nur ein Teil der Programmlogik.

4
Giorgio

Mein allgemeiner Ansatz ist es, niemals auf eine Fehlerbedingung zu testen, mit der Sie nicht umgehen können . Dann lautet die Frage, die in jeder einzelnen Instanz beantwortet werden muss: können Sie etwas Vernünftiges tun in Gegenwart des unerwarteten null -Werts?

Wenn Sie sicher fortfahren können, indem Sie einen anderen Wert ersetzen (z. B. eine leere Sammlung oder einen Standardwert oder eine Standardinstanz), tun Sie dies auf jeden Fall. @ Shahbaz 'Beispiel aus World of Warcraft das Ersetzen eines Bildes durch ein anderes fällt in diese Kategorie; Das Bild selbst ist wahrscheinlich nur eine Dekoration und hat keine Auswirkungen auf funktional. (In einem Debug-Build würde ich eine schreiende Farbe verwenden, um auf die Tatsache aufmerksam zu machen, dass eine Substitution stattgefunden hat. Dies hängt jedoch stark von der Art der Anwendung ab.) Protokollieren Sie jedoch Details zu dem Fehler, damit Sie wissen, dass er auftritt. Andernfalls wird ein Fehler ausgeblendet, über den Sie wahrscheinlich Bescheid wissen möchten. Es ist eine Sache, es vor dem Benutzer zu verbergen (vorausgesetzt, die Verarbeitung kann sicher fortgesetzt werden). es vor dem Entwickler zu verstecken ist etwas ganz anderes.

Wenn Sie bei Vorhandensein eines Nullwerts nicht sicher fortfahren können, schlagen Sie mit einem vernünftigen Fehler fehl. Im Allgemeinen ziehe ich es vor, so schnell wie möglich zu scheitern, wenn es offensichtlich ist, dass die Operation nicht erfolgreich sein kann, was die Überprüfung der Voraussetzungen impliziert. Es gibt Zeiten, in denen Sie nicht sofort versagen möchten, sondern den Ausfall so lange wie möglich verschieben möchten. Diese Überlegung kommt beispielsweise in Anwendungen im Zusammenhang mit Kryptografie zum Einsatz, bei denen Seitenkanalangriffe andernfalls Informationen preisgeben könnten, die Sie nicht kennen möchten. Lassen Sie den Fehler am Ende zu einem allgemeinen Fehlerbehandler aufsteigen, der wiederum relevante Details protokolliert und dem Benutzer eine Fehlermeldung "Schön (wie schön sie auch sein mögen)" anzeigt.

Es ist auch erwähnenswert, dass die richtige Reaktion zwischen Test-/QS-/Debug-Builds und Produktions-/Release-Builds sehr unterschiedlich sein kann. Bei Debug-Builds würde ich versuchen, früh und hart mit sehr detaillierten Fehlermeldungen zu scheitern, um deutlich zu machen, dass ein Problem vorliegt, 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.

4
a CVn

Wie die anderen Antworten zeigten Wenn Sie NULL nicht erwarten, machen Sie es klar, indem Sie ArgumentNullException werfen. Meiner Ansicht nach hilft wenn Sie das Projekt debuggen, die Fehler in der Logik Ihres Programms früher zu erkennen.

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

Ein weiterer Punkt ist, dass Sie entscheiden können, ob die Funktion der richtige Ort dafür ist oder nicht, wenn NULL die Parameter einer Funktion überprüft. Wenn nicht, suchen Sie alle Verweise auf die Funktion und überprüfen Sie dann, bevor Sie einen Parameter an die Funktion übergeben, mögliche Szenarien, die zu einer Nullreferenz führen können.

2
Ahmad

Sicher, NULL wird nicht erwartet , aber es gibt immer Fehler!

Ich glaube, Sie sollten nach NULL suchen, ob Sie es nicht erwarten oder nicht. Lassen Sie mich Ihnen Beispiele geben (obwohl sie nicht in Java sind).

  • In World of Warcraft wurde aufgrund eines Fehlers das Bild eines der Entwickler für viele Objekte auf einem Würfel angezeigt. Stellen Sie sich vor, wenn die Grafik-Engine nicht NULL Zeiger erwartet hätte, wäre es zu einem Absturz gekommen, während dies nicht der Fall war tödliche (sogar lustige) Erfahrung für den Benutzer. Das allerwenigste ist, dass Sie Ihre Verbindung oder einen Ihrer Speicherpunkte nicht unerwartet verloren hätten.

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

  • Stellen Sie sich ein Bankangestellterprogramm vor. Die Schnittstelle führt einige Überprüfungen durch und sendet Eingabedaten an einen zugrunde liegenden Teil, um die Geldtransfers durchzuführen. Wenn der Kernteil erwartet, dass NULL nicht empfängt, kann ein Fehler in der Schnittstelle ein Problem bei der Transaktion verursachen, möglicherweise etwas Geld in der Mitte verloren gehen (oder schlimmer noch, dupliziert).

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

Ich bin sicher, Sie haben die Idee.

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

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

2
Shahbaz

Jedes null in Ihrem System ist eine tickende Zeitbombe, die darauf wartet, entdeckt zu werden. Suchen Sie an den Grenzen, an denen Ihr Code mit Code interagiert, den Sie nicht kontrollieren, nach null und verbieten Sie null in Ihrem eigenen Code. Dies befreit Sie von dem Problem, ohne naiv auf Fremdcode zu vertrauen. Verwenden Sie stattdessen Ausnahmen oder eine Art Maybe/Nothing.

1
Doval

Während eine Nullzeigerausnahme irgendwann ausgelöst wird, wird sie oft 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 die Tatsache protokollieren, dass Sie so schnell wie möglich einen Nullzeiger eingeben.

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

0
Jon Strayer