Comment le fait de lancer une ArgumentNullException aide-t-il?

27

Disons que j'ai une méthode:

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

J'ai été formé pour croire que lancer le ArgumentNullExceptionest "correct" mais une erreur "Référence d'objet non définie sur une instance d'un objet" signifie que j'ai un bug.

Pourquoi?

Je sais que si je mettais en cache la référence someObjectet que je l' utilisais plus tard, il est préférable de vérifier la nullité lorsqu'elle est transmise et d'échouer tôt. Cependant, si je fais un déréférencement sur la ligne suivante, pourquoi sommes-nous censés faire le contrôle? Cela va lever une exception dans un sens ou dans l'autre.

Modifier :

Cela m'est venu à l'esprit ... la peur du null déréférencé vient-elle d'un langage comme C ++ qui ne vérifie pas pour vous (c'est-à-dire qu'il essaie simplement d'exécuter une méthode à l'emplacement de mémoire zéro + décalage de méthode)?

Scott Whitlock
la source
Rendre l'exception explicite permet d'affirmer qu'il existe une possibilité d'erreur, qui peut être directement notée dans la documentation.
zzzzBov

Réponses:

34

Je suppose que si vous déréférencez immédiatement la variable, vous pouvez débattre dans les deux sens, mais je préférerais toujours l'argumentNullException.
Il est beaucoup plus explicite de ce qui se passe. L'exception contient le nom de la variable qui était null, contrairement à une exception NullReferenceException. En particulier au niveau de l'API, une ArgumentNullException indique clairement qu'un appelant n'a pas respecté le contrat d'une méthode, et l'échec n'était pas nécessairement une erreur aléatoire ou un problème plus profond (bien que cela puisse toujours être le cas). Vous devriez échouer tôt.

De plus, qu'est-ce que les mainteneurs ultérieurs sont plus susceptibles de faire? Ajouter l'argument ArgumentNullException s'ils ajoutent du code avant la première déréférence, ou simplement ajouter le code et autoriser ultérieurement une exception NullReferenceException?

Matt H
la source
Par extension, s'il s'agissait d'une méthode non publique ou d'une méthode publique sur une classe non publique, vous ne pensez pas qu'il y ait une bonne raison pour la vérification nulle?
Scott Whitlock
Je n'ajoute généralement pas ces vérifications aux méthodes privées, mais je peux toujours les ajouter aux méthodes publiques des classes privées pour être cohérent. Pour les méthodes privées, j'ai tendance à faire l'hypothèse que les arguments sont validés plus tôt à un certain niveau d'appel public.
Matt H
54

J'ai été formé pour croire que lever l'argument ArgumentNullException est "correct" mais une erreur "Référence d'objet non définie sur une instance d'un objet" signifie que j'ai un bogue. Pourquoi?

Supposons que j'appelle la méthode M(x)que vous avez écrite. Je passe nul. J'obtiens une ArgumentNullException avec le nom défini sur "x". Cette exception signifie sans équivoque que j'ai un bug; Je n'aurais pas dû passer null pour x.

Supposons que j'appelle une méthode M que vous avez écrite. Je passe nul. Je reçois une exception null deref. Comment diable suis-je censé savoir si j'ai un bogue ou si vous avez un bogue ou si une bibliothèque tierce que vous avez appelée en mon nom a un bogue? Je ne sais pas si je dois réparer mon code ou vous appeler pour vous dire de réparer le vôtre.

Les deux exceptions indiquent des bogues; ni ne devrait jamais être jeté dans le code de production. La question est de savoir quelle exception communique qui a le bogue le mieux à la personne qui effectue le débogage? L'exception nulle deref ne vous dit presque rien; L'argument null exception vous en dit long. Soyez gentil avec vos utilisateurs; lever des exceptions qui leur permettent d'apprendre de leurs erreurs.

Eric Lippert
la source
Je ne suis pas sûr de comprendre "neither should ever be thrown in production code"Quels autres types de code / situations seraient-ils utilisés? Doivent-ils être compilés comme Debug.Assert? Ou voulez-vous dire ne les jetez pas hors d'une API?
StuperUser
6
@StuperUser: Je pense que vous avez mal compris ce que je voulais dire, car je l'ai écrit de manière confuse. Vous devriez avoir throw new ArgumentNullExceptiondans votre code de production, et il ne devrait jamais s'exécuter en dehors d'un cas de test . L'exception ne doit jamais être levée car l'appelant ne doit jamais avoir ce bogue.
Eric Lippert
1
Non seulement il communique qui a le bogue, mais aussi où il se trouve. Même si les deux domaines sont les miens, il n'est pas toujours facile de comprendre exactement quelle variable était nulle.
Ohad Schneider
5

Le fait est que votre code doit faire quelque chose de significatif.

A NullReferenceExceptionn'est pas particulièrement significatif, car il pourrait signifier tout. Sans regarder où l'exception a été levée (et le code pourrait ne pas être disponible pour moi), je ne peux pas comprendre ce qui s'est mal passé.

Un ArgumentNullExceptionest significatif, car il me dit: l'opération a échoué parce que l'argument l' someObjectétait null. Je n'ai pas besoin de regarder votre code pour le comprendre.

Élever également cette exception signifie que vous gérez explicitement null, même si votre seule façon de le faire est de dire que vous ne pouvez pas le gérer, alors que laisser le code planter pourrait potentiellement signifier que vous pourriez en fait gérer null , mais j'ai oublié de mettre en œuvre le cas.

Le point d'exceptions est de communiquer des informations en cas d'échec, qui peuvent être gérées au moment de l'exécution pour récupérer de l'échec, ou au moment du débogage pour mieux comprendre ce qui s'est passé.

back2dos
la source
2

Je crois que son seul avantage est que vous pouvez fournir de meilleurs diagnostics à l'exception. C'est que vous savez, que l'appelant de la fonction passe null, alors que si vous laissez la déréférence le jeter, vous ne seriez pas sûr si l'appelant passe des données incorrectes ou s'il y a un bogue dans la fonction elle-même.

Je le ferais si quelqu'un d'autre appelait la fonction pour la rendre plus facile à utiliser (déboguer en cas de problème) et ne pas le faire dans mon code interne, car le runtime le vérifierait de toute façon.

Jan Hudec
la source
1

J'ai été formé pour croire que lever l'argument ArgumentNullException est "correct" mais une erreur "Référence d'objet non définie sur une instance d'un objet" signifie que j'ai un bogue.

Vous avez un bogue si le code ne fonctionne pas comme prévu. Un NullReferenceExceptionest jeté par le système et ce fait fait vraiment plus un bug qu'une fonctionnalité. Non seulement parce que vous n'avez pas défini l'exception spécifique à gérer. De plus, un développeur qui lit votre code peut supposer que vous n'avez pas tenu compte de la situation.

Pour des raisons similaires, l' ApplicationExceptionappartenance à votre application vs un général Exceptionqui appartient au système d'exploitation. Le type d'exception levée indique l'origine de l'exception.

Si vous avez comptabilisé null, il est préférable de le gérer avec élégance en lançant une exception spécifique ou s'il s'agit d'une entrée acceptable, alors ne lancez pas d'exception car elles sont coûteuses. Gérez-le en effectuant des opérations avec de bonnes valeurs par défaut ou aucune opération (par exemple, aucune mise à jour requise).

Enfin, ne négligez pas la capacité de l'appelant à gérer la situation. En leur donnant une exception plus spécifique, ArgumentExceptionils peuvent construire leur try / catch de manière à gérer cette erreur avant la plus générale NullReferenceExceptionqui peut être causée par un certain nombre d'autres raisons qui se produisent ailleurs, comme un échec d'initialisation d'une variable constructeur.

P.Brian.Mackey
la source
1

La vérification rend plus explicite ce qui se passe, mais le vrai problème vient avec du code comme cet exemple (artificiel):

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

Ici, une chaîne d'appels est impliquée et sans vérification nulle, vous ne voyez que l'erreur dans someOtherObject et pas là où le problème s'est révélé pour la première fois - ce qui signifie instantanément une perte de temps de débogage ou d'interprétation des piles d'appels.

En général, il vaut mieux être cohérent avec ce genre de chose et toujours ajouter la vérification null - ce qui facilite plus de repérer si une est manquante plutôt que de choisir et de choisir au cas par cas (en supposant que vous savez que null est toujours invalide).

FinnNk
la source
1

Une autre raison à considérer est que si un traitement se produit avant que l'objet nul ne soit utilisé?

Supposons que vous ayez un fichier de données d'ID d'entreprise associés (Cid) et d'ID de personne (Pid). Il est enregistré sous forme de flux de paires de nombres:

Cid Pid Cid Pid Cid Pid Cid Pid Cid Pid

Et cette fonction VB écrit les enregistrements dans le fichier:

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

Si Personest une référence nulle, la ligne File.Write(Person.ID)lèvera une exception. Le logiciel peut intercepter l'exception et récupérer, mais cela laisse un problème. Un ID d'entreprise a été écrit sans ID de personne associé. Si tel est le cas, lors de votre prochain appel à cette fonction, vous allez mettre un ID d'entreprise là où l'ID de personne précédente était attendu. En plus que cet enregistrement soit incorrect, chaque enregistrement ultérieur aura un ID de personne suivi d'un ID de société, ce qui est le mauvais sens.

Cid Pid Cid Pid Cid Cid Pid Cid Pid Cid

En validant les paramètres au début de la fonction, vous empêchez tout traitement de se produire lorsque la méthode est garantie d'échouer.

Hand-E-Food
la source