Attraper les exceptions générales est-il vraiment une mauvaise chose?


58

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 difficultés avec celui-ci:

CA1031: Ne pas intercepter les types d'exception générale

Je comprends la raison d'être de cette règle. Mais, dans la pratique, si je veux faire la même chose, quelle que soit l'exception levée, pourquoi devrais-je traiter chacune d'elles 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 générer une nouvelle exception à l'avenir? Maintenant, je dois changer de code pour gérer cette nouvelle exception. Considérant que si je attrape simplement Exceptionmon code ne doit 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 à préciser le type d'exception que je intercepte?

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 ne capturez pas une exception générale ici, vous devez capturer tous les types d'exceptions possibles. Patrick a un bon point qui OutOfMemoryExceptionne devrait pas être traité de cette façon. Alors que faire si je veux ignorer toutes les exceptions sauf OutOfMemoryException?


12
Qu'en est- il OutOfMemoryException? Même code de manipulation que tout le reste?
Patrick

@ Patrick Bon point. J'ai ajouté un nouvel exemple dans ma question pour couvrir votre question.
Bob Horn

1
Catching exceptions générales est aussi mauvais que de faire des déclarations générales sur ce que tout le monde devrait faire. Il n'y a généralement pas d'approche unique pour tout.

4
@ Patrick ce serait OutOfMemoryError, qui est séparé de l' Exceptionarbre d'héritage pour cette raison même
Darkhogg

Qu'en est-il des exceptions au clavier, comme Ctrl-Alt-Suppr?
Mawg

Réponses:


33

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 argumenter que votre technique est meilleure (et que vous devriez être capable d'écrire un commentaire dans le code pour articuler votre argument), faites-le (puis faites-le examiner par des pairs).

Sur le contre 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é.


2
D'accord. À tout le moins , maintenant que la journalisation normale est hors ligne, prévoyez une sorte de disposition qui donnera une sorte de sortie de débogage à STDERR, si rien d'autre.
Shadur

6
Je vous ai voté tous les deux, mais je pense que vous pensez comme des développeurs, pas comme des utilisateurs. Un utilisateur ne se soucie généralement pas de la journalisation, tant que l'application est utilisable.
Mawg

1
Intéressant, car log4net ne veut pas explicitement arrêter l'application simplement parce que la journalisation échoue. Et je pense que cela dépend de ce que vous vous connectez; les audits de sécurité, bien sûr, tuer le processus. Mais les journaux de débogage? Pas si important.
Andy

1
@Andy: Oui, tout dépend de ce que vous faites.
Martin York

2
Pourquoi ne les comprends-tu pas? Et ne devriez-vous pas vous efforcer de le faire?
Mawg

31

Oui, attraper 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.

Vous pouvez gérer quelques types d’exceptions :

  • Des exceptions fatales : manque de mémoire, débordement de pile, etc. Une force surnaturelle vient de gâcher votre univers et le processus est déjà en train de mourir. Vous ne pouvez pas améliorer la situation alors renoncez
  • Exception renvoyée en raison de bogues dans le code que vous utilisez : n'essayez pas de les gérer, mais corrigez plutôt la source du problème. N'utilisez pas d'exceptions pour le contrôle de flux
  • Situations exceptionnelles : gérez uniquement les exceptions dans ces cas. Ici, nous pouvons inclure: câble réseau débranché, connexion Internet arrêtée, 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 d'échouer rapidement (transmettez l'exception à l'appelant et laissez-la s'en occuper)


1
Qu'en est-il du cas spécifique dans la question? S'il y a un bogue dans le code de journalisation, vous pouvez probablement ignorer l'exception, car le code qui importe vraiment ne devrait pas en être affecté.
svick

4
Pourquoi ne pas corriger le bogue dans le code de journalisation?
Victor Hurdugaci

3
Victor, ce n'est probablement pas un bug. Si le disque sur lequel se trouve le journal manque d'espace, s'agit-il d'un bogue dans le code de journalisation?
Carson63000

4
@ Carson63000 - eh bien, oui. Les journaux ne sont pas écrits, donc: bug. Implémentez des journaux rotatifs de taille fixe.
Détly

5
C’est la meilleure réponse possible, mais il manque un aspect crucial: la sécurité . Il y a quelques exceptions qui résultent du fait que les méchants essayent de créer votre programme. Si une exception que vous êtes incapable de gérer est levée, vous ne pouvez plus nécessairement faire confiance à ce code. Personne n'aime entendre qu'ils ont écrit le code qui permet aux méchants d'entrer.
riwalk

15

La boucle externe la plus haute devrait en avoir un pour imprimer tout ce qu'elle peut, puis mourir d'une mort horrible, violente et NOISY (car cela ne devrait pas se produire et quelqu'un doit l'entendre).

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


1
Je suis d'accord avec cela en théorie. Mais qu'en est-il de mon exemple de journalisation ci-dessus? Que faire si vous voulez simplement ignorer les exceptions de journalisation et continuer? Devez-vous capturer toutes les exceptions qui pourraient être levées et les gérer tout en laissant les exceptions plus sérieuses faire leur apparition?
Bob Horn

6
@BobHorn: Deux façons de voir ça. Oui, vous pouvez dire que la journalisation n'est pas particulièrement importante et que si elle échoue, votre application ne doit pas s'arrêter. Et c'est un assez bon point. Mais que se passe-t-il si votre application ne se connecte pas pendant cinq mois et que vous n'en ayez aucune idée? J'ai vu cela arriver. Et puis nous avions besoin des journaux. Catastrophe. Je suggérerais de faire tout ce que vous pouvez penser pour arrêter la journalisation échouant est mieux que de l'ignorer quand c'est le cas. (Mais vous n'obtiendrez pas beaucoup de consensus à ce sujet.)
pdr

@pdr Dans mon exemple de journalisation, j'envoyais généralement une alerte par courrier électronique. Ou bien, nous avons des systèmes en place pour surveiller les journaux. Si un journal n’est pas activement rempli, notre équipe de support technique reçoit une alerte. Nous avons donc pris conscience du problème de la journalisation de différentes manières.
Bob Horn

@BobHorn votre exemple de journalisation est plutôt artificiel - la plupart des frameworks de journalisation que je connaisse vont très loin pour ne pas renvoyer d'exceptions et ne seraient pas invoqués de la sorte (car cela rendrait n'importe quelle "instruction de journal passée à la ligne X du fichier Y" inutile )

@pdr J'ai soulevé la question dans la liste de consignation (Java) sur la manière de forcer la structure de journalisation à arrêter l'application en cas d'échec de la configuration de la journalisation, tout simplement parce qu'il est SI important de disposer des journaux et que toute défaillance en mode silencieux est inacceptable. Une bonne solution est toujours en attente.

9

Ce n'est pas que c'est mauvais, c'est juste que les captures spécifiques sont meilleures. Lorsque vous êtes spécifique, cela signifie que vous comprenez réellement, de manière plus concrète, ce que fait votre application et que vous en avez plus le contrôle. En général, si vous rencontrez une situation dans laquelle vous attrapez une exception, enregistrez-la et continuez, il y a probablement de mauvaises choses qui se passent de toute façon. Si vous attrapez spécifiquement les exceptions que vous savez qu'un bloc de code ou une méthode peut générer, il est plus probable que vous puissiez réellement récupérer au lieu de simplement vous connecter et espérer le meilleur.


5

Les deux possibilités ne sont pas mutuellement exclusives.

Dans une situation idéale, vous intercepteriez tous les types d'exceptions possibles que votre méthode pourrait générer, les traiter sur une base d'exception et à la fin, ajouter une catchclause générale pour intercepter toute exception future ou inconnue. 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 capturer les exceptions plus spécifiques, vous devez les définir en premier.

Edit: Pour les raisons indiquées dans les commentaires, ajouté une relance dans la dernière capture.


2
Attendez. Pourquoi voudriez-vous jamais enregistrer une exception inconnue sur la console et continuer? S'il s'agit d'une exception que vous ne saviez même pas que le code pourrait générer, vous parlez probablement d'une défaillance du système. Continuer dans ce scénario est probablement une très mauvaise idée.
pdr

1
@pdr Vous réalisez sûrement que c'est un exemple artificiel. Le but était de montrer comment capturer les exceptions spécifiques et générales, et non comment les gérer.
Rotem

@Rotem Je pense que vous avez une bonne réponse ici. Mais pdr a un point. Le code, tel qu’il est, donne l’impression que continuer et attraper va bien. Certains débutants, voyant cet exemple, pourraient penser que c'est une bonne approche.
Bob Horn

Que ce soit contrarié ou non, votre réponse est fausse. Dès que vous commencez à intercepter des exceptions telles que Out Of Memory et Disk Full et que vous les avalez, vous prouvez pourquoi attraper des exceptions générales est en fait une mauvaise chose.
pdr

1
Si vous attrapez une exception générale uniquement pour la journaliser, vous devez la redécouvrir après la journalisation.
Victor Hurdugaci

5

Je réfléchissais récemment à la même chose, et ma conclusion provisoire est que la simple question se pose parce que la hiérarchie des exceptions .NET est gravement perturbée.

Prenez, par exemple, humble ArgumentNullExceptionqui pourrait être un candidat raisonnable pour une exception que vous ne voulez pas intercepter, car il a tendance à indiquer un bogue dans le code plutôt qu'une erreur d'exécution légitime. Ah oui. Il en va de même NullReferenceException, sauf que NullReferenceExceptioncela découle SystemExceptiondirectement, de sorte qu'il n'y a pas de compartiment dans lequel vous pouvez placer toutes les "erreurs logiques" pour intercepter (ou ne pas intercepter).

Ensuite, il y a le gros problème d'IMNSHO de SEHExceptiondériver (via ExternalException) SystemExceptionet donc d'en faire un "normal".SystemException Quand vous obtenez unSEHException, vous voulez écrire un dump et terminer aussi vite que vous le pouvez - et en commençant par .NET 4 au moins certains Les exceptions SEH sont considérées comme des exceptions d'état corrompues qui ne seront pas interceptées. Une bonne chose, et un fait qui rend laCA1031règle encore plus inutile, car à présent, vos paresseuxcatch (Exception)ne pourront plus attraper ces pires types.

Ensuite, il semble que les autres éléments du cadre dérivent de manière plutôt incohérente, soit Exceptiondirectement, soit via SystemException, en essayant de regrouper les clauses de capture par quelque chose comme une gravité sans importance.

M. Lippert C#, célèbre artiste , a écrit un article intitulé Vexing Exception , dans lequel il présente une catégorisation utile des exceptions: vous pouvez affirmer que vous souhaitez uniquement capturer les applications "exogènes", sauf que ... C#le langage et la conception du .NET les exceptions-cadres empêchent de "capturer uniquement les espèces exogènes" de manière succincte. (et, par exemple, an OutOfMemoryExceptionpeut très bien être une erreur récupérable totalement normale pour une API qui doit allouer des mémoires tampons qui sont en quelque sorte grandes)

En conclusion, pour moi, la manière dont fonctionnent C#les blocs catch et la structure de la hiérarchie des exceptions du Framework CA1031est totalement inutile . Il prétend aider à résoudre le problème fondamental de "ne pas avaler les exceptions", mais avaler des exceptions n'a rien à voir avec ce que vous attrapez, mais avec ce que vous faites ensuite :

Il y a au moins 4 façons de gérer légitimement un pris Exceptionet CA1031semble ne maîtriser que superficiellement l'un d'entre eux (à savoir le cas de relance).


Par ailleurs, il existe une fonctionnalité C # 6 appelée Filtres d’exception qui rendra un CA1031peu plus valide car vous pourrez alors filtrer correctement les exceptions que vous souhaitez intercepter, et il n’existera aucune raison de rédiger un filtre non filtré catch (Exception).


1
Un problème majeur OutOfMemoryExceptionest qu’il n’existe pas de code permettant d’être sûr que cela indique "seulement" l’échec d’une allocation particulière que l’on était prêt à faire échouer. Il est possible qu'une autre exception plus grave ait été levée et qu'elle se soit OutOfMemoryExceptionproduite pendant le déroulement de la pile à partir de cette autre exception. Java a peut-être été en retard pour la partie avec ses "try-with-resources", mais il gère les exceptions lors du déroulement de la pile un peu mieux que .NET.
Supercat

1
@supercat - c'est assez vrai, mais c'est à peu près vrai de toute autre exception système générale, lorsque les choses tournent mal dans un bloc final.
Martin Ba

1
C'est vrai, mais on peut (et devrait généralement) protéger les finallyblocs contre les exceptions auxquelles on peut raisonnablement s'attendre de manière à ce que les exceptions d'origine et nouvelles soient toutes deux consignées. Malheureusement, cela nécessitera souvent une allocation de mémoire, ce qui pourrait entraîner des échecs.
Supercat

4

La gestion des exceptions Pokemon (il faut les attraper tous!) 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 de tout capturer 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 de l'exception, il est préférable de ne pas la gérer et de permettre à l'exception de bouillonner plutôt que d'avaler l'exception ou de la gérer de manière incorrecte.

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


1
Cette situation (lorsqu'un développeur doit être un formateur Pokemon) apparaît lorsque vous créez vous-même un logiciel complet: vous avez créé les couches, les connexions à la base de données et l'interface graphique de l'utilisateur. Votre application doit récupérer des situations telles que la perte de connectivité, la suppression du dossier de l'utilisateur, la corruption des données, etc. Les utilisateurs finaux n'aiment pas les applications qui plantent et brûlent. Ils aiment les applications qui peuvent fonctionner sur un ordinateur en panne explosant!
Broken_Window

Je n'y avais pas pensé jusqu'à présent, mais dans Pokemon, il est généralement admis qu'en "attrapant tous les joueurs", vous en voulez exactement un, et que vous devez vous en occuper spécifiquement, ce qui est le contraire de l'usage courant de cette phrase. parmi les programmeurs.
Magus

2
@Magus: Avec une méthode comme LoadDocument()celle-ci, il est essentiellement impossible d'identifier tout ce qui pourrait mal tourner, mais 99% des exceptions pouvant être levées signifient simplement "Il n'était pas possible d'interpréter le contenu d'un fichier portant le même nom. un document; en traiter. " Si quelqu'un tente d'ouvrir quelque chose qui n'est pas un fichier de document valide, cela ne devrait pas bloquer l'application et tuer tous les autres documents ouverts. La gestion des erreurs Pokemon dans de tels cas est laide, mais je ne connais pas de bonne alternative.
Supercat

@supercat: Je faisais juste une remarque linguistique. Cependant, je ne pense pas que le contenu d'un fichier non valide ressemble à quelque chose qui devrait générer plus d'un type d'exception.
Magus

1
@Magus: Il peut lancer toutes sortes d'exceptions - c'est le problème. Il est souvent très difficile d'anticiper tous les types d'exceptions pouvant être levées à la suite de données non valides dans un fichier. Si on n'utilise pas la gestion de PokeMon, on risque de perdre une application, par exemple, le fichier que l'on chargeait contenait une chaîne de chiffres exceptionnellement longue dans un emplacement qui nécessitait un entier 32 bits au format décimal, et un débordement numérique s'était produit dans la logique d'analyse.
Supercat

1

La capture d'une exception générale est incorrecte car elle laisse votre programme dans un état non dé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.

Là où j'autoriserais tout, c'est lors de la fermeture d'un programme. Tant que vous pouvez le nettoyer bien. Rien de plus agaçant qu'un programme que vous fermez ne lance qu'une boîte de dialogue d'erreur qui ne fait que rester assis à la place, ne disparaissant pas et empêchant votre ordinateur de fermer.

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


0

Je comprends la raison d'être de cette règle. Mais, dans la pratique, si je veux faire la même chose, quelle que soit l'exception levée, pourquoi devrais-je traiter chacune d'elles spécifiquement?

Comme d’autres l’ont dit, il est très difficile (voire impossible) d’imaginer une action que vous voudriez entreprendre quelle que soit l’exception levée. Un exemple parmi d'autres sont les situations dans lesquelles l'état du programme a été corrompu et tout traitement ultérieur peut entraîner des problèmes (c'est la raison derrière 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 générer une nouvelle exception à l'avenir? Maintenant, je dois changer de code pour gérer cette nouvelle exception. Alors que si je attrape simplement Exception, mon code ne doit pas changer.

Pour le code de passe-temps, il est tout à fait acceptable 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 radical. Si vous vous abonnez à ce point de vue, il est immédiatement évident que revenir au code client (ou à le vérifier) ​​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 à préciser le type d'exception que je intercepte?

Bien sûr, parce que vous ne capturerez pas que des exceptions Bar. Il y aura également des exceptions que les clients de Bar ou même le moteur d'exécution peuvent émettre pendant la durée de la Barpile d'appels. Un utilisateur bien écrit Bardoit définir son propre type d'exception si nécessaire afin que les appelants puissent spécifiquement intercepter les exceptions émises par lui-même.

Alors, que faire si je veux ignorer toutes les exceptions sauf OutOfMemoryException?

IMHO c'est la mauvaise façon de penser à la gestion des exceptions. Vous devriez opérer sur des listes blanches (intercepter les types d'exceptions A et B) et non sur des listes noires (intercepter toutes les exceptions sauf X).


Il est souvent possible de spécifier une action à exécuter pour toutes les exceptions indiquant qu'une tentative d'opération a échoué sans effet secondaire et sans implication néfaste pour l'état du système. Par exemple, si un utilisateur sélectionne un fichier de document à charger, un programme passe son nom à une méthode "créer un objet de document avec les données d'un fichier disque", qui échoue pour une raison particulière que l'application n'a pas anticipée (mais répond aux critères ci-dessus), le comportement approprié doit être d’afficher un message «Impossible de charger le document» plutôt que de bloquer l’application.
Supercat

Malheureusement, il n’existe aucun moyen standard par lequel une exception peut indiquer la chose la plus importante que le code client voudrait savoir, à savoir si cela implique quelque chose de mal pour l’état du système au-delà de ce que supposerait l’impossibilité d’exécuter l’opération demandée. Ainsi, même si les situations dans lesquelles toutes les exceptions doivent être traitées sont rares, il existe donc de nombreuses situations dans lesquelles de nombreuses exceptions doivent être traitées de la même manière, et aucun critère qui est satisfait par toutes ces exceptions ne serait également satisfait par des cas rares qui devrait être traité différemment.
Supercat

0

Peut - être . Il existe des exceptions à chaque règle et aucune règle ne doit être suivie sans discernement. Vous pouvez, par exemple , constituer l’un des cas où il est logique d’avaler toutes les exceptions. Par exemple, si vous souhaitez ajouter un traçage à 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 bien réfléchir aux raisons possibles d’un échec avant de décider de les ignorer en silence. 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 provoque toujours une exception particulière
  • un chemin non valide vers le fichier de journalisation dans la configuration
  • le disque est plein

Ne voulez-vous pas être averti immédiatement de la présence de ce problème afin de pouvoir le résoudre? En avalant l'exception, vous ne saurez jamais que quelque chose s'est mal passé.

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


0

J'aimerais aborder cette question d'un point de vue logique plutôt que technique,

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

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

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

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

"Alors, est-ce que je peux juste regarder de l'autre côté et faire en sorte que tout ce qui est néfaste soit automatiquement balayé sous le tapis et que ce soit fini?"

Non, ce n'est pas comme ça que ça marche.

Le fait est que la collection d'exceptions possibles est toujours plus volumineuse que la collection que vous attendez et s'intéresse au contexte de votre petit problème local. Vous gérez ceux que vous prévoyez et si quelque chose d'inattendu se produit, vous laissez le soin 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 misez quelqu'un sur la pile d'appels et ce serait un sabotage de tuer une exception avant qu'elle n'atteigne son gestionnaire.

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

Oui. Mais ils seront toujours plus importants que ceux gérés localement. Comme une alarme incendie ou le patron qui vous dit d'arrêter ce que vous faites et de relever une tâche plus urgente qui se présentait.


-3

Vous devez intercepter les exceptions générales au plus haut niveau de chaque processus et les gérer en signalant le bogue le mieux possible, puis en mettant fin au processus.

Vous ne devez pas intercepter les exceptions générales et essayer de continuer l'exécution.

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.