web-dev-qa-db-fra.com

La capture d'exceptions générales est-elle vraiment une mauvaise chose?

Je suis généralement d'accord avec la plupart des avertissements d'analyse de code et j'essaie de les respecter. Cependant, j'ai plus de mal avec celui-ci:

CA1031: Ne pas intercepter les types d'exceptions générales

Je comprends la justification de cette règle. Mais, dans la pratique, si je veux effectuer la même action, quelle que soit l'exception levée, pourquoi dois-je gérer chacun d'eux spécifiquement? De plus, si je gère des exceptions spécifiques, que se passe-t-il si le code que j'appelle change pour lever une nouvelle exception à l'avenir? Maintenant, je dois changer mon code pour gérer cette nouvelle exception. Alors que si j'ai simplement attrapé Exception mon code n'a pas à changer.

Par exemple, si Foo appelle Bar et que Foo doit arrêter le traitement quel que soit le type d'exception levée par Bar, y a-t-il un avantage à être spécifique sur le type d'exception que j'attrape?

Peut-être un meilleur exemple:

public void Foo()
{
    // Some logic here.
    LogUtility.Log("some message");
}

public static void Log()
{
    try
    {
        // Actual logging here.
    }
    catch (Exception ex)
    {
        // Eat it. Logging failures shouldn't stop us from processing.
    }
}

Si vous n'attrapez pas d'exception générale ici, vous devez intercepter tous les types d'exceptions possibles. Patrick a de bonnes raisons de ne pas traiter OutOfMemoryException de cette façon. Et si je veux ignorer toutes les exceptions mais OutOfMemoryException?

59
Bob Horn

Ces règles sont généralement une bonne idée et doivent donc être suivies.

Mais rappelez-vous que ce sont des règles génériques. Ils ne couvrent pas toutes les situations. Ils couvrent les situations les plus courantes. Si vous avez une situation spécifique et que vous pouvez faire valoir que votre technique est meilleure (et que vous devriez être en mesure d'écrire un commentaire dans le code pour articuler votre argument), faites-le (et faites-le réviser par les pairs).

Du côté opposé de l'argument.

Je ne vois pas votre exemple ci-dessus comme une bonne situation pour le faire. Si le système de journalisation échoue (probablement en enregistrant une autre exception), je ne souhaite probablement pas que l'application continue. Quittez et imprimez l'exception sur la sortie afin que l'utilisateur puisse voir ce qui s'est passé.

33
Martin York

Oui, intercepter des exceptions générales est une mauvaise chose. Une exception signifie généralement que le programme ne peut pas faire ce que vous lui avez demandé de faire.

Il existe quelques types d'exceptions que vous pourriez gérer:

  • exceptions fatales: manque de mémoire, débordement de pile, etc. Une force surnaturelle vient perturber votre univers et le processus est déjà en train de mourir. Vous ne pouvez pas améliorer la situation, alors abandonnez
  • Exception levée car des bugs dans le code que vous utilisez: N'essayez pas de les gérer mais plutôt de corriger la source du problème. N'utilisez pas d'exceptions pour le contrôle de flux
  • Situations exceptionnelles: ne gère l'exception que dans ces cas. Ici, nous pouvons inclure: câble réseau débranché, connexion Internet interrompue, autorisations manquantes, etc.

Oh, et en règle générale: si vous ne savez pas quoi faire avec une exception si vous l'attrapez, il est préférable de simplement échouer rapidement (passez l'exception à l'appelant et laissez-le le gérer)

31
Victor Hurdugaci

La boucle extérieure la plus haute devrait avoir l'un de ces éléments pour imprimer tout ce qu'elle peut, puis mourir d'une mort horrible, violente et BRUYANTE (car cela ne devrait pas se produire et quelqu'un doit entendre).

Sinon, vous devez généralement être très prudent car vous avez très probablement pas prévu tout ce qui pourrait se produire à cet endroit, et donc ne le traiterez probablement pas correctement. Soyez aussi précis que possible afin que vous n'attrapiez que ceux que vous savez se produira, et laissez ceux qui n'ont pas été vus avant bouillonner jusqu'à la mort bruyante mentionnée ci-dessus.

15
user1249

Ce n'est pas que c'est mauvais, c'est juste que les prises spécifiques sont meilleures. Quand vous êtes spécifique, cela signifie que vous comprenez réellement, plus concrètement, ce que fait votre application et que vous avez plus de contrôle sur elle. En général, si vous rencontrez une situation où vous attrapez juste un Exception, connectez-vous et continuez, il y a probablement de mauvaises choses de toute façon. Si vous attrapez spécifiquement les exceptions que vous savez qu'un bloc de code ou une méthode peut lever, il y a plus de chances que vous puissiez réellement récupérer au lieu de simplement vous connecter et d'espérer le meilleur.

11
Ryan Hayes

J'ai réfléchi à la même chose récemment, et ma conclusion provisoire est que la simple question se pose parce que la . NET Exception hierarchy est gravement foiré.

Prenons, par exemple, le modeste ArgumentNullException qui pourrait ​​être un candidat raisonnable pour une exception que vous ne pas voulez attraper , car cela tend pour indiquer un bogue dans le code plutôt qu'une erreur d'exécution légitime. Ah oui. De même NullReferenceException , sauf que NullReferenceException dérive directement de SystemException, donc il n'y a pas de compartiment dans lequel vous pouvez placer toutes les "erreurs logiques" pour intercepter (ou pas attraper).

Then il y a l'IMNSHO major botch d'avoir SEHException dériver (via ExternalException) SystemException et donc en faire un "normal" SystemException Lorsque vous obtenez un SEHException, vous voulez écrire un vidage et terminer aussi vite que possible - et à partir de .NET 4 au moins certains les exceptions SEH sont considérées Exceptions d'état corromp qui ne sera pas intercepté. Une bonne chose, et un fait qui rend la règle CA1031 Encore plus inutile, car maintenant votre catch (Exception) paresseux n'attrapera pas ces pires types de toute façon.

Ensuite, il semble que les autres trucs du Framework dérivent de manière assez incohérente soit Exception directement o via SystemException, en tentant de grouper les clauses catch par quelque chose comme la gravité moot.

Il y a un morceau par M. Lippert de la renommée de C#, Appelé Vexing Exception , où il présente une catégorisation utile des exceptions: vous pourriez dire que vous ne voulez en attraper que des "exogènes", sauf ... C# le langage et la conception des exceptions du framework .NET rendent impossible de "capturer uniquement les exogènes" de manière succincte. (et, par exemple, un OutOfMemoryException peut très bien être une erreur récupérable totalement normale pour une API qui doit allouer des tampons qui sont en quelque sorte volumineux)

La conclusion pour moi est que la façon dont C# Les blocs catch fonctionnent et la façon dont la hiérarchie des exceptions Framework est conçue, la règle CA1031 Est totalement inutile . Il prétend pour aider à résoudre le problème racine de "ne pas avaler d'exceptions" mais avaler des exceptions n'a rien à voir avec ce que vous attrapez, mais avec ce que vous alors faites:

Il existe au moins 4 façons pour gérer légitimement un Exception capturé, et CA1031 Ne semble que superficiellement gérer l'un d'eux (à savoir le re-jeter cas).


En guise de remarque, il existe une fonctionnalité C # 6 appelée Filtres d'exception qui rendra CA1031 Un peu plus valide à nouveau, car vous pourrez alors filtrer correctement, correctement et correctement les exceptions que vous souhaitez à attraper, et il y a moins de raisons d'écrire une catch (Exception) non filtrée.

5
Martin Ba

Les deux possibilités ne s'excluent pas mutuellement.

Dans une situation idéale, vous intercepteriez tous les types d'exceptions possibles que votre méthode pourrait générer, les gérer par exception et, à la fin, ajouter une clause générale catch pour intercepter toutes les exceptions futures ou inconnues. De cette façon, vous obtenez le meilleur des deux mondes.

try
{
    this.Foo();
}
catch (BarException ex)
{
    Console.WriteLine("Foo has barred!");
}
catch (BazException ex)
{
    Console.WriteLine("Foo has bazzed!");
}
catch (Exception ex)
{
    Console.WriteLine("Unknown exception on Foo!");
    throw;
}

Gardez à l'esprit que pour intercepter les exceptions plus spécifiques, vous devez les placer en premier.

Edit: Pour les raisons indiquées dans les commentaires, ajout d'un retour dans la dernière prise.

5
Rotem

La gestion des exceptions Pokemon (je dois tous les attraper!) N'est certainement pas toujours mauvaise. Lorsque vous exposez une méthode à un client, en particulier à un utilisateur final, il est souvent préférable d'attraper tout et n'importe quoi plutôt que de faire planter et graver votre application.

Généralement, ils doivent être évités autant que possible. À moins que vous ne puissiez prendre des mesures spécifiques en fonction du type d'exception, il vaut mieux ne pas la gérer et permettre à l'exception de se propager plutôt que d'avaler l'exception ou de la gérer incorrectement.

Jetez un oeil à this SO réponse pour plus de lecture.

4
Tom Squires

La capture d'une exception générale est mauvaise car elle laisse votre programme dans un état indéfini. Vous ne savez pas où les choses ont mal tourné, vous ne savez donc pas ce que votre programme a réellement fait ou n'a pas fait.

Lorsque j'autoriserais tout attraper, c'est en fermant un programme. Tant que vous pouvez le nettoyer correctement. Rien d'aussi ennuyeux qu'un programme que vous fermez, ce qui lance simplement une boîte de dialogue d'erreur qui ne fait que rester là, ne pas s'en aller et empêcher votre ordinateur de fermer.

Dans un environnement distribué, votre méthode de journalisation pourrait se retourner contre vous: intercepter une exception générale pourrait signifier que votre programme détient toujours un verrou sur le fichier journal empêchant les autres utilisateurs de créer des journaux.

1
Pieter B

Je voudrais aborder cela d'un point de vue logique plutôt que technique,

Maintenant, je dois changer mon code pour gérer cette nouvelle exception.

Eh bien, il faudrait que quelqu'un s'en occupe. Telle est l'idée. Les rédacteurs de code de bibliothèque seraient prudents quant à l'ajout de nouveaux types d'exceptions, car il est probable que cela brise les clients, vous ne devriez pas le rencontrer très souvent.

Votre question est essentiellement "Et si je me fiche de ce qui ne va pas? Dois-je vraiment passer par les tracas de découvrir ce que c'était?"

Voici la partie beauté: non, vous ne le faites pas.

"Alors, puis-je simplement regarder dans l'autre sens et faire en sorte que toute chose désagréable surgisse sous le tapis automatiquement et en ait fini avec ça?"

Non, ce n'est pas ainsi que cela fonctionne.

Le fait est que la collection d'exceptions possibles est toujours plus grande que la collection à laquelle vous vous attendez et vous intéresse dans le contexte de votre petit problème local. Vous gérez ceux que vous attendez et si quelque chose d'inattendu se produit, vous le laissez aux gestionnaires de niveau supérieur plutôt que de l'avaler. Si vous ne vous souciez pas d'une exception à laquelle vous ne vous attendiez pas, vous pariez que quelqu'un dans la pile d'appels le fera et ce serait Sabotage de tuer une exception avant qu'elle n'atteigne son gestionnaire.

"Mais ... mais ... alors une de ces autres exceptions dont je ne me soucie pas pourrait faire échouer ma tâche!"

Oui. Mais ils seront toujours plus importants que ceux traités localement. Comme une alarme incendie ou le patron vous dit d'arrêter ce que vous faites et de reprendre une tâche plus urgente.

0
Martin Maat

Je comprends la justification de cette règle. Mais, dans la pratique, si je veux effectuer la même action, quelle que soit l'exception levée, pourquoi dois-je gérer chacun d'eux spécifiquement?

Comme d'autres l'ont dit, il est vraiment difficile (voire impossible) d'imaginer une action que vous voudriez prendre indépendamment de l'exception levée. Un seul exemple est des situations où l'état du programme a été corrompu et tout traitement ultérieur peut entraîner des problèmes (c'est la raison d'être de Environment.FailFast ).

De plus, si je gère des exceptions spécifiques, que se passe-t-il si le code que j'appelle change pour lever une nouvelle exception à l'avenir? Maintenant, je dois changer mon code pour gérer cette nouvelle exception. Alors que si j'ai simplement attrapé Exception, mon code n'a pas à changer.

Pour le code amateur, il est très bien d'attraper Exception, mais pour le code de qualité professionnelle, l'introduction d'un nouveau type d'exception doit être traitée avec le même respect qu'un changement dans la signature de la méthode, c'est-à-dire être considérée comme un changement de rupture. Si vous souscrivez à ce point de vue, il est immédiatement évident que revenir à modifier (ou vérifier) ​​le code client est la seule solution correcte.

Par exemple, si Foo appelle Bar et que Foo doit arrêter le traitement quel que soit le type d'exception levée par Bar, y a-t-il un avantage à être spécifique sur le type d'exception que j'attrape?

Bien sûr, car vous n'attraperez pas uniquement les exceptions levées par Bar. Il y aura également des exceptions que les clients de Bar ou même le runtime pourraient lancer pendant que Bar est sur la pile des appels. Un Bar bien écrit devrait définir son propre type d'exception si nécessaire afin que les appelants puissent intercepter spécifiquement les exceptions émises par lui-même.

Et si je veux ignorer toutes les exceptions, sauf OutOfMemoryException?

À mon humble avis, c'est la mauvaise façon de penser à la gestion des exceptions. Vous devez opérer sur des listes blanches (intercepter les types d'exceptions A et B), pas sur des listes noires (intercepter toutes les exceptions à l'exception de X).

0
Jon

Peut-être. Il existe des exceptions à chaque règle, et aucune règle ne doit être suivie sans critique. Votre exemple pourrait en théorie être l'une des instances où il est logique d'avaler toutes les exceptions. Par exemple, si vous souhaitez ajouter un suivi à un système de production critique et que vous voulez vous assurer que vos modifications ne perturbent pas la tâche principale de l'application.

Cependant, vous devez réfléchir soigneusement aux raisons possibles de l'échec avant de décider de les ignorer silencieusement. Par exemple, que se passe-t-il si la raison de l'exception est:

  • une erreur de programmation dans la méthode de journalisation qui la fait toujours lever une exception particulière
  • un chemin non valide vers le fichier journal dans la configuration
  • le disque est plein

Vous ne voulez pas être averti immédiatement que ce problème est présent, vous pouvez donc le résoudre? Avaler l'exception signifie que vous ne savez jamais que quelque chose s'est mal passé.

Certains problèmes (comme le disque est plein) peuvent également entraîner l'échec d'autres parties de l'application - mais cet échec n'est pas enregistré maintenant, donc vous ne savez jamais!

0
JacquesB