J'ai toujours été d'avis que si une méthode peut lever une exception, il est imprudent de ne pas protéger cet appel avec un bloc try significatif.
Je viens de poster « Vous devriez TOUJOURS boucler les appels qui peuvent lancer try, catch blocks. »à cette question et on m'a dit qu'il s'agissait d'un« conseil remarquablement mauvais »- j'aimerais comprendre pourquoi.
Réponses:
Une méthode ne doit intercepter une exception que lorsqu'elle peut la gérer de manière raisonnable.
Sinon, transmettez-le, dans l'espoir qu'une méthode plus élevée dans la pile d'appels puisse la comprendre.
Comme d'autres l'ont noté, il est recommandé d'avoir un gestionnaire d'exceptions non géré (avec journalisation) au plus haut niveau de la pile d'appels pour garantir que toutes les erreurs fatales sont enregistrées.
la source
try
blocs. Il y a une bonne discussion dans "Plus efficace C ++" de Scott Meyers.try
blocs sont gratuits dans tout compilateur C moderne, cette information est datée de Nick. Je suis également en désaccord sur le fait d'avoir un gestionnaire d'exceptions de niveau supérieur, car vous perdez les informations de localité (l'endroit réel où l'instruction a échoué).terminate
) . Il s'agit plus d'un mécanisme de sécurité. En outre,try/catch
sont plus ou moins gratuits lorsqu'il n'y a aucune exception. Lorsqu'il y en a un qui se propage, il consomme du temps à chaque fois qu'il est lancé et capturé, donc une chaîne detry/catch
ce seul retour n'est pas gratuite.Comme Mitch et d' autres l'ont déclaré, vous ne devriez pas attraper une exception que vous ne prévoyez pas de gérer d'une manière ou d'une autre. Vous devez considérer comment l'application va gérer systématiquement les exceptions lorsque vous la concevez. Cela conduit généralement à avoir des couches de gestion des erreurs basées sur les abstractions - par exemple, vous gérez toutes les erreurs liées à SQL dans votre code d'accès aux données afin que la partie de l'application qui interagit avec les objets de domaine ne soit pas exposée au fait qu'il est une DB sous le capot quelque part.
Il y a quelques odeurs de code liées que vous voulez absolument éviter en plus de l' odeur "tout attraper partout" .
"catch, log, rethrow" : si vous voulez une journalisation basée sur la portée, alors écrivez une classe qui émet une instruction log dans son destructeur lorsque la pile se déroule à cause d'une exception (ala
std::uncaught_exception()
). Tout ce que vous devez faire est de déclarer une instance de journalisation dans la portée qui vous intéresse et, voila, vous avez une journalisation et pas de logiquetry
/ inutilecatch
."catch, throw traduire" : cela indique généralement un problème d'abstraction. À moins que vous n'implémentiez une solution fédérée où vous traduisez plusieurs exceptions spécifiques en une autre plus générique, vous avez probablement une couche d'abstraction inutile ... et ne dites pas que "j'en aurai peut-être besoin demain" .
"catch, cleanup, rethrow" : c'est une de mes bêtes noires. Si vous voyez beaucoup de cela, alors vous devez appliquer les techniques d' acquisition de ressources est l'initialisation et placer la partie de nettoyage dans le destructeur d'une instance d'objet concierge .
Je considère que le code jonché de
try
/catch
blocs est une bonne cible pour la révision et la refactorisation du code. Cela indique que la gestion des exceptions n'est pas bien comprise ou que le code est devenu une amœba et a sérieusement besoin d'être refactorisé.la source
Logger
classe similaire àlog4j.Logger
celle qui inclut l'ID de thread dans chaque ligne de journal et j'ai émis un avertissement dans le destructeur lorsqu'une exception était active.Parce que la question suivante est "J'ai attrapé une exception, que dois-je faire ensuite?" Que vas-tu faire? Si vous ne faites rien - c'est une erreur qui se cache et le programme pourrait "ne pas fonctionner" sans aucune chance de trouver ce qui s'est passé. Vous devez comprendre ce que vous ferez exactement une fois que vous aurez pris l'exception et ne l'attraper que si vous le savez.
la source
Vous n'avez pas besoin de couvrir chaque bloc avec try-catches car un try-catch peut toujours intercepter les exceptions non gérées jetées dans les fonctions plus bas dans la pile des appels. Donc, plutôt que de faire en sorte que chaque fonction ait un try-catch, vous pouvez en avoir un au niveau logique supérieur de votre application. Par exemple, il pourrait y avoir une
SaveDocument()
routine de niveau supérieur, qui appelle de nombreuses méthodes qui appellent d' autres méthodes , etc. Ces sous-méthodes ne ont pas besoin de leurs propres essayages prises, parce que si elles jettent, il est toujours pris parSaveDocument()
les prises de.C'est bien pour trois raisons: c'est pratique car vous avez un seul endroit pour signaler une erreur: le
SaveDocument()
(s) bloc (s) catch. Il n'est pas nécessaire de répéter cela dans toutes les sous-méthodes, et c'est ce que vous voulez de toute façon: un seul endroit pour donner à l'utilisateur un diagnostic utile sur quelque chose qui a mal tourné.Deuxièmement, la sauvegarde est annulée chaque fois qu'une exception est levée. Avec chaque sous-méthode essai-capture, si une exception est levée, vous obtenez pour le bloc catch de cette méthode, les feuilles d'exécution de la fonction, et il exerce à travers
SaveDocument()
. Si quelque chose a déjà mal tourné, vous voudrez probablement vous arrêter là.Troisièmement, toutes vos sous-méthodes peuvent supposer que chaque appel réussit . Si un appel échoue, l'exécution passe au bloc catch et le code suivant n'est jamais exécuté. Cela peut rendre votre code beaucoup plus propre. Par exemple, voici les codes d'erreur:
Voici comment cela pourrait être écrit avec des exceptions:
Maintenant, ce qui se passe est beaucoup plus clair.
Notez que le code protégé contre les exceptions peut être plus difficile à écrire de différentes manières: vous ne voulez pas perdre de mémoire si une exception est levée. Assurez-vous que vous connaissez RAII , conteneurs STL, pointeurs intelligents et autres objets qui libèrent leurs ressources dans les destructeurs, car les objets sont toujours détruits avant les exceptions.
la source
try
-catch
qui tentent de signaler chaque permutation légèrement différente d'une erreur avec un message légèrement différent, alors qu'en réalité, ils devraient tous se terminer de la même manière: échec de transaction ou de programme et sortie! Si un échec digne d'une exception se produit, je parie que la plupart des utilisateurs veulent simplement récupérer ce qu'ils peuvent ou, au moins, être laissés seuls sans avoir à traiter 10 niveaux de message à ce sujet.Herb Sutter a écrit sur ce problème ici . Pour sûr vaut la peine d'être lu.
Un teaser:
la source
Comme indiqué dans d'autres réponses, vous ne devez intercepter une exception que si vous pouvez effectuer une sorte de gestion d'erreurs sensible pour cela.
Par exemple, dans la question qui a généré votre question, l'interrogateur demande s'il est sûr d'ignorer les exceptions pour un
lexical_cast
d'un entier à une chaîne. Un tel casting ne devrait jamais échouer. S'il a échoué, quelque chose a terriblement mal tourné dans le programme. Que pourriez-vous faire pour récupérer dans cette situation? Il est probablement préférable de laisser le programme mourir, car il est dans un état auquel on ne peut pas faire confiance. Donc, ne pas gérer l'exception peut être la chose la plus sûre à faire.la source
Si vous gérez toujours les exceptions immédiatement dans l'appelant d'une méthode qui peut lever une exception, les exceptions deviennent inutiles et vous feriez mieux d'utiliser des codes d'erreur.
Le point entier des exceptions est qu'elles n'ont pas besoin d'être gérées dans toutes les méthodes de la chaîne d'appel.
la source
Le meilleur conseil que j'ai entendu est que vous ne devez jamais intercepter les exceptions qu'aux points où vous pouvez raisonnablement faire quelque chose au sujet de la condition exceptionnelle, et que "capturer, enregistrer et libérer" n'est pas une bonne stratégie (si cela est parfois inévitable dans les bibliothèques).
la source
Je suis d'accord avec l'orientation de base de votre question pour gérer autant d'exceptions que possible au niveau le plus bas.
Une partie de la réponse existante dit "Vous n'avez pas besoin de gérer l'exception. Quelqu'un d'autre le fera dans la pile". D'après mon expérience, c'est une mauvaise excuse pour ne pas penser à la gestion des exceptions au niveau du morceau de code actuellement développé, faisant de l'exception la gestion du problème de quelqu'un d'autre ou d'une version ultérieure.
Ce problème se développe considérablement dans le développement distribué, où vous devrez peut-être appeler une méthode implémentée par un collègue. Et puis vous devez inspecter une chaîne imbriquée d'appels de méthode pour savoir pourquoi il / elle vous lance une exception, qui aurait pu être gérée beaucoup plus facilement avec la méthode imbriquée la plus profonde.
la source
Le conseil que mon professeur d'informatique m'a donné une fois était: "N'utilisez les blocs Try and Catch que lorsqu'il n'est pas possible de gérer l'erreur en utilisant des moyens standard."
À titre d'exemple, il nous a dit que si un programme rencontrait un problème grave dans un endroit où il n'était pas possible de faire quelque chose comme:
Ensuite, vous devriez utiliser les blocs try, catch. Bien que vous puissiez utiliser des exceptions pour gérer cela, ce n'est généralement pas recommandé car les exceptions sont coûteuses en termes de performances.
la source
Si vous souhaitez tester le résultat de chaque fonction, utilisez des codes retour.
Le but des exceptions est que vous puissiez tester les résultats MOINS souvent. L'idée est de séparer les conditions exceptionnelles (inhabituelles, plus rares) de votre code plus ordinaire. Cela permet de garder le code ordinaire plus propre et plus simple - mais toujours capable de gérer ces conditions exceptionnelles.
Dans un code bien conçu, des fonctions plus profondes peuvent se déclencher et des fonctions plus élevées peuvent intercepter. Mais la clé est que de nombreuses fonctions "intermédiaires" seront libérées de la charge de gérer des conditions exceptionnelles. Ils doivent seulement être "exceptionnels", ce qui ne signifie pas qu'ils doivent attraper.
la source
Je voudrais ajouter à cette discussion que, depuis C ++ 11 , cela a beaucoup de sens, tant que chaque
catch
blocrethrow
est l'exception jusqu'au point où il peut / doit être géré. De cette façon, une trace peut être générée . Je pense donc que les avis précédents sont en partie dépassés.Utiliser
std::nested_exception
etstd::throw_with_nested
Il est décrit sur StackOverflow ici et ici comment y parvenir.
Puisque vous pouvez le faire avec n'importe quelle classe d'exception dérivée, vous pouvez ajouter beaucoup d'informations à une telle trace! Vous pouvez également jeter un oeil à mon MWE sur GitHub , où une trace ressemblerait à quelque chose comme ceci:
la source
J'ai eu "l'opportunité" de récupérer plusieurs projets et les cadres ont remplacé toute l'équipe de développement car l'application avait trop d'erreurs et les utilisateurs étaient fatigués des problèmes et des contournements. Ces bases de code avaient toutes une gestion centralisée des erreurs au niveau de l'application, comme le décrit la réponse la plus votée. Si cette réponse est la meilleure pratique, pourquoi cela n'a-t-il pas fonctionné et a-t-il permis à l'équipe de développement précédente de résoudre les problèmes? Peut-être que parfois cela ne fonctionne pas? Les réponses ci-dessus ne mentionnent pas combien de temps les développeurs passent à résoudre des problèmes uniques. Si le temps de résolution des problèmes est la mesure clé, l'instrumentation du code avec les blocs try..catch est une meilleure pratique.
Comment mon équipe a-t-elle résolu les problèmes sans modifier de manière significative l'interface utilisateur? Simple, chaque méthode a été instrumentée avec try..catch bloqué et tout a été enregistré au point d'échec avec le nom de la méthode, les valeurs des paramètres de méthode concaténées dans une chaîne transmise avec le message d'erreur, le message d'erreur, le nom de l'application, la date, et version. Avec ces informations, les développeurs peuvent exécuter des analyses sur les erreurs pour identifier l'exception qui se produit le plus! Ou l'espace de noms avec le plus grand nombre d'erreurs. Il peut également valider qu'une erreur qui se produit dans un module est correctement gérée et n'est pas causée par plusieurs raisons.
Un autre avantage pro de ceci est que les développeurs peuvent définir un point d'arrêt dans la méthode de journalisation des erreurs et avec un point d'arrêt et un simple clic sur le bouton de débogage "step out", ils sont dans la méthode qui a échoué avec un accès complet au réel objets au point de défaillance, facilement accessibles dans la fenêtre immédiate. Il facilite le débogage et permet de faire glisser l'exécution vers le début de la méthode pour dupliquer le problème et trouver la ligne exacte. La gestion centralisée des exceptions permet-elle à un développeur de répliquer une exception en 30 secondes? Non.
L'instruction "Une méthode ne doit intercepter une exception que lorsqu'elle peut la gérer de manière raisonnable." Cela implique que les développeurs peuvent prévoir ou rencontreront toutes les erreurs pouvant survenir avant la publication. Si cela était vrai à un niveau supérieur, le gestionnaire d'exceptions d'application ne serait pas nécessaire et il n'y aurait pas de marché pour Elastic Search et logstash.
Cette approche permet également aux développeurs de trouver et de résoudre les problèmes intermittents en production! Souhaitez-vous déboguer sans débogueur en production? Ou préférez-vous prendre des appels et recevoir des courriels d'utilisateurs en colère? Cela vous permet de résoudre les problèmes avant que quiconque ne le sache et sans avoir à envoyer un courrier électronique, une messagerie instantanée ou Slack avec le support, car tout le nécessaire pour résoudre le problème est là. 95% des numéros n'ont jamais besoin d'être reproduits.
Pour fonctionner correctement, il doit être combiné avec une journalisation centralisée qui peut capturer l'espace de nom / module, le nom de classe, la méthode, les entrées et le message d'erreur et le stocker dans une base de données afin qu'il puisse être agrégé pour mettre en évidence la méthode qui échoue le plus afin qu'elle puisse être fixe en premier.
Parfois, les développeurs choisissent de lever des exceptions dans la pile à partir d'un bloc catch, mais cette approche est 100 fois plus lente que le code normal qui ne lève pas. La capture et la libération avec journalisation sont préférées.
Cette technique a été utilisée pour stabiliser rapidement une application qui échouait toutes les heures pour la plupart des utilisateurs d'une entreprise Fortune 500 développée par 12 développeurs sur 2 ans. À l'aide de ces 3000 exceptions différentes ont été identifiées, corrigées, testées et déployées en 4 mois. Cela correspond en moyenne à un correctif toutes les 15 minutes en moyenne pendant 4 mois.
Je suis d'accord que ce n'est pas amusant de taper tout ce qui est nécessaire pour instrumenter le code et je préfère ne pas regarder le code répétitif, mais ajouter 4 lignes de code à chaque méthode en vaut la peine à long terme.
la source
Outre les conseils ci-dessus, j'utilise personnellement certains try + catch + throw; pour la raison suivante:
la source
Je me sens obligé d'ajouter une autre réponse bien que la réponse de Mike Wheat résume assez bien les points principaux. Je pense à ça comme ça. Lorsque vous avez des méthodes qui font plusieurs choses, vous multipliez la complexité, pas en l'ajoutant.
En d'autres termes, une méthode qui est enveloppée dans un catch try a deux résultats possibles. Vous avez le résultat de non-exception et le résultat d'exception. Lorsque vous avez affaire à de nombreuses méthodes, cela explose de façon exponentielle au-delà de la compréhension.
Exponentiellement parce que si chaque méthode se ramifie de deux manières différentes, chaque fois que vous appelez une autre méthode, vous équerrez le nombre précédent de résultats potentiels. Au moment où vous avez appelé cinq méthodes, vous avez au moins 256 résultats possibles au minimum. Comparez cela à ne pas faire de try / catch dans chaque méthode et vous n'avez qu'un seul chemin à suivre.
Voilà essentiellement comment je le vois. Vous pourriez être tenté de prétendre que tout type de branchement fait la même chose, mais try / catches est un cas particulier car l'état de l'application devient fondamentalement indéfini.
Donc, en bref, try / catches rend le code beaucoup plus difficile à comprendre.
la source
Vous n'avez pas besoin de couvrir chaque partie de votre code à l'intérieur
try-catch
. L'utilisation principale dutry-catch
bloc est la gestion des erreurs et les bugs / exceptions dans votre programme. Quelques utilisations detry-catch
-try-catch
bloc.la source
try
/catch
est complètement séparé / orthogonal de cela. Si vous souhaitez disposer des objets dans une portée plus petite, vous pouvez simplement en ouvrir un nouveau{ Block likeThis; /* <- that object is destroyed here -> */ }
- pas besoin d'enveloppertry
/catch
sauf si vous avez réellement besoin decatch
quoi que ce soit, bien sûr.