Pourquoi «la référence d'objet n'est pas définie sur une instance d'objet» ne nous dit pas quel objet?

39

Nous lançons un système et nous recevons parfois la fameuse exception NullReferenceExceptionavec le message Object reference not set to an instance of an object.

Cependant, dans une méthode où nous avons presque 20 objets, le fait d'avoir un journal indiquant qu'un objet est nul est vraiment inutile. C'est comme vous dire, lorsque vous êtes l'agent de sécurité d'un séminaire, qu'un homme sur 100 participants est un terroriste. C'est vraiment d'aucune utilité pour vous. Vous devriez obtenir plus d'informations si vous voulez détecter quel homme est l'homme menaçant.

De même, si nous voulons supprimer le bogue, nous devons savoir quel objet est null.

Maintenant, quelque chose a obsédé mon esprit pendant plusieurs mois, à savoir:

Pourquoi .NET ne nous donne-t-il pas le nom, ou du moins le type de la référence d'objet, qui est null? . Ne peut-il pas comprendre le type de réflexion ou de toute autre source?

En outre, quelles sont les meilleures pratiques pour comprendre quel objet est nul? Devrions-nous toujours tester manuellement la nullité des objets dans ces contextes et enregistrer le résultat? Y a-t-il un meilleur moyen?

Mise à jour: l'exception The system cannot find the file specifieda la même nature. Vous ne pouvez pas trouver quel fichier, tant que vous n'avez pas joint le processus et débogué. Je suppose que ces types d'exceptions peuvent devenir plus intelligents. Ne serait-il pas préférable que .NET puisse nous dire c:\temp.txt doesn't exist.au lieu de ce message général? En tant que développeur, je vote oui.

Saeed Neamati
la source
14
L'exception doit inclure une trace de pile avec le numéro de ligne. Je commencerais mon enquête à partir de là en regardant chaque objet accédé sur cette ligne.
PersonalNexus
2
De plus, je me suis toujours demandé pourquoi la boîte de dialogue du gestionnaire d'exceptions de Visual Studio inclut l'indicateur "utile" à utiliser newpour créer des instances d'une classe. Quand un tel indice aide-t-il vraiment?
PersonalNexus
1
Si vous avez une chaîne de dix appels d'objet, vous rencontrez un problème de couplage dans votre conception.
Pete Kirkham
4
9
J'adore la façon dont chaque réponse à cette question va dans le sens de "Utilisez un débogueur, enregistrez vos erreurs, vérifiez la valeur null, c'est de toute façon de votre faute" qui ne répondent pas à la question, mais mettent le blâme sur vous. Ce n'est que sur stackoverflow que quelqu'un vous donne réellement une réponse (ce qui, à mon avis, est trop lourd à supporter pour la VM). Mais en réalité, les seules personnes qui peuvent répondre correctement à cette question sont des personnes de Microsoft qui ont travaillé sur le framework.
Rocklan

Réponses:

28

Le NullReferenceExceptionfondamentalement vous dit: vous le faites mal. Ni plus ni moins. Ce n'est pas un outil de débogage à part entière, au contraire. Dans ce cas, je dirais que vous le faites mal à la fois parce que

  • il existe une exception NullReferenceException
  • vous ne l'avez pas empêché d'une manière que vous sachiez pourquoi / où cela s'est passé
  • et peut-être aussi: une méthode nécessitant 20 objets semble un peu en retrait

Je suis un grand partisan de tout vérifier avant que les choses ne commencent à tourner mal et de fournir de bonnes informations au développeur. En bref: écrivez des chèques en utilisant ArgumentNullExceptionet et aimez et écrivez le nom vous-même. Voici un exemple:

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.
}

Vous pouvez aussi regarder dans les contrats de code , ça a des bizarreries mais ça marche plutôt bien et vous évite de taper.

Stijn
la source
17
@SaeedNeamati, vous n'insinuez pas que parce que vous avez une base de code volumineuse, vous ne devez pas effectuer de vérification d'erreur décente, n'est-ce pas? De plus, plus le projet est volumineux, plus les rôles ou la vérification des erreurs et les rapports deviennent importants.
Départ le
6
+1 pour un bon conseil, même s'il ne répond pas à la question (à laquelle seul Anders Hejlsberg peut probablement répondre).
Ross Patterson
12
+1 pour un excellent conseil. @SaeedNeamati, vous devriez écouter ce conseil. Votre problème est dû à la négligence et au manque de professionnalisme dans le code. Si vous n'avez pas le temps d'écrire un bon code, vous avez des problèmes bien plus graves ...
MattDavey
3
Si vous n'avez pas le temps d'écrire du bon code, alors vous n'avez certainement pas le temps d'écrire du mauvais code. L'écriture de mauvais code prend plus de temps que l'écriture de bon code. À moins que vous ne vous souciez vraiment pas des bugs. Et si vous ne vous souciez vraiment pas des bugs, pourquoi écrire le programme?
MarkJ
5
@SaeedNeamati I only say that checking every object to get sure that it's not null, is not a good methodSérieusement. C'est la meilleure méthode. Et pas seulement null, vérifiez chaque argument pour des valeurs raisonnables. Plus tôt vous attrapez les erreurs, plus il est facile de trouver la cause. Vous ne voulez pas avoir à revenir en arrière sur plusieurs niveaux dans la trace de la pile pour trouver l'appel à l'origine.
Jgauffin
19

Cela devrait montrer exactement ce que vous essayez d'appeler. C'est comme dire "Il y a un problème. Vous devez le résoudre. Je sais ce que c'est. Je ne vais pas vous le dire. Vous allez le découvrir." Un peu comme la moitié des réponses à ce débordement de pile, ironiquement.

Alors, quelle serait l'utilité, par exemple, si vous obteniez ceci ...

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

...? Vous devez entrer dans le code, le parcourir et vous rendre compte que vous essayez d'appeler, nullc'est bien, mais pourquoi ne pas simplement nous donner un petit coup de pouce?

Je conviens qu'il est généralement utile de parcourir le code pour trouver la réponse (car vous en saurez probablement plus), mais souvent, beaucoup de temps et de frustration seraient économisés si le NullReferenceExceptiontexte ressemblait davantage à l'exemple ci-dessus.

Juste dire.

LiverpoolsNumber9
la source
Comment le moteur d'exécution est-il supposé savoir ce qui est null?
Will
3
Le moteur d'exécution a plus d'informations qu'il n'en fournit. Quelles que soient les informations utiles dont il dispose, il devrait fournir. C'est tout ce que je dis. En réponse à votre question, pourquoi ne le sait-il pas? Si vous connaissez la réponse à cette question, vous pourrez peut-être fournir un meilleur commentaire que celui que vous avez.
LiverpoolsNumber9
D'accord, et je dirais la même chose pour KeyNotFoundExceptionbeaucoup d'autres ennuis ...
Sinelaw
En fait, dans le cas d'une déréférencement nul, cela ressemble à une limitation de la déréférence des CLR (grâce au commentaire de Shahrooz Jefri sur la question du PO)
sinelaw
1
blogs.msdn.microsoft.com/dotnet/2016/08/02/… ENFIN !!!
LiverpoolsNumber9
4

Votre journal doit inclure une trace de la pile - elle vous indique généralement quelle ligne de la méthode pose problème. Vous devrez peut-être obliger votre version à inclure les symboles PDB pour avoir une idée de la ligne sur laquelle l'erreur se produit.

Certes, cela ne vous aidera pas dans ce cas:

Foo.Bar.Baz.DoSomething()

Le principe dit de ne pas demander peut aider à éviter un tel code.

En ce qui concerne la raison pour laquelle les informations ne sont pas incluses, je ne suis pas sûr - je suppose qu'au moins dans une version de débogage, s'ils le voulaient vraiment, ils pourraient le comprendre. Effectuer un vidage sur incident et une ouverture dans WinDBG peut aider.

Paul Stovell
la source
2

Des exceptions ont été créées en tant qu'outil pour signaler des conditions exceptionnelles non fatales tout au long de la chaîne d'appels. Autrement dit, ils ne sont pas conçus comme un outil de débogage.

Si une exception null-pointeur était un outil de débogage, il abandonnerait l'exécution du programme sur-le-champ, permettant à un débogueur de se connecter, en le pointant directement sur la ligne incriminée. Cela donnerait au programmeur toutes les informations de contexte disponibles. (Ce qui est à peu près ce qu'un Segfault dû à un pointeur nul, fait en C, bien qu'un peu grossièrement.)

Cependant, l'exception null-pointeur est conçue comme une condition d'exécution valide pouvant être levée et interceptée dans le flux de programme normal. Par conséquent, les considérations de performance doivent être prises en compte. Et toute personnalisation du message d'exception nécessite la création, la concaténation et la destruction des objets chaîne au moment de l'exécution. En tant que tel, un message statique est incontestablement plus rapide.

Je ne dis pas que le runtime ne pourrait pas être programmé de manière à donner le nom de la référence incriminante. Cela pourrait être fait. Cela ferait juste des exceptions encore plus lentement qu'elles ne le sont. Si quelqu'un le tenait suffisamment à cœur, une telle fonctionnalité pourrait même être commutée pour ne pas ralentir le code de production, mais permettre un débogage plus facile. mais pour une raison quelconque, personne ne semble s’être assez intéressé.

cmaster
la source
1

Cromulent a touché le clou, je pense, cependant, il y a aussi le point évident que si vous obtenez une NullReferenceExceptionvariable non initialisée. L'argument selon lequel environ 20 objets sont passés dans une méthode ne peut pas être considéré comme une atténuation: en tant que créateur d'un bloc de code, vous devez être responsable de son fonctionnement, ce qui inclut sa conformité au reste d'une base de code, comme ainsi que l'utilisation correcte et correcte des variables, etc.

c’est pénible, fastidieux et parfois ennuyeux, mais les récompenses finales en valent la peine: bien souvent, j’ai dû parcourir des fichiers journaux pesant plusieurs giga-octets et qui sont presque toujours utiles. Cependant, avant d’arriver à cette étape, le débogueur peut vous aider et, avant cette étape, une bonne planification vous épargnera beaucoup de peine (et je ne veux pas dire une approche entièrement conçue de votre solution de code: des croquis simples et quelques notes peuvent et vont vous aider. être meilleur que rien).

En ce qui concerne Object reference not set to an instance of an objectle code ne peut pas deviner les valeurs que nous pouvons aimer: c’est notre travail de programmeurs et cela signifie simplement que vous avez passé une variable non initialisée dans.

GMasucci
la source
Vous vous rendez compte que les gens offraient des justifications comme celle-ci pour écrire en langage d'assemblage? Et ne pas utiliser la collecte automatique des ordures? Et être capable de faire de l'arithmétique - en hexagone? "onéreux, fastidieux et parfois ennuyeux" est la façon dont nous décrivons les emplois qui devraient être automatisés.
Spike0xff
0

Apprenez à utiliser le débogueur. C'est exactement le genre de chose pour laquelle il est conçu. Définissez un point de rupture sur la méthode en question et c'est parti.

Parcourez simplement votre code et voyez exactement quelles sont les valeurs de toutes vos variables à certains moments.

Edit: Franchement, je suis choqué que personne d’autre n’a encore mentionné l’utilisation du débogueur.

Cromulent
la source
2
Vous dites donc que toutes les exceptions peuvent être facilement reproduites lors de l’utilisation d’un débogueur?
Jgauffin
@ jgauffin Je dis que vous pouvez utiliser un débogueur pour voir pourquoi une exception est générée dans le code du monde réel plutôt que dans des tests unitaires synthétiques qui pourraient ne pas réellement tester complètement le code en question, ou les tests unitaires eux-mêmes peuvent comporter des bogues dont les causes qu'ils manquent des bugs dans le code réel. Un débogueur remplace presque tout autre outil auquel je peux penser (sauf peut-être des choses comme Valgrind ou DTrace).
Cromulent
1
Vous dites donc que nous avons toujours accès à l'ordinateur défaillant et que le débogage est préférable aux tests unitaires?
Jgauffin
@ jgauffin Je dis que si vous avez le choix, privilégiez le débogueur plutôt que d'autres outils. Bien sûr, si vous n'avez pas ce choix, c'est un peu un non-démarreur. Clairement, ce n'est pas le cas avec cette question, c'est pourquoi j'ai donné la réponse que j'ai donnée. Si la question était de savoir comment résoudre ce problème sur un ordinateur client sans aucun moyen de procéder à un débogage à distance (ou à un débogage local), ma réponse aurait été différente. Vous semblez essayer de fausser ma réponse d’une manière qui n’a aucun rapport avec la question à l’examen.
Cromulent
0

Si vous voulez aller avec la réponse de @ stijn et mettre des contrôles nuls dans votre code, cet extrait de code devrait vous aider. Voici quelques informations sur les extraits de code . Une fois cette configuration configurée, il vous suffit de taper argnull, d'appuyer deux fois sur la touche tabulation, puis de remplir le vide.

<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>
utilisateur2023861
la source
remarque: c # 6 a maintenant nameofvotre extrait throw new ArgumentNullException(nameof($argument$))qui présente les avantages de ne pas inclure les constantes magiques, d'être vérifié par le compilateur, et de mieux fonctionner avec les outils de refactoring
lundi