web-dev-qa-db-fra.com

Quand devriez-vous créer votre propre type d'exception?

Je travaille sur un projet dans lequel nous restructurons l'ancien code C en un nouveau C++ où, pour la gestion des erreurs, nous utilisons des exceptions.

Nous créons différents types d'exceptions pour différents modules.

Je ne vois pas que cela en vaut la peine, mais je n’ai aucun argument valable pour prouver mon propos. Donc, si nous avions écrit la bibliothèque standard, vous devriez voir vector_exception, list_exception et ainsi de suite.

En y réfléchissant, je suis tombé sur cette question:

Quand devriez-vous créer votre propre type d'exception et quand devriez-vous vous en tenir aux exceptions déjà créées dans la bibliothèque std?

En outre, quels pourraient être les problèmes auxquels nous pourrions faire face dans un proche avenir si nous adoptions l'approche ci-dessus?

24
Amit Bhaira

Créez vos propres types d'exceptions lorsque:

  1. vous voudrez peut-être les différencier lors de la manipulation. S'ils sont de types différents, vous avez la possibilité d'écrire différentes clauses catch. Ils doivent toujours avoir une base commune afin que vous puissiez avoir une manipulation commune lorsque cela est approprié
  2. vous voulez ajouter des données structurées spécifiques que vous pouvez réellement utiliser dans le gestionnaire

Avoir des exceptions séparées list et vector ne semble pas la peine, sauf si elles ont quelque chose de particulier qui ressemble à une liste ou à un vecteur. Est-ce que vous allez vraiment avoir une manipulation de capture différente selon le type de conteneur qui a eu une erreur?

Inversement, il peut être judicieux de définir des types d’exception distincts pour les éléments récupérables au moment de l’exécution, par rapport aux éléments annulés mais susceptibles d’être réessayés, par rapport aux éléments définitivement fatals ou indiquant un bogue.

25
Useless

Utiliser la même exception partout est facile. Surtout en essayant d'attraper cette exception. Malheureusement, cela ouvre la porte à gestion des exceptions Pokemon . Cela comporte le risque d'attraper des exceptions inattendues.

L'utilisation d'une exception dédiée pour tous les différents modules ajoute plusieurs avantages:

  • Un message personnalisé pour chaque cas, le rendant plus utile pour les rapports
  • Catch ne peut capturer que les exceptions prévues et se bloque plus facilement dans des cas inattendus (oui, c'est un avantage par rapport à une gestion incorrecte)
  • En cas de blocage, IDE peut afficher le type d'exception, facilitant encore davantage le débogage
10
JVApen

Je pense qu’en plus des raisons évoquées dans d’autres réponses, il pourrait s’agir de la lisibilité du code, car les programmeurs consacrent beaucoup de temps à le supporter. Considérons deux morceaux de code (en supposant qu’ils génèrent l’erreur "frame vide"):

void MyClass::function() noexcept(false) {

    // ...
    if (errorCondition) {
        throw std::exception("Error: empty frame");
    }
}
void MyClass::function() noexcept(false) {

    // ...
    if (errorCondition) {
        throw EmptyFrame();
    }
}

Dans le second cas, je pense qu'il est plus lisible et que le message destiné à l'utilisateur (imprimé avec la fonction what ()) est masqué dans cette classe d'exceptions personnalisée.

5
stackoverflower

Je vois deux raisons possibles.

1) Si vous souhaitez stocker des informations personnalisées sur l'exception dans la classe d'exceptions, vous avez besoin d'une exception avec des membres de données supplémentaires. Vous hériterez souvent de l'une des classes d'exceptions std.

2) Si vous souhaitez différencier les exceptions. Par exemple, supposons que vous ayez deux invalid argument _ situations et dans votre bloc catch vous voulez pouvoir différencier les deux. Vous pouvez créer deux classes enfants de std::invalid_argument

2
Fabio

Le problème que je vois avec chaque élément de la STL déclenchant une exception de mémoire distincte est que certaines parties de la stl sont construites sur d'autres, de sorte que la file d'attente doit capturer et traduire les exceptions de liste, ou que les clients de la file d'attente doivent savoir comment gérer les exceptions de liste. .

Je peux voir une certaine logique dans la séparation des exceptions de différents modules, mais je peux aussi voir la logique d'avoir une classe de base d'exception à l'échelle du projet. Je peux aussi voir la logique d'avoir des classes de type exception.

L’alliance impie serait de construire une hiérarchie d’exceptions:

std::exception
  EXProjectBase
    EXModuleBase
      EXModule1
      EXModule2
    EXClassBase
      EXClassMemory
      EXClassBadArg
      EXClassDisconnect
      EXClassTooSoon
      EXClassTooLate

Insistez ensuite sur le fait que chaque exception déclenchée est dérivée de À LA FOIS un module et une classification. Ensuite, vous pouvez capturer tout ce qui est logique pour le récupérateur, tel qu'une déconnexion, plutôt que d'avoir à capturer séparément HighLevelDisconnect et LowLevelDisconnect.

D'un autre côté, il est également tout à fait juste de suggérer que l'interface HighLevel devrait avoir complètement traité les défaillances LowLevel et qu'elles ne devraient jamais être vues par le client API HighLevel. C’est là que la capture par module serait une fonctionnalité utile de dernier recours.

2
Gem Taylor

Je demanderais, de la try...catch section, "ce code sait-il comment récupérer de l'erreur, ou pas?"

Code utilisant try...catch prévoit qu’une exception pourrait se produire. Raisons pour lesquelles vous écririez try...catchdu tout au lieu de laisser l’exception simplement passer sont les suivants:

  1. Ajoutez du code pour sécuriser l'exception de fonction parent. Une grande partie de cette opération doit être effectuée discrètement avec la RAII dans les destructeurs, mais parfois (par exemple, une opération de déplacement) nécessite plus de travail pour annuler un travail partiellement terminé, cela dépend du niveau de sécurité d'exception souhaité.
  2. Émettez ou ajoutez des informations de débogage et renvoyez l'exception.
  3. Essayez de récupérer de l'erreur.

Dans les cas 1 et 2, vous n'avez probablement pas besoin de vous inquiéter des types d'exceptions utilisateur, elles ressembleront à ceci:

try {
    ....
} catch (const std::exception & e) {
    // print or tidy up or whatever
    throw;
} catch (...) {
    // print or tidy up or whatever
    // also complain that std::exception should have been thrown
    throw;
}

Ce sera probablement 99% des cas réels, selon mon expérience.

Parfois, vous voudrez récupérer l'erreur. Peut-être que la fonction parent sait qu’une bibliothèque échouera dans certaines conditions qui peut être corrigée de manière dynamique, ou il existe une stratégie permettant de réessayer un travail en utilisant différents mécanismes.

Parfois, le bloc catch sera écrit spécifiquement parce que le code de niveau inférieur a été conçu pour lancer des exceptions dans le cadre de son flux normal. (Je le fais souvent lors de la lecture de données externes non fiables, où beaucoup de choses peuvent de manière prévisible se tromper.)

Dans ces cas, plus rares, les types définis par l'utilisateur ont du sens.

1
spraff