204 votes

Pourquoi ne pas utiliser les exceptions comme flux de contrôle régulier ?

Pour éviter toutes les réponses standard que j'aurais pu trouver sur Google, je vais fournir un exemple que vous pouvez tous attaquer à volonté.

C# et Java (et trop d'autres) ont, avec de nombreux types, un comportement de "débordement" que je n'aime pas du tout (par ex. type.MaxValue + type.SmallestValue == type.MinValue par exemple : int.MaxValue + 1 == int.MinValue ).

Mais, vu ma nature vicieuse, je vais ajouter une insulte à cette blessure en étendant ce comportement à, disons, un Overridden DateTime type. (Je sais que DateTime est scellé dans .NET, mais pour les besoins de cet exemple, j'utilise un pseudo-langage qui est exactement comme C#, à l'exception du fait que DateTime n'est pas scellé).

La surcharge Add méthode :

/// <summary>
/// Increments this date with a timespan, but loops when
/// the maximum value for datetime is exceeded.
/// </summary>
/// <param name="ts">The timespan to (try to) add</param>
/// <returns>The Date, incremented with the given timespan. 
/// If DateTime.MaxValue is exceeded, the sum wil 'overflow' and 
/// continue from DateTime.MinValue. 
/// </returns>
public DateTime override Add(TimeSpan ts) 
{
    try
    {                
        return base.Add(ts);
    }
    catch (ArgumentOutOfRangeException nb)
    {
        // calculate how much the MaxValue is exceeded
        // regular program flow
        TimeSpan saldo = ts - (base.MaxValue - this);
        return DateTime.MinValue.Add(saldo)                         
    }
    catch(Exception anyOther) 
    {
        // 'real' exception handling.
    }
}

Bien sûr, un "if" pourrait résoudre ce problème tout aussi facilement, mais il n'en reste pas moins que je ne vois pas pourquoi on ne pourrait pas utiliser les exceptions (d'un point de vue logique, je peux comprendre que, lorsque les performances sont un problème, les exceptions doivent être évitées dans certains cas).

Je pense que dans de nombreux cas, ils sont plus clairs que les structures if et ne rompent aucun contrat de la méthode.

IMHO la réaction "Ne jamais les utiliser pour le flux de programme régulier" que tout le monde semble avoir n'est pas aussi bien construite que la force de cette réaction peut le justifier.

Ou est-ce que je me trompe ?

J'ai lu d'autres articles, traitant de toutes sortes de cas particuliers, mais ce que je veux dire, c'est qu'il n'y a rien de mal à cela si vous êtes les deux :

  1. Clair
  2. Honorer le contrat de votre méthode

Tirez sur moi.

3 votes

+1 Je ressens la même chose. Outre les performances, la seule bonne raison d'éviter les exceptions pour le flux de contrôle est lorsque le code de l'appelant sera beaucoup plus lisible avec des valeurs de retour.

4 votes

Est-ce que le : retour -1 si quelque chose est arrivé, retour -2 si autre chose, etc... est vraiment plus lisible que les exceptions ?

1 votes

Il est triste que l'on ait une réputation négative pour avoir dit la vérité : que votre exemple n'aurait pas pu être écrit avec des instructions si. (Cela ne veut pas dire qu'il est correct/complet).

182voto

Brann Points 9983

Avez-vous déjà essayé de déboguer un programme qui lève cinq exceptions par seconde dans le cours normal de ses opérations ?

Je l'ai fait.

Le programme était assez complexe (il s'agissait d'un serveur de calcul distribué), et une légère modification à un endroit du programme pouvait facilement casser quelque chose à un endroit totalement différent.

J'aurais aimé pouvoir simplement lancer le programme et attendre que les exceptions se produisent, mais il y a eu environ 200 exceptions pendant le démarrage dans le cours normal des activités

Mon avis : si vous utilisez des exceptions pour les situations normales, comment repérer les situations inhabituelles (c'est-à-dire exception al) ?

Bien sûr, il y a d'autres bonnes raisons de ne pas trop utiliser les exceptions, notamment en termes de performances.

1 votes

Si vous utilisez des exceptions pour les situations normales, comment repérer les situations inhabituelles (c'est-à-dire exceptionnelles) ? --> Comme dans l'exemple, rien ne vous empêche d'attraper les exceptions normales.

14 votes

Exemple : lorsque je débogue un programme .net, je le lance à partir de Visual Studio et je demande à VS de s'arrêter sur toutes les exceptions. Si vous comptez sur les exceptions comme un comportement attendu, je ne peux plus le faire (car il se casserait 5 fois/sec), et il est beaucoup plus compliqué de localiser la partie problématique du code.

2 votes

/Je suis d'accord. J'ai été là... je suis toujours là. Et le type qui a fait que FileNotFound soit lancé en interne pour chaque recherche de ressource dans CF doit être abattu.

166voto

Anton Gogolev Points 59794

Les exceptions sont essentiellement non locales goto avec toutes les conséquences de ces dernières. L'utilisation d'exceptions pour le contrôle de flux viole une principe du moindre étonnement les programmes sont difficiles à lire (n'oubliez pas que les programmes sont d'abord écrits pour les programmeurs).

De plus, ce n'est pas ce que les vendeurs de compilateurs attendent. Ils s'attendent à ce que les exceptions soient rarement levées, et ils laissent généralement l'option throw serait plutôt inefficace. Lancer des exceptions est l'une des opérations les plus coûteuses en .NET.

Cependant, certains langages (notamment Python) utilisent les exceptions comme des constructions de contrôle de flux. Par exemple, les itérateurs lèvent une StopIteration exception s'il n'y a pas d'autres éléments. Même les constructions linguistiques standard (telles que for ) s'appuient sur cela.

12 votes

Hey, les exceptions ne sont pas étonnantes ! Et tu te contredis un peu quand tu dis "c'est une mauvaise idée" et que tu dis ensuite "mais c'est une bonne idée en python".

6 votes

Je ne suis toujours pas convaincu : 1) L'efficacité n'était pas la question, beaucoup de programmes non-bacht s'en moquent (par exemple l'interface utilisateur) 2) Étonnant : comme je l'ai dit, c'est juste étonnant parce que ce n'est pas utilisé, mais la question reste : pourquoi ne pas utiliser id en premier lieu ? Mais, puisque c'est la réponse

2 votes

Si je me souviens bien, l'instruction itérative for en Java (for (foo : bar)) s'appuie également sur les exceptions pour s'interrompre. Il n'y avait pas de garantie que hasNext() soit appelé et il me semble qu'il ne le sera pas.

33voto

cwap Points 6098

Ma règle d'or est la suivante :

  • Si vous pouvez faire quelque chose pour récupérer d'une erreur, attrapez les exceptions.
  • Si l'erreur est très courante (par exemple, l'utilisateur a essayé de se connecter avec un mauvais mot de passe), utilisez les valeurs de retour.
  • Si vous ne pouvez rien faire pour récupérer une erreur, ne l'attrapez pas (ou attrapez-la dans votre attrapeuse principale pour effectuer un arrêt semi-gracieux de l'application).

Le problème que je vois avec les exceptions est d'un point de vue purement syntaxique (je suis presque sûr que la surcharge de performance est minime). Je n'aime pas avoir des blocs d'essai partout.

Prenez cet exemple :

try
{
   DoSomeMethod();  //Can throw Exception1
   DoSomeOtherMethod();  //Can throw Exception1 and Exception2
}
catch(Exception1)
{
   //Okay something messed up, but is it SomeMethod or SomeOtherMethod?
}

.. Un autre exemple pourrait être lorsque vous avez besoin d'assigner quelque chose à un handle en utilisant une factory, et que cette factory pourrait lancer une exception :

Class1 myInstance;
try
{
   myInstance = Class1Factory.Build();
}
catch(SomeException)
{
   // Couldn't instantiate class, do something else..
}
myInstance.BestMethodEver();   // Will throw a compile-time error, saying that myInstance is uninitalized, which it potentially is.. :(

Donc, personnellement, je pense que vous devriez garder les exceptions pour les conditions d'erreur rares (manque de mémoire, etc.) et utiliser les valeurs de retour (classes de valeurs, structs ou enums) pour vérifier les erreurs.

J'espère avoir bien compris votre question :)

4 votes

Re : Votre deuxième exemple - pourquoi ne pas simplement mettre l'appel à BestMethodEver à l'intérieur du bloc try, après Build ? Si Build() lève une exception, il ne sera pas exécuté, et le compilateur sera content.

3 votes

Oui, c'est probablement ce que vous obtiendrez, mais considérez un exemple plus complexe où le type myInstance lui-même peut lancer des exceptions Et d'autres isntances dans la portée de la méthode le peuvent aussi. Vous vous retrouverez avec beaucoup de blocs try/catch imbriqués :(

0 votes

Vous devez effectuer la traduction des exceptions (vers un type d'exception approprié au niveau d'abstraction) dans votre bloc catch. Pour votre information : "Multi-catch" est censé être intégré à Java 7.

24voto

Peter Points 14647

Une première réaction à de nombreuses réponses :

vous écrivez pour les programmeurs et le principe du moindre étonnement.

Bien sûr ! Mais un si n'est pas toujours plus clair.

Ça ne devrait pas être étonnant ex : divide (1/x) catch (divisionByZero) est plus clair que n'importe quel if pour moi (chez Conrad et d'autres) . Le fait que ce type de programmation ne soit pas attendu est purement conventionnel, et en fait, toujours pertinent. Peut-être que dans mon exemple, un if serait plus clair.

Mais DivisionByZero et FileNotFound d'ailleurs sont plus clairs que des si.

Bien sûr, si c'est moins performant et nécessaire un million de fois par seconde, vous devriez bien sûr l'éviter, mais je n'ai pas lu de bonne raison d'éviter la conception générale.

En ce qui concerne le principe du moindre étonnement : il y a un risque de raisonnement circulaire : si toute une communauté utilise un mauvais design, ce design deviendra attendu ! Par conséquent, ce principe ne peut être un graal et doit être considéré avec attention.

des exceptions pour les situations normales, comment repérer les situations inhabituelles (c'est-à-dire exceptionnelles) ?

Dans de nombreuses réactions, qqch. comme ça brille à travers. Il suffit de les attraper, non ? Votre méthode doit être claire, bien documentée, et faire honneur à son contrat. Je ne comprends pas cette question, je dois l'admettre.

Déboguer sur toutes les exceptions : c'est la même chose, c'est juste fait parfois parce que la conception de ne pas utiliser les exceptions est commune. Ma question était : pourquoi est-ce commun en premier lieu ?

1 votes

1) Vérifiez-vous toujours x avant d'appeler 1/x ? 2) Enveloppez-vous chaque opération de division dans un bloc try-catch pour attraper DivideByZeroException ? 3) Quelle logique mettez-vous dans le bloc de capture pour récupérer de DivideByZeroException ?

1 votes

Sauf que DivisionByZero et FileNotFound sont de mauvais exemples car ce sont des cas exceptionnels qui doivent être traités comme des exceptions.

0 votes

Il n'y a rien de "trop exceptionnel" à propos d'un fichier non trouvé de la manière que les personnes ici présentes vantent comme "anti-exception". openConfigFile(); peut être suivi d'un FileNotFound attrapé avec { createDefaultConfigFile(); setFirstAppRun(); } L'exception FileNotFound est gérée avec élégance ; pas de plantage, faisons en sorte que l'expérience de l'utilisateur final soit meilleure, et non pire. Vous pouvez dire "Mais si ce n'était pas vraiment la première exécution et qu'ils obtiennent cela à chaque fois ?". Au moins, l'application fonctionne à chaque fois et ne plante pas à chaque démarrage ! Sur une échelle de 1 à 10 "c'est terrible" : "première exécution" à chaque démarrage = 3 ou 4, crash chaque démarrage = 10.

17voto

necromancer Points 4120

Avant les exceptions, en C, il y avait setjmp et longjmp qui pourrait être utilisé pour accomplir un déroulement similaire de la trame de la pile.

Ensuite, la même construction a reçu un nom : "Exception". Et la plupart des réponses s'appuient sur la signification de ce nom pour argumenter sur son utilisation, affirmant que les exceptions sont destinées à être utilisées dans des conditions exceptionnelles. Cela n'a jamais été l'intention de la version originale longjmp . Il y avait juste des situations où vous deviez rompre le flux de contrôle à travers de nombreuses trames de pile.

Les exceptions sont légèrement plus générales dans la mesure où vous pouvez les utiliser dans le même cadre de pile également. Cela soulève des analogies avec goto qui, selon moi, sont erronées. Les Gotos sont une paire étroitement couplée (ainsi que les setjmp et longjmp ). Les exceptions suivent une méthode de publication/abonnement à couplage lâche qui est beaucoup plus propre ! Par conséquent, les utiliser dans le même cadre de pile est à peine la même chose que d'utiliser goto s.

La troisième source de confusion concerne le fait de savoir s'il s'agit d'exceptions vérifiées ou non vérifiées. Bien sûr, les exceptions non vérifiées semblent particulièrement horribles à utiliser pour le flux de contrôle et peut-être beaucoup d'autres choses.

Les exceptions vérifiées sont cependant excellentes pour le flux de contrôle, une fois que vous avez surmonté tous les problèmes victoriens et vécu un peu.

Mon utilisation préférée est une séquence de throw new Success() dans un long fragment de code qui essaie une chose après l'autre jusqu'à ce qu'il trouve ce qu'il cherche. Chaque chose -- chaque morceau de logique -- peut avoir une imbrication arbitraire de sorte que break ainsi que tout type de test de condition. Le site if-else Le modèle est fragile. Si je supprime un else ou de gâcher la syntaxe d'une autre manière, alors il y a un bug poilu.

Utilisation de throw new Success() linéarise le flux de code. J'utilise des définitions locales Success vérifiée bien sûr, de sorte que si j'oublie de l'attraper, le code ne sera pas compilé. Et je n'attrape pas l'erreur d'une autre méthode Success es.

Parfois, mon code vérifie une chose après l'autre et ne réussit que si tout est OK. Dans ce cas, j'ai une linéarisation similaire en utilisant throw new Failure() .

L'utilisation d'une fonction distincte perturbe le niveau naturel de compartimentage. Ainsi, la return n'est pas optimale. Je préfère avoir une page ou deux de code en un seul endroit pour des raisons cognitives. Je ne crois pas à la division ultrafine du code.

Ce que font les JVM ou les compilateurs est moins important pour moi, sauf s'il y a un point chaud. Je ne peux pas croire qu'il y ait une raison fondamentale pour que les compilateurs ne détectent pas les exceptions lancées et capturées localement et les traitent simplement comme des exceptions très efficaces. goto au niveau du code machine.

Pour ce qui est de leur utilisation entre les fonctions pour le flux de contrôle - c'est-à-dire pour les cas courants plutôt qu'exceptionnels - je ne vois pas en quoi ils seraient moins efficaces que de multiples ruptures, tests de condition, retours pour parcourir trois cadres de pile au lieu de simplement restaurer le pointeur de pile.

Personnellement, je n'utilise pas le motif sur des cadres de pile et je peux voir comment il faudrait une certaine sophistication du design pour le faire de manière élégante. Mais utilisé avec parcimonie, il devrait être parfait.

Enfin, pour ce qui est de surprendre les programmeurs vierges, ce n'est pas une raison convaincante. Si vous les initiez doucement à cette pratique, ils apprendront à l'aimer. Je me souviens que le C++ avait l'habitude de surprendre et d'effrayer les programmeurs C.

3 votes

En utilisant ce modèle, la plupart de mes fonctions grossières ont deux petites prises à la fin - une pour le succès et une pour l'échec - et c'est là que la fonction résume des choses comme la préparation de la réponse correcte du servlet ou la préparation des valeurs de retour. Il est agréable d'avoir un seul endroit pour faire la synthèse. Le site return -L'autre solution, qui consiste à utiliser le modèle de l'UE, nécessiterait deux fonctions pour chaque fonction de ce type. Une fonction externe pour préparer la réponse du servlet ou d'autres actions de ce type, et une fonction interne pour effectuer le calcul. PS : Un professeur d'anglais me suggérerait probablement d'utiliser "astonishing" plutôt que "surprising" dans le dernier paragraphe :-)

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