Quelles exceptions doivent être levées pour les paramètres non valides ou inattendus dans .NET?


163

Quels types d'exceptions doivent être levées pour les paramètres non valides ou inattendus dans .NET? Quand en choisirais-je un au lieu d'un autre?

Suivre:

Quelle exception utiliseriez-vous si vous avez une fonction qui attend un entier correspondant à un mois et que vous passez en '42'? Cela tomberait-il dans la catégorie «hors de portée» même s'il ne s'agit pas d'une collection?

Réponses:


249

J'aime utiliser: ArgumentException, ArgumentNullExceptionet ArgumentOutOfRangeException.

Il existe également d'autres options qui ne se concentrent pas tellement sur l'argument lui-même, mais jugent plutôt l'appel dans son ensemble:

  • InvalidOperationException- L'argument peut être OK, mais pas dans l'état actuel de l'objet. Le crédit revient à STW (anciennement Yoooder). Votez aussi sa réponse .
  • NotSupportedException- Les arguments transmis sont valides, mais ne sont simplement pas pris en charge dans cette implémentation. Imaginez un client FTP, et vous passez une commande que le client ne prend pas en charge.

L'astuce consiste à lancer l'exception qui exprime le mieux pourquoi la méthode ne peut pas être appelée telle quelle. Idéalement, l'exception devrait être détaillée sur ce qui n'a pas fonctionné, pourquoi elle est erronée et comment y remédier.

J'adore quand les messages d'erreur indiquent de l'aide, de la documentation ou d'autres ressources. Par exemple, Microsoft a fait une bonne première étape avec ses articles de la base de connaissances, par exemple "Pourquoi est-ce que je reçois un message d'erreur" Opération abandonnée "lorsque je visite une page Web dans Internet Explorer?" . Lorsque vous rencontrez l'erreur, ils vous dirigent vers l'article de la base de connaissances dans le message d'erreur. Ce qu'ils ne font pas bien, c'est qu'ils ne vous disent pas pourquoi cela a échoué.

Merci encore à STW (ex Yoooder) pour les commentaires.


En réponse à votre suivi, je lancerais un ArgumentOutOfRangeException. Regardez ce que MSDN dit à propos de cette exception:

ArgumentOutOfRangeExceptionest levée lorsqu'une méthode est appelée et qu'au moins l'un des arguments passés à la méthode n'est pas une référence null ( Nothingdans Visual Basic) et ne contient pas de valeur valide.

Donc, dans ce cas, vous transmettez une valeur, mais ce n'est pas une valeur valide, car votre plage est comprise entre 1 et 12. Cependant, la façon dont vous le documentez indique clairement ce que votre API génère. Parce que même si je pourrais dire ArgumentOutOfRangeException, un autre développeur pourrait dire ArgumentException. Rendez-le facile et documentez le comportement.


Psst! Je conviens que vous avez répondu exactement à sa question spécifique, mais voyez ma réponse ci-dessous pour aider à compléter le codage défensif et la validation des paramètres ;-D
STW

+1 mais documenter quelle exception est lancée et pourquoi est plus important que de choisir la «bonne».
pipTheGeek

@pipTheGeek - Je pense que c'est vraiment un point discutable. Bien que la documentation soit vraiment importante, elle s'attend également à ce que le développeur consommateur soit proactif ou défensif et lise réellement la documentation en détail. J'opterais pour une erreur conviviale / descriptive plutôt qu'une bonne documentation puisque l'utilisateur final a la chance de voir l'un d'eux et pas l'autre; il y a plus de chances qu'un utilisateur final communique une erreur descriptive à un mauvais programmeur qu'un mauvais programmeur lisant la documentation complète
STW

4
Notez que si vous interceptez ArgumentException, il intercepte également ArgumentOutOfRange.
Steven Evers

Que diriez-vous FormatException: l'exception levée lorsque le format d'un argument n'est pas valide ou lorsqu'une chaîne de format composite n'est pas correctement formée.
Anthony du

44

J'ai voté pour la réponse de Josh , mais j'aimerais en ajouter une de plus à la liste:

System.InvalidOperationException doit être levée si l'argument est valide, mais que l'objet est dans un état où l'argument ne doit pas être utilisé.

Mise à jour prise à partir de MSDN:

InvalidOperationException est utilisé dans les cas où l'échec de l'appel d'une méthode est dû à des raisons autres que des arguments non valides.

Disons que votre objet a une méthode PerformAction (action enmSomeAction), les enmSomeActions valides sont Open et Close. Si vous appelez PerformAction (enmSomeAction.Open) deux fois de suite, le deuxième appel doit lancer l'exception InvalidOperationException (puisque l'arugment était valide, mais pas pour l'état actuel du contrôle)

Puisque vous faites déjà la bonne chose en programmant de manière défensive, j'ai une autre exception à mentionner: ObjectDisposedException. Si votre objet implémente IDisposable, vous devriez toujours avoir une variable de classe qui suit l'état supprimé; si votre objet a été supprimé et qu'une méthode est appelée dessus, vous devez lever l'ObjectDisposedException:

public void SomeMethod()
{
    If (m_Disposed) {
          throw new ObjectDisposedException("Object has been disposed")
     }
    // ... Normal execution code
}

Mise à jour: Pour répondre à votre suivi: C'est un peu une situation ambiguë, et est rendue un peu plus compliquée par un type de données générique (pas dans le sens .NET Generics) utilisé pour représenter un ensemble spécifique de données; un enum ou un autre objet fortement typé serait un ajustement plus idéal - mais nous n'avons pas toujours ce contrôle.

Je me pencherais personnellement vers l'exception ArgumentOutOfRangeException et fournirais un message indiquant que les valeurs valides sont de 1 à 12. Mon raisonnement est que lorsque vous parlez de mois, en supposant que toutes les représentations entières de mois sont valides, vous vous attendez à une valeur comprise entre 1 et 12. Si seuls certains mois (comme les mois qui avaient 31 jours) étaient valides, vous ne seriez pas confronté à un Range en soi et je lancerais une ArgumentException générique indiquant les valeurs valides, et je les documenterais également dans les commentaires de la méthode.


1
Bon point. Cela pourrait expliquer la différence entre une entrée invalide et non attendue. +1
Daniel Brückner

Psst, je suis d'accord avec vous n'allait tout simplement pas voler votre tonnerre. Mais depuis que vous l'avez signalé, j'ai mis à jour ma réponse
JoshBerke

38

En fonction de la valeur réelle et de l'exception qui convient le mieux:

Si ce n'est pas assez précis, dérivez simplement votre propre classe d'exceptions ArgumentException.

La réponse de Yoooder m'a éclairé. Une entrée est invalide si elle n'est pas valide à tout moment, tandis qu'une entrée est inattendue si elle n'est pas valide pour l'état actuel du système. Donc dans le dernier cas, un InvalidOperationExceptionest un choix raisonnable.


6
Extrait de la page de MSDN sur InvalidOperationException: «InvalidOperationException est utilisé dans les cas où l'échec de l'appel d'une méthode est dû à des raisons autres que des arguments non valides.»
STW


3

ArgumentException :

ArgumentException est levée lorsqu'une méthode est appelée et qu'au moins un des arguments transmis ne répond pas à la spécification de paramètre de la méthode appelée. Toutes les instances d'ArgumentException doivent porter un message d'erreur significatif décrivant l'argument non valide, ainsi que la plage de valeurs attendue pour l'argument.

Quelques sous-classes existent également pour des types spécifiques d'invalidité. Le lien contient des résumés des sous-types et quand ils doivent s'appliquer.


1

Réponse courte:
ni l'un ni l'autre

Réponse plus longue:
utiliser Argument * Exception (sauf dans une bibliothèque qui est un produit sur son on, comme une bibliothèque de composants) est une odeur. Les exceptions sont de gérer des situations exceptionnelles, pas des bogues, ni des lacunes de l'utilisateur (c'est-à-dire du consommateur d'API).

Réponse la plus longue:
lancer des exceptions pour des arguments non valides est impoli, sauf si vous écrivez une bibliothèque.
Je préfère utiliser des assertions, pour deux (ou plus) raisons:

  • Les assertions n'ont pas besoin d'être testées, contrairement aux assertions de jet, et le test avec ArgumentNullException semble ridicule (essayez-le).
  • Les assertions communiquent mieux l'utilisation prévue de l'unité et sont plus proches d'une documentation exécutable qu'une spécification de comportement de classe.
  • Vous pouvez modifier le comportement de la violation d'assertion. Par exemple, dans la compilation de débogage, une boîte de message convient, de sorte que votre contrôle qualité vous frappe immédiatement (vous obtenez également la rupture de votre IDE sur la ligne où cela se produit), tandis que dans le test unitaire, vous pouvez indiquer l'échec d'assertion comme un échec de test .

Voici à quoi ressemble la gestion des exceptions nulles (étant sarcastique, évidemment):

try {
    library.Method(null);
}
catch (ArgumentNullException e) {
    // retry with real argument this time
    library.Method(realArgument);
}

Des exceptions doivent être utilisées lorsque la situation est attendue mais exceptionnelle (des événements qui échappent au contrôle du consommateur, tels qu'une défaillance d'E / S). Argument * Exception est une indication d'un bogue et doit (à mon avis) être gérée par des tests et assistée avec Debug.

BTW: Dans ce cas particulier, vous auriez pu utiliser le type Month au lieu de int. C # n'est pas à la hauteur en ce qui concerne la sécurité des types (Aspect # rulez!) Mais parfois vous pouvez éviter (ou attraper au moment de la compilation) ces bogues tous ensemble.

Et oui, MicroSoft a tort à ce sujet.


6
À mon humble avis, des exceptions doivent également être lancées lorsque la méthode appelée ne peut raisonnablement pas se dérouler. Cela inclut le cas où l'appelant a passé de faux arguments. Que feriez-vous à la place? Renvoyer -1?
John Saunders

1
Si des arguments non valides provoquaient l'échec d'une fonction interne, quels sont les avantages et les inconvénients de tester des arguments pour la validité, par rapport à attraper une exception InvalidArgumentException à partir de la fonction interne et à l'envelopper avec une autre plus informative? Cette dernière approche semblerait améliorer les performances dans le cas courant, mais je ne l'ai pas vue faire beaucoup.
supercat

Une recherche rapide sur Google autour de cette question indique que lancer l'exception générale est la pire pratique de toutes. En ce qui concerne une affirmation de l'argument, je vois du mérite à cela sur de petits projets personnels, mais pas sur des applications d'entreprise où des arguments invalides sont très probablement dus à une mauvaise configuration ou une mauvaise compréhension de l'application.
Max

0

Il existe une ArgumentException standard que vous pouvez utiliser, ou vous pouvez sous-classer et créer la vôtre. Il existe plusieurs classes ArgumentException spécifiques:

http://msdn.microsoft.com/en-us/library/system.argumentexception(VS.71).aspx

Celui qui fonctionne le mieux.


1
Je ne suis pas d'accord pour presque tous les cas; les classes d'exception .NET Argument * fournies sont très couramment utilisées et vous permettent de fournir suffisamment d'informations spécifiques pour informer le consommateur du problème.
STW

Pour clarifier - je ne suis pas d'accord pour presque tous les cas de dérivation des classes Argument * Exception. L'utilisation de l'une des exceptions d'argument .NET plus un message descriptif et clair fournit suffisamment de détails pour plus ou moins toutes les situations où les arguments ne sont pas valides.
STW

d'accord, mais je décrivais simplement les options disponibles. J'aurais certainement dû être plus clair sur la méthode «préférée».
Scott M.
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.