Wie hilft das Auslösen einer ArgumentNullException?


27

Angenommen, ich habe eine Methode:

public void DoSomething(ISomeInterface someObject)
{
    if(someObject == null) throw new ArgumentNullException("someObject");
    someObject.DoThisOrThat();
}

Ich bin darauf trainiert zu glauben, dass das Werfen von ArgumentNullException"richtig" ist, aber ein Fehler "Objektreferenz nicht auf eine Instanz eines Objekts festgelegt" bedeutet, dass ich einen Fehler habe.

Warum?

Ich weiß, dass, wenn ich den Verweis zwischengespeichert habe someObjectund später verwende, es besser ist, bei der Übergabe auf Ungültigkeit zu prüfen und früh zu scheitern. Wenn ich es jedoch in der nächsten Zeile dereferenziere, warum sollten wir dann die Überprüfung durchführen? Es wird auf die eine oder andere Weise eine Ausnahme auslösen.

Bearbeiten :

Mir ist gerade in den Sinn gekommen ... Kommt die Angst vor dereferenzierten Null aus einer Sprache wie C ++, die nicht auf Sie prüft (dh es wird nur versucht, eine Methode am Speicherort Null + Methodenoffset auszuführen)?


Indem die Ausnahme explizit angegeben wird, wird bestätigt, dass die Möglichkeit eines Fehlers besteht, der direkt in der Dokumentation vermerkt werden kann.
zzzzBov

Antworten:


34

Ich nehme an, wenn Sie die Variable sofort dereferenzieren, könnten Sie so oder so diskutieren, aber ich würde immer noch die ArgumentNullException vorziehen.
Es ist viel expliziter darüber, was los ist. Die Ausnahme enthält den Namen der Variablen, die null war, während eine NullReferenceException dies nicht tut. Insbesondere auf API-Ebene macht eine ArgumentNullException deutlich, dass ein Aufrufer den Vertrag einer Methode nicht eingehalten hat und der Fehler nicht unbedingt ein zufälliger Fehler oder ein tieferes Problem war (obwohl dies möglicherweise immer noch der Fall ist). Sie sollten früh scheitern.

Was tun spätere Betreuer außerdem mit größerer Wahrscheinlichkeit? Fügen Sie die ArgumentNullException hinzu, wenn sie vor der ersten Dereferenzierung Code hinzufügen, oder fügen Sie einfach den Code hinzu und lassen Sie später zu, dass eine NullReferenceException ausgelöst wird.


Wenn dies eine nicht öffentliche Methode oder eine öffentliche Methode für eine nicht öffentliche Klasse wäre, gibt es Ihrer Meinung nach keinen guten Grund für die Nullprüfung?
Scott Whitlock

Normalerweise füge ich diese Prüfungen nicht zu privaten Methoden hinzu, aber ich kann sie trotzdem zu öffentlichen Methoden privater Klassen hinzufügen, um konsistent zu sein. Bei privaten Methoden gehe ich eher davon aus, dass Argumente auf einer öffentlichen Aufrufebene früher validiert werden.
Matt H

54

Ich habe gelernt zu glauben, dass das Auslösen der ArgumentNullException "korrekt" ist, aber ein Fehler "Objektreferenz nicht auf eine Instanz eines Objekts gesetzt" bedeutet, dass ich einen Fehler habe. Warum?

Angenommen, ich rufe M(x)die von Ihnen geschriebene Methode auf . Ich übergebe null. Ich erhalte eine ArgumentNullException mit dem Namen "x". Diese Ausnahme bedeutet eindeutig, dass ich einen Fehler habe; Ich hätte für x nicht null übergeben sollen.

Angenommen, ich rufe eine Methode M auf, die Sie geschrieben haben. Ich übergebe null. Ich erhalte eine Null-Deref-Ausnahme. Wie zum Teufel soll ich wissen, ob ich einen Fehler habe oder ob Sie einen Fehler haben oder ob eine Bibliothek eines Drittanbieters, die Sie in meinem Namen aufgerufen haben, einen Fehler hat? Ich weiß nicht, ob ich meinen Code reparieren oder Sie anrufen muss, um Ihnen mitzuteilen, dass Sie Ihren reparieren sollen.

Beide Ausnahmen weisen auf Fehler hin. Weder sollte jemals in Produktionscode geworfen werden. Die Frage ist, welche Ausnahme der Person, die das Debuggen durchführt, mitteilt, wer den Fehler besser hat . Die Null-Deref-Ausnahme sagt Ihnen fast nichts. Argument Null Ausnahme sagt Ihnen viel. Seien Sie freundlich zu Ihren Nutzern; wirf Ausnahmen aus, die es ihnen ermöglichen, aus ihren Fehlern zu lernen.


Ich bin mir nicht sicher, ob ich das verstehe. "neither should ever be thrown in production code"Welche anderen Arten von Code / Situationen würden sie verwenden? Sollen sie so zusammengestellt werden Debug.Assert? Oder meinst du sie nicht aus einer API geworfen?
StuperUser

6
@StuperUser: Ich denke du hast falsch verstanden, was ich meinte, weil ich es auf verwirrende Weise geschrieben habe. Sie sollten haben throw new ArgumentNullExceptionin Ihrem Produktionscode, und es sollte nie außerhalb eines Testfall ausgeführt . Die Ausnahme sollte eigentlich niemals geworfen werden, da der Aufrufer niemals diesen Fehler haben sollte.
Eric Lippert

1
Es kommuniziert nicht nur, wer den Fehler hat, sondern auch, wo sich der Fehler befindet. Selbst wenn beide Bereiche mir gehören, ist es nicht immer leicht zu verstehen, welche Variable null war.
Ohad Schneider

5

Der Punkt ist, Ihr Code sollte etwas Sinnvolles tun.

A NullReferenceExceptionist nicht besonders aussagekräftig, weil es über alles bedeuten könnte. Ohne nachzusehen, wo die Ausnahme ausgelöst wurde (und der Code mir möglicherweise nicht zur Verfügung steht), kann ich nicht herausfinden, was schief gelaufen ist.

Ein ArgumentNullExceptionist sinnvoll, weil es mir sagt: Die Operation ist fehlgeschlagen, weil das Argument someObjectwar null. Ich muss nicht auf Ihren Code schauen, um das herauszufinden.

Das Auslösen dieser Ausnahme bedeutet auch, dass Sie explizit behandeln null, obwohl Ihre einzige Möglichkeit darin besteht, zu sagen, dass Sie nicht damit umgehen können. Wenn Sie jedoch den Code zum Absturz bringen, kann dies möglicherweise dazu führen, dass Sie tatsächlich in der Lage sind, mit null umzugehen , aber vergessen, den Fall umzusetzen.

Ausnahmen bilden die Übermittlung von Informationen im Fehlerfall, die zur Laufzeit verarbeitet werden können, um einen Fehler zu beheben, oder zur Debug-Zeit, um besser zu verstehen, was schief gelaufen ist.


2

Ich glaube, es ist nur von Vorteil, dass Sie in Ausnahmefällen eine bessere Diagnose liefern können. Das heißt, Sie wissen, dass der Aufrufer der Funktion null übergibt, während Sie, wenn Sie die Dereferenzierung auslösen lassen, nicht sicher sind, ob der Aufrufer falsche Daten übergibt oder ob ein Fehler in der Funktion selbst vorliegt.

Ich würde es tun, wenn jemand anderes die Funktion aufruft, um die Verwendung zu vereinfachen (Debuggen, wenn etwas schief geht), und nicht in meinem internen Code, da die Laufzeitumgebung es trotzdem überprüft.


1

Ich habe gelernt zu glauben, dass das Auslösen der ArgumentNullException "korrekt" ist, aber ein Fehler "Objektreferenz nicht auf eine Instanz eines Objekts gesetzt" bedeutet, dass ich einen Fehler habe.

Sie haben einen Fehler, wenn der Code nicht wie vorgesehen funktioniert. Ein NullReferenceExceptionwird vom System geworfen und diese Tatsache macht mehr aus einem Fehler als aus einer Funktion. Nicht nur, weil Sie nicht die spezifische Ausnahme definiert haben, die behandelt werden soll. Auch weil ein Entwickler, der Ihren Code liest, davon ausgehen kann, dass Sie die aufgetretene Situation nicht berücksichtigt haben.

Aus ähnlichen Gründen ApplicationExceptiongehört das zu Ihrer Anwendung und ein allgemeines Exceptionzum Betriebssystem. Der Typ der ausgelösten Ausnahme gibt an, woher die Ausnahme stammt.

Wenn Sie für null gerechnet haben, ist es besser, wenn Sie es ordnungsgemäß handhaben, indem Sie eine bestimmte Ausnahme auslösen, oder wenn es sich um eine akzeptable Eingabe handelt, dann werfen Sie keine Ausnahme, weil sie teuer ist. Behandeln Sie das Problem, indem Sie Vorgänge mit guten Standardeinstellungen oder ohne Vorgänge ausführen (z. B. kein Update erforderlich).

Vernachlässigen Sie nicht die Fähigkeit des Anrufers, mit der Situation umzugehen. Indem sie ihnen eine spezifischere Ausnahme geben, können ArgumentExceptionsie ihren Versuch / Fang so konstruieren, dass er diesen Fehler behandelt, bevor er allgemeiner wird. Dies NullReferenceExceptionkann durch eine Reihe anderer Gründe verursacht werden, die an anderer Stelle auftreten, z. B. ein Fehler beim Initialisieren einer Konstruktorvariablen.


1

Die Prüfung macht es klarer, was los ist, aber das eigentliche Problem ergibt sich aus folgendem (erfundenen) Code:

public void DoSomething(Foo someOtherObject, ISomeInterface someObject)
{
    someOtherObject.DoThisOrThat(this, someObject);
}

Hier handelt es sich um eine Aufrufkette, bei der ohne Nullprüfung nur der Fehler in someOtherObject angezeigt wird und nicht, wo das Problem zum ersten Mal aufgetreten ist. Dies bedeutet, dass sofort Zeit für das Debuggen oder Interpretieren von Aufrufstapeln verschwendet wird.

Im Allgemeinen ist es besser, mit dieser Art von Dingen im Einklang zu sein und immer den Null-Check hinzuzufügen - was es einfacher macht, zu erkennen, wenn einer fehlt, anstatt von Fall zu Fall auszuwählen (vorausgesetzt, Sie wissen, dass Null ist) immer ungültig).


1

Ein weiterer zu berücksichtigender Grund ist, was zu tun ist, wenn eine Verarbeitung erfolgt, bevor das Nullobjekt verwendet wird?

Angenommen, Sie haben eine Datendatei mit zugeordneten Firmen-IDs (Cid) und Personen-IDs (Pid). Es wird als Stream von Zahlenpaaren gespeichert:

Cid Pid Cid Pid Cid Pid Cid Pid Cid Pid

Und diese VB-Funktion schreibt die Datensätze in die Datei:

Sub WriteRecord(Company As CompanyInfo, Person As PersonInfo)
    Using File As New StringWriter(DataFileName)
        File.Write(Company.ID)
        File.Write(Person.ID)
    End Using
End Sub

Wenn Persones sich um eine Nullreferenz handelt, löst die Zeile File.Write(Person.ID)eine Ausnahme aus. Die Software kann die Ausnahme abfangen und wiederherstellen, dies hinterlässt jedoch ein Problem. Eine Firmen-ID wurde ohne zugehörige Personen-ID geschrieben. Wenn Sie diese Funktion das nächste Mal aufrufen, geben Sie eine Firmen-ID ein, unter der die vorherige Personen-ID erwartet wurde. Jeder nachfolgende Datensatz enthält eine Personen-ID, gefolgt von einer Firmen-ID. Dies ist der falsche Weg.

Cid Pid Cid Pid Cid Pid Cid Pid Cid

Indem Sie die Parameter zu Beginn der Funktion validieren, verhindern Sie, dass eine Verarbeitung stattfindet, bei der ein Fehlschlagen der Methode garantiert ist.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.