Utilisation idiomatique des exceptions en C ++

16

La FAQ d'exception isocpp.org indique

N'utilisez pas throw pour indiquer une erreur de codage lors de l'utilisation d'une fonction. Utilisez assert ou un autre mécanisme pour envoyer le processus dans un débogueur ou pour bloquer le processus et collecter le vidage sur incident pour le développeur à déboguer.

D'un autre côté, la bibliothèque standard définit std :: logic_error et tous ses dérivés, qui me semblent être censés gérer, entre autres, les erreurs de programmation. Passer une chaîne vide à std :: stof (lancera invalid_argument) n'est-il pas une erreur de programmation? Passer une chaîne contenant des caractères différents de «1» / «0» à std :: bitset (lancera invalid_argument) n'est-il pas une erreur de programmation? L'appel de std :: bitset :: set avec un index invalide (lancera out_of_range) n'est-il pas une erreur de programmation? Si ce n'est pas le cas, alors quelle est une erreur de programmation que l'on pourrait tester? Le constructeur basé sur une chaîne std :: bitset n'existe que depuis C ++ 11, il aurait donc dû être conçu avec une utilisation idiomatique des exceptions à l'esprit. D'un autre côté, des gens m'ont dit que logic_error ne devait pas du tout être utilisé.

Une autre règle qui revient fréquemment avec des exceptions est "n'utilisez les exceptions que dans des circonstances exceptionnelles". Mais comment une fonction de bibliothèque est-elle censée savoir quelles circonstances sont exceptionnelles? Pour certains programmes, le fait de ne pas pouvoir ouvrir un fichier peut être exceptionnel. Pour d'autres, l'impossibilité d'allouer de la mémoire n'est pas exceptionnelle. Et il y a des centaines de cas entre les deux. Impossible de créer une socket? Impossible de se connecter ou d'écrire des données sur un socket ou un fichier? Impossible d'analyser l'entrée? Cela pourrait être exceptionnel, peut-être pas. La fonction elle-même ne peut certainement pas généralement le savoir, elle n'a aucune idée dans quel type de contexte elle est appelée.

Alors, comment suis-je censé décider si je dois utiliser des exceptions ou non pour une fonction particulière? Il me semble que le seul moyen réellement cohérent est de les utiliser pour toutes les manipulations d'erreurs, ou pour rien. Et si j'utilise la bibliothèque standard, ce choix a été fait pour moi.

cooky451
la source
6
Vous devez lire attentivement cette entrée de la FAQ . Il ne s'applique qu'aux erreurs de codage, pas aux données non valides, au déréférencement d'un objet nul ou à tout ce qui a à voir avec la mauvaise exécution générale. En général, les affirmations visent à identifier des choses qui ne devraient jamais se produire. Pour tout le reste, il existe des exceptions, des codes d'erreur, etc.
Robert Harvey
1
@RobertHarvey cette définition a toujours le même problème - si quelque chose peut être résolu sans intervention humaine ou non n'est connu que des couches supérieures d'un programme.
cooky451
1
Vous êtes accroché aux aspects juridiques. Évaluez les avantages et les inconvénients et décidez-vous. De plus, le dernier paragraphe de votre question ... Je ne pense pas du tout que cela va de soi. Votre pensée est très noire et blanche, alors que la vérité est probablement plus proche de quelques nuances de gris.
Robert Harvey
4
Avez-vous tenté de faire des recherches avant de poser cette question? Les idiomes de gestion des erreurs C ++ sont presque certainement discutés en détail nauséabonds sur le Web. Une référence à une entrée de la FAQ n'est pas une bonne recherche. Après avoir fait vos recherches, vous devrez toujours vous décider. Ne me lancez pas sur la façon dont nos écoles de programmation créent apparemment des robots de codage de schémas de logiciels stupides qui ne savent pas penser par eux-mêmes.
Robert Harvey
2
Ce qui donne du crédit à ma théorie selon laquelle une telle règle peut ne pas exister réellement. J'ai invité des gens de The C ++ Lounge pour voir s'ils peuvent répondre à votre question, bien que chaque fois que j'y vais, leur conseil est "Arrêtez d'utiliser C ++, ça va vous faire frémir." Prenez donc leurs conseils à vos risques et périls.
Robert Harvey

Réponses:

15

Tout d'abord, je me sens obligé de souligner que std::exceptionet ses enfants ont été conçus il y a longtemps. Il existe un certain nombre de pièces qui seraient probablement (presque certainement) différentes si elles étaient conçues aujourd'hui.

Ne vous méprenez pas: il y a des parties de la conception qui ont assez bien fonctionné, et sont de très bons exemples de la façon de concevoir une hiérarchie d'exceptions pour C ++ (par exemple, le fait que, contrairement à la plupart des autres classes, ils partagent tous un racine commune).

En regardant spécifiquement logic_error, nous avons une énigme. D'une part, si vous avez un choix raisonnable en la matière, le conseil que vous avez cité est juste: il est généralement préférable d'échouer aussi rapidement et bruyamment que possible afin qu'il puisse être débogué et corrigé.

Pour le meilleur ou pour le pire, cependant, il est difficile de définir la bibliothèque standard autour de ce que vous devez généralement faire. S'il les définissait pour quitter le programme (par exemple, appeler abort()) lorsque des données incorrectes étaient fournies, ce serait toujours ce qui se passerait dans ces circonstances - et il y a en fait un certain nombre de circonstances dans lesquelles ce n'est probablement pas vraiment la bonne chose à faire , au moins dans le code déployé.

Cela s'appliquerait au code avec des exigences en temps réel (au moins douces) et une pénalité minimale pour une sortie incorrecte. Par exemple, envisagez un programme de chat. S'il décode des données vocales et obtient des entrées incorrectes, il est probable qu'un utilisateur sera beaucoup plus heureux de vivre avec une milliseconde de statique en sortie qu'un programme qui s'arrête complètement. De même, lors de la lecture vidéo, il peut être plus acceptable de vivre avec la production de valeurs erronées pour certains pixels pour une ou deux images que de quitter sommairement le programme car le flux d'entrée a été corrompu.

Quant à savoir s'il faut utiliser des exceptions pour signaler certains types d'erreurs: vous avez raison - la même opération peut être considérée comme une exception ou non, selon la façon dont elle est utilisée.

D'un autre côté, vous avez également tort - l'utilisation de la bibliothèque standard ne vous force pas (nécessairement) à prendre cette décision. Dans le cas de l'ouverture d'un fichier, vous utiliseriez normalement un iostream. Les Iostreams ne sont pas non plus exactement la dernière et la meilleure conception, mais dans ce cas, ils font les choses correctement: ils vous permettent de définir un mode d'erreur, afin que vous puissiez contrôler si l'échec de l'ouverture d'un fichier entraîne une exception ou non. Donc, si vous avez un fichier qui est vraiment nécessaire pour votre application, et si vous ne l'ouvrez pas, vous devez prendre des mesures correctives sérieuses, vous pouvez lui faire lever une exception s'il ne parvient pas à ouvrir ce fichier. Pour la plupart des fichiers que vous essayerez d'ouvrir, s'ils n'existent pas ou ne sont pas accessibles, ils échoueront (c'est la valeur par défaut).

Quant à la façon dont vous décidez: je ne pense pas qu'il y ait de réponse facile. Pour le meilleur ou pour le pire, les «circonstances exceptionnelles» ne sont pas toujours faciles à mesurer. Bien qu'il y ait certainement des cas faciles à décider qui doivent être [non] exceptionnels, il y a (et il y aura probablement toujours) des cas où cela peut être remis en question, ou nécessite une connaissance du contexte qui est en dehors du domaine de la fonction en question. Pour des cas comme celui-ci, il peut au moins être utile d'envisager une conception à peu près similaire à cette partie des iostreams, où l'utilisateur peut décider si l'échec entraîne la levée ou non d'une exception. Alternativement, il est tout à fait possible d'avoir deux ensembles distincts de fonctions (ou classes, etc.), dont l'un lèvera des exceptions pour indiquer l'échec, l'autre utilisant d'autres moyens. Si vous suivez cette voie,

Jerry Coffin
la source
9

Le constructeur basé sur une chaîne std :: bitset n'existe que depuis C ++ 11, il aurait donc dû être conçu avec une utilisation idiomatique des exceptions à l'esprit. D'un autre côté, j'ai entendu des gens me dire que logic_error ne devait fondamentalement pas être utilisé du tout.

Vous ne le croyez peut-être pas, mais différents codeurs C ++ ne sont pas d'accord. C'est pourquoi la FAQ dit une chose mais la bibliothèque standard n'est pas d'accord.

La FAQ préconise un plantage car cela sera plus facile à déboguer. Si vous vous plantez et obtenez un vidage de mémoire, vous aurez l'état exact de votre application. Si vous jetez une exception, vous perdrez beaucoup de cet état.

La bibliothèque standard prend la théorie selon laquelle donner au codeur la capacité d'attraper et de gérer éventuellement l'erreur est plus important que le débogage.

Cela pourrait être exceptionnel, peut-être pas. La fonction elle-même ne peut certainement pas généralement le savoir, elle n'a aucune idée dans quel type de contexte elle est appelée.

L'idée ici est que si votre fonction ne sait pas si la situation est exceptionnelle ou non, elle ne doit pas lever d'exception. Il devrait retourner un état d'erreur via un autre mécanisme. Une fois qu'il atteint un point dans le programme où il sait que l'état est exceptionnel, il doit lever l'exception.

Mais cela a son propre problème. Si un état d'erreur est renvoyé par une fonction, vous pourriez ne pas vous souvenir de le vérifier et l'erreur passera silencieusement. Cela amène certaines personnes à abandonner les exceptions qui sont une règle exceptionnelle en faveur de la levée d'exceptions pour tout type d'état d'erreur.

Dans l'ensemble, le point clé est que différentes personnes ont des idées différentes sur le moment de lever les exceptions. Vous n'allez pas trouver une seule idée cohérente. Même si certains voudront affirmer dogmatiquement que telle ou telle est la bonne façon de gérer les exceptions, il n'y a pas de théorie unique convenue.

Vous pouvez lever des exceptions:

  1. Jamais
  2. Partout
  3. Uniquement sur les erreurs de programmation
  4. Jamais sur les erreurs du programmeur
  5. Uniquement lors d'échecs non courants (exceptionnels)

et trouver quelqu'un sur Internet qui est d'accord avec vous. Vous devrez adopter le style qui vous convient.

Winston Ewert
la source
Il est peut-être intéressant de noter que la suggestion de n'utiliser les exceptions que lorsque les circonstances sont vraiment exceptionnelles a été largement encouragée par les personnes enseignant les langues où les exceptions ont de mauvaises performances. C ++ n'est pas l'un de ces langages.
Jules
1
@Jules - maintenant que (performance) mérite certainement une réponse personnelle où vous sauvegardez votre réclamation. La performance des exceptions C ++ est certainement un problème, peut-être plus, peut-être moins qu'ailleurs, mais déclarer que "C ++ n'est pas un de ces langages [où les exceptions ont de mauvaises performances]" est certainement discutable.
Martin Ba
1
@MartinBa - par rapport à, disons, Java, les performances d'exception C ++ sont plus rapides de plusieurs ordres de grandeur. Les références suggèrent que les performances de lever une exception de niveau 1 sont environ 50 fois plus lentes que la gestion d'une valeur de retour en C ++, contre plus de 1000 fois plus lentement en Java. Les conseils écrits pour Java dans ce cas ne doivent pas être appliqués au C ++ sans réflexion supplémentaire car il y a plus d'un ordre de grandeur de différence de performances entre les deux. J'aurais peut-être dû écrire «performances extrêmement médiocres» plutôt que «performances médiocres».
Jules
1
@Jules - merci pour ces chiffres. (toutes les sources?) Je peux les croire, car Java (et C #) doivent capturer la trace de la pile, ce qui semble certainement être très coûteux. Je pense toujours que votre réponse initiale est un peu trompeuse, car même un ralentissement de 50x est assez lourd, je pense, en particulier. dans un langage axé sur les performances comme C ++.
Martin Ba
2

Beaucoup d'autres bonnes réponses ont été écrites, je veux juste ajouter un petit point.

La réponse traditionnelle, en particulier lorsque la FAQ ISO C ++ a été écrite, compare principalement «l'exception C ++» et le «code retour de style C». Une troisième option, "renvoyer un certain type de valeur composite, par exemple un structou union, ou de nos jours, boost::variantou le (proposé) std::expected, n'est pas considérée.

Avant C ++ 11, l'option "renvoyer un type composite" était généralement très faible. Parce qu'il n'y avait pas de sémantique de mouvement, la copie de choses dans et hors d'une structure était potentiellement très coûteuse. À ce stade du langage, il était extrêmement important de styliser votre code vers RVO afin d'obtenir les meilleures performances. Les exceptions étaient comme un moyen facile de renvoyer efficacement un type composite, sinon ce serait assez difficile.

IMO, après C ++ 11, cette option "retourner une union discriminée", similaire à l'idiome Result<T, E>utilisé dans Rust de nos jours, devrait être privilégiée plus souvent dans le code C ++. Parfois, c'est vraiment un style plus simple et plus pratique pour indiquer les erreurs. À quelques exceptions près, il y a toujours cette possibilité que des fonctions qui n'ont pas été lancées avant puissent soudainement lancer après un refactor, et les programmeurs ne documentent pas toujours bien ce genre de choses. Lorsque l'erreur est indiquée comme faisant partie de la valeur de retour dans une union discriminée, cela réduit considérablement le risque que le programmeur ignore simplement le code d'erreur, ce qui est la critique habituelle de la gestion des erreurs de style C.

Fonctionne généralement un Result<T, E>peu comme boost facultatif. Vous pouvez tester, en utilisant operator bool, s'il s'agit d'une valeur ou d'une erreur. Et puis utilisez say operator *pour accéder à la valeur, ou une autre fonction "get". Habituellement, cet accès n'est pas contrôlé, pour la vitesse. Mais vous pouvez faire en sorte que dans une version de débogage, l'accès soit vérifié et une assertion s'assure qu'il existe réellement une valeur et non une erreur. De cette façon, toute personne qui ne vérifie pas correctement les erreurs obtiendra une affirmation ferme plutôt qu'un problème plus insidieux.

Un avantage supplémentaire est que, contrairement aux exceptions où s'il n'est pas capturé, il parcourt la pile à une distance arbitraire, avec ce style, lorsqu'une fonction commence à signaler une erreur là où elle ne l'était pas auparavant, vous ne pouvez pas compiler à moins que le le code est modifié pour le gérer. Cela rend les problèmes plus forts - le problème traditionnel de «l'exception non capturée» ressemble plus à une erreur de compilation qu'à une erreur d'exécution.

Je suis devenu un grand fan de ce style. Habituellement, j'utilise de nos jours ceci ou des exceptions. Mais j'essaie de limiter les exceptions aux problèmes majeurs. Pour quelque chose comme une erreur d'analyse, j'essaie de revenir expected<T>par exemple. Des choses comme std::stoiet boost::lexical_castqui lèvent une exception C ++ en cas de problème relativement mineur "la chaîne n'a pas pu être convertie en nombre" me semblent de très mauvais goût de nos jours.

Chris Beck
la source
1
std::expectedest toujours une proposition non acceptée, non?
Martin Ba
Vous avez raison, je suppose que ce n'est pas encore accepté. Mais il y a plusieurs implémentations open source qui flottent, et j'ai roulé la mienne plusieurs fois je suppose. C'est moins compliqué que de faire un type variant car il n'y a que deux états possibles. Les principales considérations de conception sont, quelle interface exacte voulez-vous et voulez-vous qu'elle soit comme le <T> attendu d'Andrescu où l'objet d'erreur est réellement censé être un exception_ptr, ou voulez-vous simplement utiliser un type de structure ou quelque chose comme ça.
Chris Beck
Le discours d'Andrei Alexandrescu est ici: channel9.msdn.com/Shows/Going+Deep/… Il montre en détail comment construire une classe comme celle-ci et quelles considérations vous pourriez avoir.
Chris Beck
La proposition [[nodiscard]] attributesera utile pour cette approche de gestion des erreurs car elle garantit que vous n'ignorez pas simplement le résultat de l'erreur par accident.
CodesInChaos
- ouais je connaissais le discours des AA. J'ai trouvé le design assez étrange car pour le déballer ( except_ptr), il fallait lever une exception en interne. Personnellement, je pense qu'un tel outil devrait fonctionner complètement indépendamment des exécutions. Juste une remarque.
Martin Ba
1

Il s'agit d'un problème hautement subjectif, car il fait partie de la conception. Et parce que le design est fondamentalement de l'art, je préfère discuter de ces choses au lieu de débattre (je ne dis pas que vous débattez).

Pour moi, les cas exceptionnels sont de deux types - ceux qui concernent les ressources et ceux qui traitent des opérations critiques. Ce qui peut être considéré comme critique dépend du problème à résoudre et, dans de nombreux cas, du point de vue du programmeur.

L'échec à acquérir des ressources est un candidat idéal pour lever des exceptions. La ressource peut être de la mémoire, un fichier, une connexion réseau ou tout autre élément en fonction de votre problème et de votre plate-forme. Maintenant, l'échec de la libération d'une ressource justifie-t-il une exception? Eh bien, cela dépend encore une fois. Je n'ai rien fait où la libération de mémoire a échoué, donc je ne suis pas sûr de ce scénario. Cependant, la suppression de fichiers dans le cadre de la libération des ressources peut échouer, et a échoué pour moi, et cet échec est généralement lié à un autre processus l'ayant maintenu ouvert dans une application multi-processus. Je suppose que d'autres ressources pourraient échouer lors de la publication comme un fichier pourrait le faire, et c'est généralement une faille de conception qui provoque ce problème, il serait donc préférable de le corriger que de lancer une exception.

Vient ensuite la mise à jour des ressources. Ce point est, du moins pour moi, étroitement lié à l'aspect opérationnel critique de l'application. Imaginez une Employeeclasse avec une fonction UpdateDetails(std::string&)qui modifie les détails en fonction de la chaîne séparée par des virgules donnée. Semblable à l'échec de la libération de la mémoire, j'ai du mal à imaginer l'échec de l'affectation des valeurs des variables membres en raison de mon manque d'expérience dans ces domaines où cela pourrait se produire. Cependant, une fonction comme celle UpdateDetailsAndUpdateFile(std::string&)qui fait comme son nom l'indique doit échouer. C'est ce que j'appelle une opération critique.

Maintenant, vous devez voir si l'opération dite critique mérite une exception. Je veux dire, la mise à jour du fichier se fait-elle à la fin, comme dans le destructeur, ou est-ce simplement un appel paranoïaque effectué après chaque mise à jour? Existe-t-il un mécanisme de secours qui écrit régulièrement des objets non écrits? Ce que je dis, c'est que vous devez évaluer la criticité de l'opération.

De toute évidence, il existe de nombreuses opérations critiques qui ne sont pas liées aux ressources. Si la UpdateDetails()donnée est incorrecte, elle ne mettra pas à jour les détails et l'échec doit être signalé, vous lèverez donc une exception ici. Mais imaginez une fonction comme GiveRaise(). Maintenant, si ledit employé a de la chance d'avoir un patron aux cheveux pointus et ne recevra pas d'augmentation (en termes de programmation, la valeur de certaines variables empêche cela de se produire), la fonction a essentiellement échoué. Souhaitez-vous lever une exception ici? Ce que je dis, c'est qu'il faut évaluer la nécessité d'une exception.

Pour moi, la cohérence est au niveau de mon approche du design plutôt que de la convivialité de mes cours. Ce que je veux dire, c'est que je ne pense pas en termes de «toutes les fonctions Get doivent le faire et toutes les fonctions de mise à jour doivent le faire», mais voyez si une fonction particulière fait appel à une certaine idée dans mon approche. À première vue, les cours pourraient avoir l'air d'une sorte de «hasard», mais chaque fois que les utilisateurs (principalement des collègues d'autres équipes) râleront ou poseront des questions à ce sujet, je m'expliquerai et ils semblent satisfaits.

Je vois beaucoup de gens qui remplacent essentiellement les valeurs de retour par des exceptions parce qu'ils utilisent C ++ et non C, et que cela donne une `` bonne séparation de la gestion des erreurs '', etc. et m'encourage à arrêter de `` mélanger '' les langues, etc. Je reste généralement à l'écart de ces gens.

vin
la source
1

Premièrement, comme d'autres l'ont déclaré, les choses ne sont pas aussi claires en C ++, à mon humble avis, principalement parce que les exigences et les contraintes sont un peu plus variées en C ++ que dans d'autres langages, en particulier. C # et Java, qui ont des problèmes d'exception "similaires".

Je vais exposer sur l'exemple std :: stof:

passer une chaîne vide à std :: stof (lancera invalid_argument) pas une erreur de programmation

Le contrat de base , comme je le vois, de cette fonction est qu'elle essaie de convertir son argument en un flottant, et tout échec à le faire est signalé par une exception. Les deux exceptions possibles sont dérivées, logic_errormais pas dans le sens d'erreur de programmeur, mais dans le sens de "l'entrée ne peut jamais être convertie en flottant".

Ici, on peut dire que a logic_errorest utilisé pour indiquer que, étant donné que l'entrée (runtime), c'est toujours une erreur d'essayer de la convertir - mais c'est le travail de la fonction de le déterminer et de vous le dire (via une exception).

Note latérale: Dans cette vue, un runtime_error pourrait être considéré comme quelque chose qui, étant donné la même entrée à une fonction, pourrait théoriquement réussir pour différentes exécutions. (par exemple, une opération sur fichier, accès DB, etc.)

Note supplémentaire: La bibliothèque d'expressions régulières C ++ a choisi de dériver son erreur runtime_errorbien qu'il existe des cas où elle pourrait être classée de la même manière qu'ici (modèle d'expression régulière non valide).

Cela montre juste, à mon humble avis, que le regroupement en logic_ou runtime_erreur est assez flou en C ++ et n'aide pas vraiment beaucoup dans le cas général (*) - si vous devez gérer des erreurs spécifiques, vous devez probablement rattraper plus bas que les deux.

(*): Cela ne veut pas dire qu'un seul morceau de code ne doit pas être cohérent, mais si vous jetez runtime_ou logic_ou custom_somethings est vraiment pas si important que cela, je pense.


Pour commenter les deux stofet bitset:

Les deux fonctions prennent des chaînes comme argument, et dans les deux cas, c'est:

  • non trivial pour vérifier si l'appelant est valide (par exemple, dans le pire des cas, vous devrez répliquer la logique de la fonction; dans le cas d'un jeu de bits, il n'est pas immédiatement clair si une chaîne vide est valide, alors laissez le ctor décider)
  • Il est déjà de la responsabilité de la fonction "d'analyser" la chaîne, elle doit donc déjà valider la chaîne, il est donc logique qu'elle signale une erreur pour "utiliser" la chaîne uniformément (et dans les deux cas, c'est une exception) .

la règle qui revient fréquemment avec les exceptions est "n'utiliser les exceptions que dans des circonstances exceptionnelles". Mais comment une fonction de bibliothèque est-elle censée savoir quelles circonstances sont exceptionnelles?

Cette déclaration a, à mon humble avis, deux racines:

Performances : si une fonction est appelée dans un chemin critique et que le cas "exceptionnel" n'est pas exceptionnel, c'est-à-dire qu'un nombre important de passes impliquera de lever une exception, alors payer à chaque fois pour la machine de déroulement d'exception n'a pas de sens et peut être trop lent.

Localité de la gestion des erreurs : si une fonction est invoquée et que l'exception est immédiatement interceptée et traitée, il est inutile de lancer une exception, car la gestion des erreurs sera plus détaillée avec le catchqu'avec avec if.

Exemple:

float readOrDefault;
try {
  readOrDefault = stof(...);
} catch(std::exception&) {
  // discard execption, just use default value
  readOrDefault = 3.14f; // 3.14 is the default value if cannot be read
}

Voici où des fonctions comme TryParsevs Parseentrent en jeu: une version pour quand le code local attend que la chaîne analysée soit valide, une version lorsque le code local suppose qu'il est réellement prévu (c'est-à-dire non exceptionnel) que l'analyse échoue.

En effet, stofest juste (défini comme) un wrapper strtof, donc si vous ne voulez pas d'exceptions, utilisez-le.


Alors, comment suis-je censé décider si je dois utiliser des exceptions ou non pour une fonction particulière?

À mon humble avis, vous avez deux cas:

  • Fonction de type "bibliothèque" (réutilisée souvent dans des contextes différents): vous ne pouvez pas décider. Fournissez éventuellement les deux versions, peut-être une qui signale une erreur et une encapsuleuse qui convertit l'erreur renvoyée en exception.

  • Fonction "Application" (spécifique pour un blob de code d'application, peut être réutilisée, mais est limitée par le style de gestion des erreurs des applications, etc.): Ici, elle devrait souvent être assez claire. Si le (s) chemin (s) de code appelant les fonctions gèrent les exceptions de manière saine et utile, utilisez les exceptions pour signaler toute erreur (mais voir ci-dessous) . Si le code d'application est plus facile à lire et à écrire pour un style de retour d'erreur, utilisez-le par tous les moyens.

Bien sûr, il y aura des endroits entre les deux - utilisez simplement ce dont vous avez besoin et souvenez-vous de YAGNI.


Enfin, je pense que je devrais revenir à la déclaration FAQ,

N'utilisez pas throw pour indiquer une erreur de codage lors de l'utilisation d'une fonction. Utilisez assert ou un autre mécanisme pour envoyer le processus dans un débogueur ou pour bloquer le processus ...

Je souscris à cela pour toutes les erreurs qui indiquent clairement que quelque chose est gravement gâché ou que le code appelant ne savait clairement pas ce qu'il faisait.

Mais lorsque cela est approprié est souvent très spécifique à l'application, donc voir ci-dessus domaine de bibliothèque vs domaine d'application.

Cela revient à la question de savoir si et comment valider les conditions préalables à l' appel , mais je n'entrerai pas dans les détails, répondez déjà trop longtemps :-)

Martin Ba
la source