Vers la fin des années 90, j'ai beaucoup travaillé avec une base de code qui utilisait des exceptions comme contrôle de flux. Il a mis en œuvre une machine à états finis pour gérer les applications de téléphonie. Dernièrement, je me souviens de cette époque parce que je développais des applications Web MVC.
Ils ont tous les deux Controller
le choix de la prochaine étape et fournissent les données à la logique de destination. Les actions des utilisateurs d'un téléphone de la vieille école, telles que les tonalités DTMF, sont devenues des paramètres pour les méthodes d'action, mais au lieu de renvoyer quelque chose comme un ViewResult
, elles ont jeté un StateTransitionException
.
Je pense que la principale différence était que les méthodes d'action étaient des void
fonctions. Je ne me souviens pas de tout ce que j'ai fait avec ce fait, mais j'ai hésité à ne pas trop m'en souvenir, car depuis ce travail, comme il y a 15 ans, je ne l'avais jamais vu dans le code de production . J'ai supposé que c'était un signe qu'il s'agissait d'un prétendu anti-modèle.
Est-ce le cas, et si oui, pourquoi?
Mise à jour: lorsque j'ai posé la question, j'avais déjà la réponse de @ MasonWheeler en tête, alors je suis allée avec la réponse qui a le plus enrichi mes connaissances. Je pense que sa réponse est également valable.
la source
Réponses:
Il y a une discussion détaillée à ce sujet sur le Wiki de Ward . En règle générale, l'utilisation des exceptions pour le flux de contrôle est un anti-modèle, avec de nombreuses situations notable - et le langage spécifique (voir par exemple Python ) toux exceptions toux .
Voici un résumé rapide des raisons pour lesquelles c'est généralement un anti-modèle:
Lisez la discussion sur le wiki de Ward pour des informations plus détaillées.
Voir aussi un duplicata de cette question, ici
la source
if
etforeach
sont aussi desGOTO
s sophistiqués . Franchement, je pense que la comparaison avec goto n'est pas utile; c'est presque comme un terme péjoratif. L'utilisation de GOTO n'est pas intrinsèquement mauvaise - elle pose de vrais problèmes et les exceptions peuvent les partager, mais pas nécessairement. Il serait plus utile d’entendre ces problèmes.Le cas d’utilisation pour lequel les exceptions ont été conçues est «Je viens de rencontrer une situation que je ne peux pas traiter correctement à ce stade, car je n’ai pas assez de contexte pour le gérer, mais la routine qui m’a appelé (ou quelque chose de plus avancé dans l’appel). pile) devrait savoir comment s'y prendre. "
Le cas d'utilisation secondaire est "Je viens de rencontrer une grave erreur, et pour le moment, sortir de ce flux de contrôle pour éviter la corruption des données ou d'autres dommages est plus important que d'essayer de continuer."
Si vous n'utilisez pas d'exceptions pour l'une de ces deux raisons, il existe probablement une meilleure façon de le faire.
la source
const myvar = theFunction
car myvar doit être créé à partir d'essais-captures, ainsi ce n'est plus constant. Cela ne veut pas dire que je ne l'utilise pas en C #, car il s'agit d'un courant dominant pour une raison quelconque, mais j'essaie de le réduire quand même.Les exceptions sont aussi puissantes que les Continuations et
GOTO
. Ils sont une construction de flux de contrôle universel.Dans certaines langues, ils constituent la seule construction de flux de contrôle universelle. JavaScript, par exemple, n'a ni Continuations ni
GOTO
même appels appropriés. Ainsi, si vous souhaitez implémenter un flux de contrôle sophistiqué en JavaScript, vous devez utiliser des exceptions.Le projet Microsoft Volta était un projet de recherche (maintenant abandonné) visant à compiler du code .NET arbitraire en JavaScript. .NET a des exceptions dont la sémantique ne correspond pas exactement à JavaScript, mais plus important encore, il a des threads et vous devez les mapper en quelque sorte à JavaScript. Pour ce faire, Volta a mis en œuvre des suites Volta à l'aide d'exceptions JavaScript, puis toutes les structures de flux de contrôle .NET en termes de suites Volta. Ils ont dû utiliser Exceptions comme flux de contrôle, car il n'y a pas d'autre construction de flux de contrôle assez puissante.
Vous avez mentionné State Machines. Les SM sont simples à implémenter avec les appels de queue appropriés: chaque état est un sous-programme, chaque transition d'état est un appel de sous-programme. Les MS peuvent également être facilement implémentés avec
GOTO
ou Coroutines ou Continuations. Cependant, Java ne dispose pas de ces quatre, mais il ne possède des exceptions. Il est donc parfaitement acceptable de les utiliser comme flux de contrôle. (En fait, le bon choix serait probablement d'utiliser un langage avec la structure de flux de contrôle appropriée, mais vous pouvez parfois être bloqué avec Java.)la source
Comme d'autres l'ont mentionné à maintes reprises ( par exemple dans cette question de dépassement de pile ), le principe du moindre étonnement interdira que vous utilisiez des exceptions de manière excessive à des fins de contrôle de flux uniquement. D'un autre côté, aucune règle n'est exacte à 100%, et il existe toujours des cas dans lesquels une exception est "juste le bon outil" - un peu comme
goto
elle, d'ailleurs, livrée sous formebreak
etcontinue
dans des langages comme Java, qui sont souvent le moyen idéal pour sortir de boucles fortement imbriquées, qui ne sont pas toujours évitables.Le billet de blog suivant explique un cas d'utilisation assez complexe mais également intéressant pour un non-local
ControlFlowException
:Il explique comment, à l’intérieur de jOOQ (une bibliothèque d’abstractions SQL pour Java) (disclaimer: je travaille pour le fournisseur), de telles exceptions sont parfois utilisées pour abandonner le processus de rendu SQL plus tôt lorsque certaines conditions "rares" sont remplies.
Des exemples de telles conditions sont:
Trop de valeurs de liaison sont rencontrées. Certaines bases de données ne prennent pas en charge les nombres arbitraires de valeurs de liaison dans leurs instructions SQL (SQLite: 999, Ingres 10.1.0: 1024, Sybase ASE 15.5: 2000, SQL Server 2008: 2100). Dans ces cas, jOOQ abandonne la phase de rendu SQL et rend à nouveau l'instruction SQL avec des valeurs de liaison intégrées. Exemple:
Si nous extrayions explicitement les valeurs de liaison de la requête AST pour les compter à chaque fois, nous gaspillerions des cycles de processeur précieux pour ces 99,9% des requêtes qui ne souffrent pas de ce problème.
Certaines logiques ne sont disponibles qu'indirectement via une API que nous voulons exécuter seulement "partiellement". La
UpdatableRecord.store()
méthode génère une instructionINSERT
ouUPDATE
, en fonctionRecord
des indicateurs internes de. De "l'extérieur", nous ne savons pas quel type de logique est contenu dansstore()
(par exemple, le verrouillage optimiste, la gestion d'écouteurs d'événements, etc.), nous ne voulons donc pas répéter cette logique lorsque nous stockons plusieurs enregistrements dans une instruction batch. où nous aimerions avoirstore()
seulement générer l’instruction SQL, pas réellement l’exécuter. Exemple:Si nous avions externalisé la
store()
logique en une API "réutilisable" pouvant être personnalisée pour éventuellement ne pas exécuter le code SQL, nous envisagerions de créer une API plutôt difficile à maintenir, à peine réutilisable.Conclusion
Essentiellement, notre utilisation de ces messages non locaux
goto
va dans le sens de ce que Mason Wheeler a dit dans sa réponse:Les deux utilisations de
ControlFlowExceptions
étaient plutôt faciles à mettre en œuvre par rapport à leurs alternatives, ce qui nous permettait de réutiliser une large gamme de logique sans la refactoriser à partir des éléments internes pertinents.Mais le sentiment d'être un peu surprenant pour les futurs responsables reste. Le code est plutôt délicat et, même s’il s’agissait du bon choix dans ce cas, nous préférerions toujours ne pas utiliser d’exceptions pour les flux de contrôle locaux , où il est facile d’éviter l’utilisation de ramifications ordinaires
if - else
.la source
L'utilisation d'exceptions pour le contrôle de flux est généralement considérée comme un anti-modèle, mais il existe des exceptions (sans jeu de mots).
On a dit mille fois que les exceptions sont destinées à des conditions exceptionnelles. Une connexion de base de données rompue est une condition exceptionnelle. Un utilisateur qui entre des lettres dans un champ de saisie qui ne devrait autoriser que des chiffres ne l’est pas .
Un bogue dans votre logiciel qui provoque l’appel d’une fonction avec des arguments illégaux, par exemple,
null
lorsque non autorisé, est une condition exceptionnelle.En utilisant des exceptions pour quelque chose qui n'est pas exceptionnel, vous utilisez des abstractions inappropriées pour le problème que vous essayez de résoudre.
Mais il peut aussi y avoir une pénalité de performance. Certaines langues ont une implémentation plus ou moins efficace de la gestion des exceptions. Par conséquent, si votre langue de choix ne permet pas une gestion efficace des exceptions, elle peut être très coûteuse en termes de performances *.
Mais d'autres langages, par exemple Ruby, ont une syntaxe semblable à une exception pour le flux de contrôle. Les situations exceptionnelles sont gérées par les opérateurs
raise
/rescue
. Mais vous pouvez utiliserthrow
/catch
pour des constructions de flux de contrôle semblables à des exceptions **.Ainsi, bien que les exceptions ne soient généralement pas utilisées pour le contrôle de flux, votre langue de choix peut avoir d'autres idiomes.
* En exemple d'une utilisation coûteuse d'exceptions coûteuse: J'étais auparavant configuré pour optimiser une application ASP.NET Web Form peu performante. Il s’est avéré que le rendu d’une grande table demandait
int.Parse()
env. un millier de chaînes vides sur une page moyenne, ce qui donne env. mille exceptions sont traitées. En remplaçant le code parint.TryParse()
je me suis rasé une seconde! Pour chaque demande de page!** Cela peut être très déroutant pour un programmeur venant d’autres langues
throw
et quicatch
sont des mots-clés associés à des exceptions dans de nombreuses autres langues.la source
Il est tout à fait possible de gérer les conditions d'erreur sans utiliser d'exceptions. Certaines langues, notamment le C, ne comportent même pas d'exception et les utilisateurs parviennent toujours à créer des applications assez complexes avec. Les exceptions pour lesquelles les exceptions sont utiles sont qu'elles vous permettent de spécifier succinctement deux flux de contrôle essentiellement indépendants dans le même code: un si une erreur se produit et un autre si ce n'est pas le cas. Sans eux, vous vous retrouvez avec un code qui ressemble à ceci:
Ou l'équivalent dans votre langue, par exemple renvoyer un tuple avec une valeur comme statut d'erreur, etc. Souvent, les personnes qui indiquent à quel point la gestion des exceptions est "coûteuse" négligent toutes les
if
déclarations supplémentaires , comme ci-dessus, que vous devez ajouter si vous ne le faites pas. t utiliser des exceptions.Bien que cette tendance se produise le plus souvent lorsque des erreurs ou d’autres "conditions exceptionnelles" sont gérées, à mon avis, si vous commencez à voir un code standard semblable à celui-ci dans d’autres circonstances, vous disposez d’un très bon argument pour utiliser des exceptions. Selon la situation et l'implémentation, je peux voir que des exceptions sont utilisées correctement dans une machine à états, car vous avez deux flux de contrôle orthogonaux: un qui modifie l'état et l'autre pour les événements qui se produisent dans les états.
Cependant, ces situations sont rares et si vous faites une exception (jeu de mots) à la règle, vous feriez mieux de vous préparer à montrer sa supériorité par rapport à d'autres solutions. Une déviation sans cette justification s'appelle à juste titre un anti-modèle.
la source
En Python, les exceptions sont utilisées pour le générateur et la fin de l'itération. Python a des blocs try / except très efficaces, mais le fait de lever une exception a toutefois un coût supplémentaire.
En raison du manque de sauts multi-niveaux ou d'une instruction goto en Python, j'ai parfois utilisé des exceptions:
la source
return
à la place degoto
.try:
blocs à 1 ou 2 lignes s'il vous plaît, jamaistry: # Do lots of stuff
.Esquissons un tel usage de l'exception:
L'algorithme cherche récursivement jusqu'à ce que quelque chose soit trouvé. Donc, pour revenir de la récursivité, il faut vérifier le résultat et le retourner, sinon, continuez. Et cela revient à plusieurs reprises d'une certaine profondeur de récursivité.
En plus d'avoir besoin d'un booléen supplémentaire
found
(pour être emballé dans une classe, sinon, seul un int aurait été renvoyé), et pour la profondeur de récursion, le même postlude se produit.Un tel dénouement d’une pile d’appels n’est qu’une exception. Il me semble donc qu’il s’agit d’un moyen de codage non similaire à celui du goto, plus immédiat et plus approprié. Pas nécessaire, usage rare, peut-être un mauvais style, mais au point. Comparable à l'opération de coupe Prolog.
la source
La programmation concerne le travail
Je pense que la meilleure façon de répondre à cette question est de comprendre les progrès réalisés par OOP au fil des ans. Tout ce qui est fait dans la POO (et la plupart des paradigmes de programmation, d'ailleurs) est modelé sur le besoin de travail .
Chaque fois qu'une méthode est appelée, l'appelant dit "Je ne sais pas comment faire ce travail, mais vous savez comment, alors vous le faites pour moi".
Cela présentait une difficulté: que se passe-t-il lorsque la méthode appelée sait généralement comment faire le travail, mais pas toujours? Nous avions besoin d'un moyen de communiquer "Je voulais vous aider, je l'ai vraiment fait, mais je ne peux pas le faire."
Une des premières méthodes utilisées pour communiquer cela consistait simplement à renvoyer une valeur "déchet". Peut-être que vous vous attendez à un entier positif, la méthode appelée renvoie donc un nombre négatif. Pour ce faire, vous pouvez également définir une valeur d'erreur quelque part. Malheureusement, les deux méthodes ont abouti à ce que le code caché soit complet . Au fur et à mesure que les choses se compliquent, ce système s'effondre.
Une analogie exceptionnelle
Disons que vous avez un charpentier, un plombier et un électricien. Vous voulez que le plombier répare votre évier, alors il l'examine. Ce n'est pas très utile s'il vous dit seulement: "Désolé, je ne peux pas le réparer. C'est cassé." Enfer, c'est encore pire s'il jetait un coup d'œil, partait et vous envoyait une lettre vous disant qu'il ne pouvait pas le réparer. Maintenant, vous devez vérifier votre courrier avant même de savoir qu'il n'a pas fait ce que vous vouliez.
Ce que vous préférez, c'est qu'il vous le dise: "Ecoute, je ne pourrais pas régler le problème, car il me semble que ta pompe ne fonctionne pas."
Avec ces informations, vous pouvez en conclure que vous souhaitez que l’électricien examine le problème. L'électricien trouvera peut-être un élément lié au menuisier et vous devrez le faire réparer par le menuisier.
Heck, vous ne savez peut-être même pas que vous avez besoin d'un électricien, vous ne savez peut-être pas de qui vous avez besoin. Vous êtes juste un cadre dans une entreprise de réparation de maison et votre objectif est la plomberie. Vous dites donc que vous êtes le chef du problème, puis il demande à l'électricien de le régler.
C’est ce que les exceptions modélisent: les modes de défaillance complexes de manière découplée. Le plombier n'a pas besoin de savoir à propos d'électricien - il n'a même pas besoin de savoir que quelqu'un en amont peut résoudre le problème. Il ne fait que rendre compte du problème rencontré.
Alors ... un anti-motif?
Ok, la première étape consiste donc à comprendre le point des exceptions. La prochaine consiste à comprendre ce qu'est un anti-modèle.
Pour être qualifié d'anti-pattern, il faut
Le premier point est facilement satisfait - le système a fonctionné, non?
Le deuxième point est plus collant. La principale raison d'utiliser des exceptions en tant que flux de contrôle normal est mauvaise, car ce n'est pas leur objectif. Toute fonctionnalité donnée dans un programme doit avoir un objectif relativement clair, et la cooptation de cet objectif crée une confusion inutile.
Mais ce n'est pas un préjudice définitif . C'est une mauvaise façon de faire les choses, et bizarre, mais un anti-modèle? Non, juste ... étrange.
la source