482 votes

Pourquoi ne pas envelopper chaque bloc dans des "try"-"catch" ?

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 par un bloc try significatif.

Je viens de poster ' Vous devriez TOUJOURS envelopper les appels qui peuvent lancer dans des blocs try, catch. à cette question et on m'a dit que c'était un "conseil remarquablement mauvais" - j'aimerais comprendre pourquoi.

400voto

Mitch Wheat Points 169614

Une méthode ne doit attraper une exception que si elle peut la traiter de manière raisonnable.

Sinon, il faut le transmettre vers le haut, dans l'espoir qu'une méthode située plus haut dans la pile d'appels puisse lui donner un sens.

Comme d'autres l'ont fait remarquer, il est bon d'avoir un gestionnaire d'exceptions non gérées (avec journalisation) au niveau le plus élevé de la pile d'appels afin de s'assurer que toute erreur fatale est enregistrée.

12 votes

Il est également intéressant de noter qu'il y a des coûts (en termes de code généré) à try blocs. Il y a une bonne discussion dans "More Effective C++" de Scott Meyers.

33 votes

En fait, try Les blocs sont libres dans tout compilateur C moderne, cette information est datée Nick. Je ne suis pas non plus d'accord avec l'idée d'avoir un gestionnaire d'exception de haut niveau, car vous perdez les informations de localité (l'endroit réel où l'instruction a échoué).

36 votes

@Blindly : le gestionnaire d'exception supérieur n'est pas là pour gérer l'exception, mais en fait pour crier haut et fort qu'il y a eu une exception non gérée, donner son message, et terminer le programme de manière gracieuse (retourner 1 au lieu d'un appel à terminate ). Il s'agit plutôt d'un mécanisme de sécurité. Aussi, try/catch sont plus ou moins libres quand il n'y a pas d'exception. Lorsqu'il y en a une qui se propage, elle consomme du temps à chaque fois qu'elle est levée et rattrapée, donc une chaîne de try/catch que seule la relance n'est pas sans coût.

153voto

D.Shawley Points 30324

Comme Mitch et autres Vous ne devriez pas attraper une exception que vous ne prévoyez pas de traiter d'une manière ou d'une autre. Lors de la conception de l'application, vous devez tenir compte de la manière dont elle va traiter systématiquement les exceptions. Par exemple, vous traitez 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 du domaine ne soit pas exposée au fait qu'il y a une base de données sous le capot quelque part.

Il y a quelques odeurs de code connexes que vous devez absolument éviter, en plus de l'élément de code suivant "attrape tout partout" odeur.

  1. "attraper, enregistrer, relancer" si vous voulez une journalisation basée sur la portée, écrivez une classe qui émet une déclaration de journalisation dans son destructeur lorsque la pile se déroule en raison d'une exception (comme dans le cas de la classe std::uncaught_exception() ). Tout ce que vous avez à faire est de déclarer une instance de journalisation dans la portée qui vous intéresse et, voilà, vous avez la journalisation et pas d'inutiles try / catch logique.

  2. "attraper, lancer traduit" Dans la plupart des cas, il s'agit d'un problème d'abstraction. À moins que vous ne mettiez en œuvre une solution fédérée où vous traduisez plusieurs exceptions spécifiques en une exception plus générique, vous avez probablement une couche d'abstraction inutile... et ne dites pas que "je pourrais en avoir besoin demain". .

  3. "attraper, nettoyer, relancer" C'est l'une de mes bêtes noires. Si vous voyez beaucoup de cela, alors vous devriez postuler. L'acquisition des ressources est l'initialisation et placer la partie de nettoyage dans le destructeur d'un objet de type concierge l'instance de l'objet.

Je considère que le code qui est jonché de try / catch blocs pour être une bonne cible pour la révision du code et la refactorisation. Cela indique que soit la gestion des exceptions n'est pas bien comprise, soit le code est devenu un amœba et a sérieusement besoin d'être remanié.

9 votes

Le numéro 1 est nouveau pour moi. +1 pour cela. De plus, j'aimerais noter une exception commune au n°2, à savoir que si vous concevez une bibliothèque, vous voudrez souvent traduire les exceptions internes en quelque chose de spécifié par l'interface de votre bibliothèque afin de réduire le couplage (c'est peut-être ce que vous entendez par "solution fédérée", mais je ne connais pas ce terme).

3 votes

1 votes

2, lorsque ce n'est pas un problème de code mais que cela a du sens, peut être amélioré en conservant l'ancienne exception comme une exception imbriquée.

55voto

sharptooth Points 93379

Parce que la prochaine question est "J'ai attrapé une exception, que dois-je faire ensuite ?". Que ferez-vous ? Si vous ne faites rien, cela revient à masquer les erreurs et le programme pourrait "ne pas fonctionner" sans qu'il soit possible de savoir ce qui s'est passé. Vous devez comprendre ce que vous ferez exactement une fois que vous aurez attrapé l'exception et ne l'attrapez que si vous le savez.

36voto

AshleysBrain Points 11439

Vous n'avez pas besoin de couvrir chaque avec des try-catchs parce qu'un try-catch peut toujours attraper des exceptions non gérées lancées dans des fonctions situées plus bas dans la pile d'appels. Ainsi, plutôt que d'avoir un try-catch pour chaque fonction, vous pouvez en avoir un au niveau logique supérieur de votre application. Par exemple, il peut y avoir une fonction SaveDocument() une routine de haut niveau, qui appelle de nombreuses méthodes qui appellent d'autres méthodes, etc. Ces sous-méthodes n'ont pas besoin de leurs propres try-catches, car s'ils sont lancés, ils sont quand même rattrapés par SaveDocument() de la prise.

C'est intéressant pour trois raisons : c'est pratique car vous disposez d'un seul endroit pour signaler une erreur : le fichier SaveDocument() bloc(s) de capture. 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 dès qu'une exception est levée. Avec chaque sous-méthode try-catching, si une exception est levée, vous entrez dans le bloc catch de cette méthode, l'exécution quitte la fonction, et elle poursuit par le biais de SaveDocument() . Si quelque chose a déjà mal tourné, vous voulez probablement vous arrêter là.

Trois, tous vos sous-méthodes peut supposer que chaque appel aboutit . Si un appel échoue, l'exécution sautera au bloc catch et le code suivant ne sera jamais exécuté. Cela peut rendre votre code beaucoup plus propre. Par exemple, voici avec les codes d'erreur :

int ret = SaveFirstSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

ret = SaveSecondSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

ret = SaveThirdSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

Voici comment cela pourrait être écrit avec des exceptions :

// these throw if failed, caught in SaveDocument's catch
SaveFirstSection();
SaveSecondSection();
SaveThirdSection();

Maintenant, ce qui se passe est beaucoup plus clair.

Notez que le code sécurisé par les exceptions peut être plus difficile à écrire d'une autre manière : vous ne voulez pas perdre de mémoire si une exception est levée. Assurez-vous de connaître RAII Les objets de la STL, les conteneurs de la STL, les pointeurs intelligents et les autres objets qui libèrent leurs ressources dans les destructeurs, puisque les objets sont toujours détruits avant les exceptions.

2 votes

De magnifiques exemples. Oui, attraper aussi haut que possible, en unités logiques, comme autour d'une opération "transactionnelle" comme un chargement/sauvegarde/etc. Rien n'est pire qu'un code parsemé d'éléments répétitifs, redondants, etc. try - catch les blocs 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 la transaction ou du programme et sortie ! Si un échec digne d'une exception se produit, je parie que la plupart des utilisateurs veulent juste sauver ce qu'ils peuvent ou, au moins, être laissés tranquilles sans avoir à gérer 10 niveaux de messages à ce sujet.

0 votes

Je voulais juste dire que c'est l'une des meilleures explications sur le thème "lancer tôt, attraper tard" que j'aie jamais lues : elle est concise et les exemples illustrent parfaitement vos propos. Je vous remercie !

28voto

Tadeusz Kopec Points 7625

Herb Sutter a écrit sur ce problème ici . A lire absolument.
Un teaser :

"Écrire du code sécurisé par les exceptions consiste fondamentalement à écrire "try" et "catch" aux bons endroits." Discutez-en.

Pour parler franchement, cette affirmation reflète une incompréhension fondamentale de la sécurité des exceptions. Les exceptions ne sont qu'une autre forme de signalement des erreurs, et nous savons bien que l'écriture d'un code sécurisé contre les erreurs ne consiste pas seulement à savoir où vérifier les codes de retour et gérer les conditions d'erreur.

En fait, il s'avère que la protection contre les exceptions consiste rarement à écrire des " try " et des " catch " - et plus c'est rare, mieux c'est. De plus, n'oubliez jamais que la protection contre les exceptions affecte la conception d'un morceau de code ; il ne s'agit jamais d'une simple réflexion après coup que l'on peut agrémenter de quelques instructions "catch" supplémentaires, comme pour l'assaisonnement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X