Warum sagt uns "Objektreferenz nicht auf eine Instanz eines Objekts gesetzt" nicht, welches Objekt?

39

Wir führen ein System ein und manchmal kommt die berühmte Ausnahme NullReferenceExceptionbei der Meldung vor Object reference not set to an instance of an object.

In einer Methode mit fast 20 Objekten ist das Vorhandensein eines Protokolls, das besagt, dass ein Objekt null ist, überhaupt nicht von Nutzen. Es ist, als ob Sie als Sicherheitsbeamter eines Seminars sagen, dass ein Mann unter 100 Teilnehmern ein Terrorist ist. Das nützt dir wirklich gar nichts. Sie sollten mehr Informationen erhalten, wenn Sie feststellen möchten, welcher Mann der bedrohliche Mann ist.

Wenn wir den Fehler entfernen möchten, müssen wir auch wissen, welches Objekt null ist.

Nun, seit einigen Monaten ist mir etwas durch den Kopf gegangen, und das ist:

Warum gibt .NET nicht den Namen oder zumindest den Typ der Objektreferenz an, der null ist? . Kann es den Typ nicht aus der Reflexion oder einer anderen Quelle verstehen?

Was sind die Best Practices, um zu verstehen, welches Objekt null ist? Sollten wir die Nullfähigkeit von Objekten in diesen Kontexten immer manuell testen und das Ergebnis protokollieren? Gibt es einen besseren Weg?

Update: Die Ausnahme The system cannot find the file specifiedhat den gleichen Charakter. Sie können die Datei erst finden, wenn Sie sie an den Prozess anhängen und debuggen. Ich denke, diese Art von Ausnahmen kann intelligenter werden. Wäre es nicht besser, wenn .NET uns c:\temp.txt doesn't exist.anstelle dieser allgemeinen Nachricht Bescheid geben könnte ? Als Entwickler stimme ich ja.

Saeed Neamati
quelle
14
Die Ausnahme sollte einen Stack-Trace mit Zeilennummer enthalten. Ich würde meine Untersuchung von dort aus beginnen und mir jedes Objekt ansehen, auf das in dieser Zeile zugegriffen wurde.
PersonalNexus
2
Außerdem habe ich mich immer gefragt, warum der Ausnahmehilfedialog in Visual Studio den "hilfreichen" Hinweis newzum Erstellen von Instanzen einer Klasse enthält. Wann hilft so ein Hinweis wirklich?
PersonalNexus
1
Wenn Sie eine Kette von zehn Objektaufrufen haben, haben Sie ein Problem mit der Kopplung in Ihrem Entwurf.
Pete Kirkham
9
Ich finde es toll, wie jede einzelne Antwort auf diese Frage im Sinne von "Verwende einen Debugger, protokolliere deine Fehler, prüfe auf Null, es ist sowieso deine Schuld" ist, die die Frage nicht beantwortet, sondern die Schuld auf dich legt. Nur beim Stackoverflow gibt Ihnen tatsächlich jemand eine Antwort (was meiner Meinung nach zu viel Aufwand bedeutet, als dass die VM den Überblick behalten könnte). Aber die einzigen, die diese Frage richtig beantworten können, sind Microsoft-Mitarbeiter, die an dem Framework gearbeitet haben.
Rocklan

Antworten:

28

Das NullReferenceExceptionsagt Ihnen im Grunde: Sie machen es falsch. Nicht mehr, nicht weniger. Im Gegenteil, es ist kein vollwertiges Debugging-Tool. In diesem Fall würde ich sagen, dass Sie beides falsch machen, weil

  • Es gibt eine NullReferenceException
  • du hast es nicht auf eine Weise verhindert, die du weißt, warum / wo es passiert ist
  • und vielleicht auch: Eine Methode, die 20 Objekte benötigt, scheint ein bisschen abzulehnen

Ich bin ein großer Fan davon, alles zu überprüfen, bevor etwas schief geht, und dem Entwickler gute Informationen zu liefern. Kurzum: Schreiben Sie Schecks mit ArgumentNullExceptionund Likes und schreiben Sie den Namen selbst. Hier ist ein Beispiel:

void Method(string a, SomeObject b)
{
    if (a == null) throw ArgumentNullException("a");
    if (b == null) throw ArgumentNullException("b");

    // See how nice this is, and what peace of mind this provides? As long as
    // nothing modifies a or b you can use them here and be 100% sure they're not
    // null. Should they be when entering the method, at least you know which one
    // is null.
    var c = FetchSomeObject();
    if(c == null)
    {
        throw InvalidOperationException("Fetching B failed!!");
    }

    // etc.
}

Sie könnten auch in aussehen - Code Contracts , es hat es Macken , aber es funktioniert ziemlich gut und spart Tipparbeit .

stijn
quelle
17
@ SaeedNeamati du meinst nicht, dass du, weil du eine große Codebasis hast, keine anständige Fehlerprüfung machen solltest, oder? Je größer das Projekt, desto wichtiger werden die Rollen oder die Fehlerprüfung und das Berichtswesen.
15.
6
+1 für gute Ratschläge, auch wenn sie die eigentliche Frage nicht beantworten (die wahrscheinlich nur Anders Hejlsberg beantworten kann).
Ross Patterson
12
+1 für exzellente Beratung. @ SaeedNeamati sollten Sie diesen Rat hören. Ihr Problem ist auf Nachlässigkeit und mangelnde Professionalität im Code zurückzuführen. Wenn Sie keine Zeit haben, guten Code zu schreiben, haben Sie viel größere Probleme ...
MattDavey
3
Wenn Sie keine Zeit haben, guten Code zu schreiben , haben Sie definitiv keine Zeit, schlechten Code zu schreiben . Das Schreiben von schlechtem Code dauert länger als das Schreiben von gutem Code. Es sei denn, Sie interessieren sich wirklich nicht für Fehler. Und wenn Sie sich wirklich nicht für Bugs interessieren, warum schreiben Sie das Programm überhaupt?
MarkJ
5
@ SaeedNeamati I only say that checking every object to get sure that it's not null, is not a good methodErnsthaft. Es ist die beste Methode. Und nicht nur null, überprüfen Sie jedes Argument auf sinnvolle Werte. Je früher Sie Fehler abfangen, desto leichter ist die Ursache zu finden. Sie möchten nicht mehrere Ebenen im Stack-Trace zurückverfolgen müssen, um den verursachenden Aufruf zu finden.
Jgauffin
19

Es sollte wirklich genau zeigen, was Sie anrufen möchten. Es ist so, als würde man sagen: "Es gibt ein Problem. Sie müssen es beheben. Ich weiß, was es ist. Ich werde es Ihnen nicht sagen. Sie finden es heraus."

Wie nützlich wäre es zum Beispiel, wenn Sie Folgendes hätten ...

Object reference (HttpContext.Current) not set to instance of an object

...? Um in den Code einzusteigen, ihn durchzuarbeiten und herauszufinden, dass das, was Sie aufrufen nullmöchten, in Ordnung ist, geben Sie uns doch einfach eine kleine Hilfe.

Ich bin damit einverstanden, dass es normalerweise nützlich ist, den Code durchzugehen, um zur Antwort zu gelangen (da Sie wahrscheinlich mehr herausfinden werden), aber oftmals würde viel Zeit und Frustration gespart, wenn der NullReferenceExceptionText dem obigen Beispiel ähnlicher wäre.

Ich sag bloß.

LiverpoolsNumber9
quelle
Wie soll die Laufzeit wissen, was aber null ist?
Wird
3
Die Laufzeit hat mehr Informationen als sie liefert. Welche nützlichen Informationen es auch gibt, es sollte liefern. Das ist alles, was ich sage. Warum weiß es Ihre Frage nicht? Wenn Sie die Antwort darauf kennen, können Sie möglicherweise einen besseren Kommentar abgeben als Sie.
LiverpoolsNumber9
Einverstanden, und ich würde das gleiche für KeyNotFoundExceptionund viele andere Ärgernisse sagen ...
sinelaw
Im Falle einer Null-Dereferenzierung scheint dies eine Einschränkung in der Art und Weise zu sein, wie die CLR-Dereferenzen (dank Shahrooz Jefris Kommentar zur Frage des OP)
sinelaw
1
blogs.msdn.microsoft.com/dotnet/2016/08/02/… ENDLICH !!!
LiverpoolsNumber9
4

Ihr Protokoll sollte einen Stack-Trace enthalten, der normalerweise einen Hinweis darauf gibt, in welcher Zeile der Methode das Problem auftritt. Möglicherweise müssen Sie dafür sorgen, dass Ihr Release-Build PDB-Symbole enthält, damit Sie eine Vorstellung davon haben, in welcher Zeile sich der Fehler befindet.

Zugegeben, es wird dir in diesem Fall nicht helfen:

Foo.Bar.Baz.DoSomething()

Das Tell-Don't-Ask- Prinzip kann helfen, solchen Code zu vermeiden.

Ich bin mir nicht sicher, warum die Informationen nicht enthalten sind. Ich vermute, dass sie es zumindest in einem Debug-Build herausfinden könnten, wenn sie es wirklich wollten. Das Erstellen eines Absturzabbilds und das Öffnen in WinDBG kann hilfreich sein.

Paul Stovell
quelle
2

Es wurden Ausnahmen erstellt, um außergewöhnliche, nicht tödliche Zustände in der Aufrufkette zu signalisieren . Das heißt, sie sind nicht als Debugging-Tool konzipiert.

Wenn eine Nullzeiger-Ausnahme ein Debugging-Tool wäre, würde sie die Programmausführung sofort abbrechen, sodass ein Debugger eine Verbindung herstellen und direkt auf die belastende Zeile zeigen kann. Dies würde dem Programmierer alle verfügbaren Kontextinformationen geben . (Das ist ziemlich genau das, was ein Segfault aufgrund eines Nullzeigerzugriffs in C macht, wenn auch etwas grob.)

Die Nullzeigerausnahme ist jedoch als gültige Laufzeitbedingung konzipiert, die im normalen Programmablauf ausgelöst und abgefangen werden kann. Folglich müssen Leistungsüberlegungen berücksichtigt werden. Bei jeder Anpassung der Ausnahmebedingungsnachricht müssen Zeichenfolgenobjekte zur Laufzeit erstellt, verkettet und zerstört werden. Somit ist eine statische Nachricht unbestreitbar schneller.

Ich sage aber nicht, dass die Laufzeit nicht so programmiert werden konnte, dass der Name der belastenden Referenz angezeigt würde. Das könnte gemacht werden. Es würde Ausnahmen nur noch langsamer machen als sie sind. Wenn es irgendjemand interessiert, könnte eine solche Funktion sogar umschaltbar gemacht werden, damit der Produktionscode nicht verlangsamt wird, sondern das Debuggen vereinfacht wird. aber aus irgendeinem Grund scheint sich niemand genug darum gekümmert zu haben.

cmaster
quelle
1

Ich denke, Cromulent trifft den Nagel auf den Kopf, aber es gibt auch den offensichtlichen Punkt, dass NullReferenceExceptionSie nicht initialisierte Variablen haben , wenn Sie eine erhalten . Das Argument, dass Sie ungefähr 20 Objekte an eine Methode übergeben haben, kann nicht als Milderung bezeichnet werden: Als Ersteller eines Codeblocks müssen Sie für deren Ausführung verantwortlich sein, einschließlich der Konformität mit dem Rest einer Codebasis wie sowie ordnungsgemäße und korrekte Verwendung von Variablen usw.

es ist lästig, mühsam und manchmal langweilig, aber die Belohnungen am Ende sind es wert: In vielen Fällen musste ich Protokolldateien mit einem Gewicht von mehreren Gigabyte durchsuchen, und sie sind fast immer hilfreich. Bevor Sie jedoch zu dieser Phase gelangen, kann Ihnen der Debugger helfen, und vor dieser Phase erspart eine gute Planung viel Schmerz (und ich meine auch keine ausgereifte Herangehensweise an Ihre Codelösung: Einfache Skizzen und einige Notizen können und werden besser sein als nichts).

In Bezug auf den Object reference not set to an instance of an objectCode können wir keine Werte erraten, die uns gefallen könnten: Das ist unsere Aufgabe als Programmierer, und es bedeutet einfach, dass Sie eine nicht initialisierte Variable übergeben haben.

GMasucci
quelle
Ihnen ist klar, dass früher solche Begründungen für das Schreiben in Assemblersprache angeboten wurden? Und nicht mit automatischer Müllabfuhr? Und rechnen können - in hex? "lästig, mühsam und manchmal langweilig" beschreiben wir Jobs, die automatisiert werden sollten.
Spike0xff
0

Erfahren Sie, wie Sie den Debugger verwenden. Dies ist genau das, wofür es entwickelt wurde. Setzen Sie einen Haltepunkt für die betreffende Methode und los geht's.

Gehen Sie einfach Ihren Code durch und sehen Sie genau, welche Werte alle Ihre Variablen an bestimmten Punkten haben.

Edit: Ehrlich gesagt bin ich schockiert, dass noch niemand die Verwendung des Debuggers erwähnt hat.

Cromulent
quelle
2
Sie sagen also, dass mit einem Debugger alle Ausnahmen einfach reproduziert werden können?
jgauffin
@jgauffin Ich sage, dass Sie einen Debugger verwenden können, um zu sehen, warum eine Ausnahme im Code der realen Welt ausgelöst wird, anstatt synthetische Komponententests durchzuführen, die den fraglichen Code möglicherweise nicht vollständig testen, oder die Komponententests selbst können Fehler aufweisen, bei denen Ursachen auftreten sie, um Fehler im realen Code zu verpassen. Ein Debugger übertrifft fast jedes andere Tool, das mir einfällt (außer vielleicht Dinge wie Valgrind oder DTrace).
Cromulent
1
Sie sagen also, wir haben immer Zugriff auf den fehlerhaften Computer und das Debuggen ist besser als Unit-Tests?
jgauffin
@jgauffin Ich sage, wenn Sie die Wahl haben, dann ziehen Sie den Debugger anderen Tools vor. Natürlich, wenn Sie diese Wahl nicht haben, dann ist es ein bisschen ein Nichtstarter. Offensichtlich ist dies bei dieser Frage nicht der Fall, weshalb ich die Antwort gegeben habe, die ich gegeben habe. Wenn die Frage gewesen wäre, wie Sie dieses Problem auf einem Client-Computer beheben können, ohne die Möglichkeit zu haben, ein Remote-Debugging (oder lokales Debugging) durchzuführen, wäre meine Antwort anders ausgefallen. Sie scheinen zu versuchen, meine Antwort auf eine Weise zu verzerren, die für die vorliegende Frage nicht relevant ist.
Cromulent
0

Wenn Sie die Antwort von @ stijn befolgen und den Code auf Null setzen möchten, sollte dieses Code-Snippet hilfreich sein. Hier finden Sie einige Informationen zu Code-Snippets . Sobald Sie dies eingerichtet haben, geben Sie einfach ein argnull, drücken Sie zweimal die Tabulatortaste und füllen Sie dann die Lücke aus.

<CodeSnippet Format="1.0.0">
  <Header>
    <Title>EnsureArgNotNull</Title>
    <Shortcut>argnull</Shortcut>
  </Header>
  <Snippet>
    <Declarations>
      <Literal>
        <ID>argument</ID>
        <ToolTip>The name of the argument that shouldn't be null</ToolTip>
        <Default>arg</Default>
      </Literal>
    </Declarations>
    <Code Language="CSharp">
      <![CDATA[if ($argument$ == null) throw new ArgumentNullException("$argument$");$end$]]>
    </Code>
  </Snippet>
</CodeSnippet>
user2023861
quelle
hinweis: c # 6 hat jetzt den Vorteil, nameofdass Ihr Snippet throw new ArgumentNullException(nameof($argument$))keine magischen Konstanten mehr enthält, vom Compiler überprüft wird und besser mit Refactoring-Tools
funktioniert