Pourquoi ne pas lever ces exceptions?

111

Je suis tombé sur cette page MSDN qui déclare:

Ne lancez pas Exception , SystemException , NullReferenceException ou IndexOutOfRangeException intentionnellement à partir de votre propre code source.

Malheureusement, cela ne prend pas la peine d'expliquer pourquoi. Je peux deviner les raisons, mais j'espère que quelqu'un de plus autoritaire sur le sujet pourrait offrir son point de vue.

Les deux premiers ont un sens évident, mais les deux derniers semblent être ceux que vous voudriez utiliser (et en fait, je l'ai).

De plus, s'agit-il des seules exceptions à éviter? S'il y en a d'autres, quels sont-ils et pourquoi devraient-ils également être évités?

DonBoitnott
la source
22
À partir de msdn.microsoft.com/en-us/library/ms182338.aspx : Si vous lancez un type d'exception général, tel que Exception ou SystemException dans une bibliothèque ou un framework, cela oblige les consommateurs à intercepter toutes les exceptions, y compris les exceptions inconnues qu'ils font ne sais pas comment gérer.
Henrik
3
Pourquoi lanceriez-vous une NullReferenceException?
Rik
5
@Rik: similaire à NullArgumentExceptionlaquelle certaines personnes pourraient confondre les deux.
Moslem Ben Dhaou
2
Méthodes d'extension @Rik, selon ma réponse
Marc Gravell
3
Un autre que vous ne devriez pas jeter estApplicationException
Matthew Watson

Réponses:

98

Exceptionest le type de base pour toutes les exceptions, et en tant que tel terriblement non spécifique. Vous ne devriez jamais lever cette exception car elle ne contient tout simplement aucune information utile. L'appel de code capturant des exceptions ne pouvait pas lever l'ambiguïté de l'exception intentionnellement levée (de votre logique) à partir d'autres exceptions système qui sont totalement indésirables et signalent de véritables erreurs.

La même raison s'applique également à SystemException. Si vous regardez la liste des types dérivés, vous pouvez voir un grand nombre d'autres exceptions avec une sémantique très différente.

NullReferenceExceptionet IndexOutOfRangeExceptionsont d'une nature différente. Maintenant, ce sont des exceptions très spécifiques, donc les lancer pourrait être bien. Cependant, vous ne voudrez toujours pas les lancer, car ils signifient généralement qu'il y a de réelles erreurs dans votre logique. Par exemple, l'exception de référence nulle signifie que vous essayez d'accéder à un membre d'un objet qui est null. Si c'est une possibilité dans votre code, vous devriez toujours vérifier explicitement nullet lancer une exception plus utile à la place (par exemple ArgumentNullException). De même, les IndexOutOfRangeExceptions se produisent lorsque vous accédez à un index non valide (sur des tableaux et non sur des listes). Vous devez toujours vous assurer de ne pas faire cela en premier lieu et vérifier d'abord les limites d'un tableau, par exemple.

Il existe quelques autres exceptions comme celles-ci, par exemple InvalidCastExceptionou DivideByZeroException, qui sont lancées pour des erreurs spécifiques dans votre code et signifient généralement que vous faites quelque chose de mal ou que vous ne vérifiez pas d'abord certaines valeurs non valides. En les jetant sciemment à partir de votre code, vous rendez simplement plus difficile pour le code appelant de déterminer s'ils ont été lancés en raison d'une erreur dans le code, ou simplement parce que vous avez décidé de les réutiliser pour quelque chose dans votre implémentation.

Bien sûr, il existe quelques exceptions (hah) à ces règles. Si vous créez quelque chose qui peut provoquer une exception qui correspond exactement à une exception existante, n'hésitez pas à l'utiliser, en particulier si vous essayez de faire correspondre un comportement intégré. Assurez-vous simplement de choisir un type d'exception très spécifique.

En général cependant, à moins que vous ne trouviez une exception (spécifique) qui répond à vos besoins, vous devriez toujours envisager de créer vos propres types d'exceptions pour des exceptions attendues spécifiques. Surtout lorsque vous écrivez du code de bibliothèque, cela peut être très utile pour séparer les sources d'exceptions.

poussée
la source
3
La troisième partie n'a pas de sens pour moi. Bien sûr, vous devriez plutôt éviter de provoquer ces erreurs au début, mais lorsque vous écrivez par exemple une IListimplémentation, ce n'est pas en votre pouvoir d'affecter les indices demandés, c'est l' erreur de logique de l'appelant lorsqu'un index est invalide, et vous ne pouvez que les informer de cette erreur logique en lançant une exception appropriée. Pourquoi n'est IndexOutOfRangeExceptionpas approprié?
7
@delnan Si vous implémentez IList, alors vous lancerez un ArgumentOutOfRangeExceptioncomme le suggère la documentation de l' interface . IndexOutOfRangeExceptionest pour les tableaux, et pour autant que je sache, vous ne pouvez pas réimplémenter les tableaux.
poke
2
Ce qui pourrait aussi être quelque peu lié, NullReferenceExceptionest généralement jeté en interne comme un cas spécial de AccessViolationException(IIRC, le test est quelque chose comme cmp [addr], addr, c'est-à-dire qu'il essaie de déréférencer le pointeur et s'il échoue avec une violation d'accès, il gère la différence entre NRE et AVE dans le gestionnaire d'interruption résultant). Donc, à part des raisons sémantiques, il y a aussi de la triche. Cela peut également vous décourager de vérifier nullmanuellement lorsque cela ne sert à rien - si vous voulez quand même lancer un NRE, pourquoi ne pas laisser .NET le faire?
Luaan
En ce qui concerne votre dernière déclaration, à propos des exceptions personnalisées: je n'ai jamais ressenti le besoin de le faire. Peut-être que je rate quelque chose. Dans quelles conditions faudrait-il créer un type d'exception personnalisé au lieu de quelque chose du framework?
DonBoitnott
1
Eh bien, pour les petites applications, il se peut que cela ne soit pas nécessaire. Mais dès que vous créez quelque chose de plus complexe où les pièces individuelles fonctionnent comme des «composants» indépendants, il est souvent judicieux d'introduire des exceptions personnalisées pour les situations d'erreur personnalisées. Par exemple, si vous disposez d'une couche de contrôle d'accès et que vous essayez d'exécuter un service alors que vous n'êtes pas autorisé à le faire, vous pouvez lancer une «exception d'accès refusé» personnalisée ou quelque chose. Ou si vous avez un analyseur qui analyse certains fichiers, vous pouvez avoir vos propres erreurs d'analyseur à signaler à l'utilisateur.
poke
36

Je soupçonne que l'intention avec les 2 derniers est d'éviter la confusion avec des exceptions intégrées qui ont une signification attendue. Cependant, je suis d'avis que si vous préservez l'intention exacte de l'exception : c'est la bonne throw. Par exemple, si vous écrivez une collection personnalisée, il semble tout à fait raisonnable de l'utiliser IndexOutOfRangeException- plus clair et plus spécifique, IMO, que ArgumentOutOfRangeException. Et bien que vous List<T>puissiez choisir ce dernier, il y a au moins 41 endroits (gracieuseté du réflecteur) dans la BCL (sans compter les tableaux) qui jettent sur mesure est un peu utile dans les méthodes d'extension - si vous voulez préserver la sémantique que:IndexOutOfRangeException - dont aucun n'est suffisamment "bas" pour mériter une exemption spéciale. Alors oui, je pense que vous pouvez à juste titre soutenir que cette directive est idiote. Également,NullReferenceException

obj.SomeMethod(); // this is actually an extension method

jette un NullReferenceExceptionquand objest null.

Marc Gravell
la source
2
"si vous préservez l'intention exacte de l'exception" - si tel était le cas, l'exception serait lancée sans que vous ayez à la tester en premier lieu? Et si vous l'avez déjà testé, ce n'est pas vraiment une exception?
PugFugly
5
@PugFugly prend 2 secondes pour regarder l'exemple de la méthode d'extension: non, cela ne sera pas lancé sans que vous ayez à le tester. S'il SomeMethod()n'est pas nécessaire d'effectuer un accès membre, il est incorrect de le forcer. De même: reprenez ce point avec les 41 endroits dans la BCL qui créent la personnalisation IndexOutOfRangeExceptionet les 16 endroits qui créent la personnalisationNullReferenceException
Marc Gravell
16
Je dirais qu'une méthode d'extension devrait toujours lancer un fichier au ArgumentNullExceptionlieu d'un NullReferenceException. Même si le sucre syntaxique des méthodes d'extension autorise la même syntaxe que l'accès normal aux membres, cela fonctionne toujours très différemment. Et obtenir un NRE MyStaticHelpers.SomeMethod(obj)serait tout simplement faux.
poke
1
@PugFugly BCL est une «bibliothèque de classes de base», essentiellement le cœur de .NET.
poke
1
@PugFugly: Il existe de nombreux scénarios dans lesquels le fait de ne pas détecter de manière préventive une condition entraînerait la levée d'une exception à un moment "peu pratique". Si une opération ne réussit pas, il vaut mieux lever une exception tôt que de démarrer l'opération, de passer à mi-chemin, puis de devoir nettoyer le désordre partiellement traité qui en résulte.
supercat
5

Comme vous le faites remarquer, dans l'article Créer et lancer des exceptions (Guide de programmation C #) sous la rubrique Choses à éviter lors de la levée d'exceptions , Microsoft répertorie en effet System.IndexOutOfRangeExceptioncomme un type d'exception qui ne doit pas être levé intentionnellement à partir de votre propre code source.

En revanche, dans l'article throw (référence C #) , Microsoft semble enfreindre ses propres directives. Voici une méthode que Microsoft a incluse dans son exemple:

static int GetNumber(int index)
{
    int[] nums = { 300, 600, 900 };
    if (index > nums.Length)
    {
        throw new IndexOutOfRangeException();
    }
    return nums[index];
}

Donc, Microsoft lui-même n'est pas cohérent car il démontre le lancement de IndexOutOfRangeExceptiondans sa documentation pour throw!

Cela me porte à croire qu'au moins pour le cas de IndexOutOfRangeException, il peut y avoir des occasions où ce type d'exception peut être levé par le programmeur et être considéré comme une pratique acceptable.

DavidRR
la source
1

Quand j'ai lu votre question, je me suis demandé dans quelles conditions on voudrait lever les types d'exceptions NullReferenceException, InvalidCastExceptionou ArgumentOutOfRangeException.

À mon avis, lorsque je rencontre l'un de ces types d'exceptions, je (le développeur) me sens concerné par l'avertissement dans le sens où le compilateur me parle. Donc, vous permettre (le développeur) de lancer de tels types d'exception équivaut à (le compilateur) vendre la responsabilité. Par exemple, cela suggère que le compilateur devrait maintenant permettre au développeur de décider si un objet l'est null. Mais faire une telle détermination devrait vraiment être le travail du compilateur.

PS: Depuis 2003, je développe mes propres exceptions pour pouvoir les lancer comme je le souhaite. Je pense que cela est considéré comme une pratique exemplaire.

Bellash
la source
Bons points. Cependant, je pense qu'il serait plus précis de dire que le programmeur devrait laisser le runtime .NET Framework lancer ces types d'exceptions (et que le programmeur devrait les gérer de manière appropriée).
DavidRR
0

Mettre la discussion sur NullReferenceExceptionet de IndexOutOfBoundsExceptioncôté:

Qu'en est-il de la capture et du lancer System.Exception. J'ai beaucoup jeté ce type d'exception dans mon code et je n'ai jamais été foutu par cela. De même, très souvent, j'attrape le Exceptiontype non spécifique , et cela a également très bien fonctionné pour moi. Alors, pourquoi est-ce?

Habituellement, les utilisateurs soutiennent qu'ils devraient être capables de distinguer les causes d'erreur. D'après mon expérience, il existe très peu de situations dans lesquelles vous souhaiteriez gérer différents types d'exceptions différemment. Dans les cas où vous vous attendez à ce que les utilisateurs gèrent les erreurs par programme, vous devez lever un type d'exception plus spécifique. Pour les autres cas, je ne suis pas convaincu par le guide général des meilleures pratiques.

Donc, en ce qui concerne le lancer, Exceptionje ne vois pas de raison d'interdire cela dans tous les cas.

EDIT: également à partir de la page MSDN:

Les exceptions ne doivent pas être utilisées pour modifier le déroulement d'un programme dans le cadre d'une exécution ordinaire. Les exceptions ne doivent être utilisées que pour signaler et gérer les conditions d'erreur.

L'exagération des clauses catch avec une logique individuelle pour différents types d'exceptions n'est pas non plus une bonne pratique.

uebe
la source
Le message d'exception est toujours en place pour dire ce qui s'est passé. Je ne peux pas imaginer que vous allez créer un nouveau type d'exception pour chaque erreur différente, juste au cas où un consommateur voudrait distinguer ces erreurs, par programme.
uebe
Qu'est-il arrivé à mon commentaire précédent?
DonBoitnott