Utiliser une validation try-finally (sans catch) vs enum-state

9

J'ai lu les conseils sur cette question sur la manière de traiter une exception aussi près que possible de son origine.

Mon dilemme sur la meilleure pratique est de savoir si l'on doit utiliser un try / catch / enfin pour retourner une énumération (ou un int qui représente une valeur, 0 pour l'erreur, 1 pour ok, 2 pour l'avertissement, etc., selon le cas) afin que une réponse est toujours en ordre, ou faut-il laisser passer l'exception pour que la partie appelante s'en occupe?

D'après ce que je peux comprendre, cela pourrait être différent selon le cas, donc le conseil d'origine semble étrange.

Par exemple, sur un service Web, vous voudrez toujours retourner un état bien sûr, donc toute exception doit être traitée sur place, mais disons à l'intérieur d'une fonction qui publie / obtient des données via http, vous voudriez que le exception (par exemple dans le cas de 404) pour simplement passer à celui qui l'a tiré. Si vous ne le faites pas, vous devrez créer un moyen d'informer la partie appelante de la qualité du résultat (erreur: 404), ainsi que du résultat lui-même.

Bien qu'il soit possible d'essayer d'attraper l'exception 404 à l'intérieur de la fonction d'assistance qui obtient / publie les données, devriez-vous? Est-ce seulement moi qui utilise un petit entier pour désigner les états dans le programme (et les documenter de manière appropriée bien sûr), puis utiliser ces informations à des fins de validation de bon sens (tout va bien / gestion des erreurs) à l'extérieur?

Mise à jour: je m'attendais à une exception fatale / non fatale pour la classification principale, mais je ne voulais pas l'inclure afin de ne pas nuire aux réponses. Permettez-moi de clarifier la question: gérer les exceptions levées, et non lever des exceptions. L'effet souhaité: détectez une erreur et essayez de la récupérer. Si la récupération n'est pas possible, fournissez les commentaires les plus significatifs.

Encore une fois, avec l'exemple http get / post, la question est, devez-vous fournir un nouvel objet qui décrit ce qui est arrivé à l'appelant d'origine? Si cet assistant était dans une bibliothèque que vous utilisez, vous attendriez-vous à ce qu'il vous fournisse un code d'état pour l'opération, ou l'inclueriez-vous dans un bloc try-catch? Si vous le concevez , fourniriez-vous un code d'état ou lèveriez-vous une exception et laisser le niveau supérieur le traduire en code / message d'état à la place?

Synopsis: Comment choisissez-vous si un morceau de code au lieu de produire une exception, retourne un code d'état avec les résultats qu'il peut produire?

Mihalis Bagos
la source
1
Cela ne concerne pas l'erreur qui modifie la forme de gestion des erreurs utilisée. Dans le cas du 404, vous le laisseriez passer car vous ne pouvez pas le gérer.
stonemetal
"un int qui représente une valeur, 0 pour une erreur, 1 pour ok, 2 pour un avertissement, etc." Veuillez dire que c'était un exemple tout de suite! Utiliser 0 pour signifier OK est très certainement la norme ...
anaximander

Réponses:

15

Des exceptions doivent être utilisées pour des conditions exceptionnelles. Lancer une exception revient à dire: "Je ne peux pas gérer cette condition ici; quelqu'un plus haut dans la pile des appels peut-il attraper cela pour moi et le gérer?"

Renvoyer une valeur peut être préférable, s'il est clair que l'appelant prendra cette valeur et fera quelque chose de significatif avec elle. Cela est particulièrement vrai si le lancement d'une exception a des implications en termes de performances, c'est-à-dire qu'elle peut se produire dans une boucle étroite. Lancer une exception prend beaucoup plus de temps que de renvoyer une valeur (d'au moins deux ordres de grandeur).

Les exceptions ne doivent jamais être utilisées pour implémenter la logique du programme. En d'autres termes, ne lancez pas d'exception pour faire quelque chose; lever une exception pour déclarer que cela ne pouvait pas être fait.

Robert Harvey
la source
Merci pour la réponse, c'est la plus informative mais je me concentre sur la gestion des exceptions, et non sur la levée des exceptions. Devriez-vous attraper l'exception 404 dès que vous la recevez ou devez-vous la laisser monter plus haut dans la pile?
Mihalis Bagos
Je vois votre modification, mais cela ne change pas ma réponse. Convertissez l'exception en un code d'erreur si cela est significatif pour l'appelant. Si ce n'est pas le cas, gérez l'exception; laissez-le remonter la pile; ou attrapez-le, faites-en quelque chose (comme l'écrire dans un journal, ou autre chose), et recommencez.
Robert Harvey
1
+1: pour une explication raisonnable et équilibrée. Une exception doit être utilisée pour traiter les cas exceptionnels. Sinon, une fonction ou une méthode doit renvoyer une valeur significative (type énumération ou option) et l'appelant doit la gérer correctement. On pourrait peut-être mentionner une troisième alternative populaire en programmation fonctionnelle, à savoir les continuations.
Giorgio
4

Un bon conseil que j'ai lu une fois était de lever des exceptions lorsque vous ne pouvez pas progresser compte tenu de l'état des données que vous traitez, mais si vous avez une méthode qui peut lever une exception, fournissez également si possible une méthode pour affirmer si les données sont réellement valide avant l'appel de la méthode.

Par exemple, System.IO.File.OpenRead () lèvera une FileNotFoundException si le fichier fourni n'existe pas, mais il fournit également une méthode .Exists () qui renvoie une valeur booléenne indiquant si le fichier est présent que vous devez appeler avant appeler OpenRead () pour éviter toute exception inattendue.

Pour répondre à la partie "quand dois-je traiter une exception" de la question, je dirais où vous pouvez réellement faire quelque chose. Si votre méthode ne peut pas traiter une exception levée par une méthode qu'elle appelle, ne l'attrapez pas. Laissez-le monter plus haut dans la chaîne d'appel vers quelque chose qui puisse y faire face. Dans certains cas, il peut simplement s'agir d'un enregistreur écoutant Application.UnhandledException.

Trevor Pilley
la source
Beaucoup de gens, en particulier les programmeurs python , préfèrent EAFP c'est-à-dire "Il est plus facile de demander pardon que d'obtenir une autorisation"
Mark Booth
1
+1 pour un commentaire sur la façon d'éviter les exceptions comme avec .Exists ().
codingoutloud
+1 C'est toujours un bon conseil. Dans le code que j'écris / gère, une exception est "exceptionnelle", 9/10 fois une exception est destinée à un développeur à voir, dit-il, vous devriez être une programmation défensive! L'autre fois, c'est quelque chose que nous ne pouvons pas traiter, et nous l'enregistrons, et sortons du mieux que nous pouvons. La cohérence est importante, par exemple, par convention, nous aurions normalement une vraie fausse réponse et des messages internes pour le tarif / traitement standard. Les API tierces qui semblent lever des exceptions pour tout peuvent être traitées à l'appel et retournées en utilisant le processus standard convenu.
Gavin Howden
3

utilisez un try / catch / finalement pour renvoyer une énumération (ou un int qui représente une valeur, 0 pour l'erreur, 1 pour ok, 2 pour l'avertissement, etc., selon le cas) afin qu'une réponse soit toujours en ordre,

C'est une conception terrible. Ne «masquez» pas une exception en traduisant en un code numérique. Laissez-le comme une exception appropriée et sans ambiguïté.

ou faut-il laisser passer l'exception pour que la partie appelante s'en occupe?

C'est à cela que servent les exceptions.

traité le plus près possible de l'endroit où il est soulevé

Ce n'est pas du tout une vérité universelle . C'est une bonne idée parfois. D'autres fois, ce n'est pas aussi utile.

S.Lott
la source
Ce n'est pas une conception terrible. Microsoft l'implémente à de nombreux endroits, notamment sur le fournisseur d'appartenance asp.NET par défaut. En dehors de cela, je ne vois pas comment cette réponse contribue à la conversation
Mihalis Bagos
@MihalisBagos: Tout ce que je peux faire, c'est suggérer que l'approche de Microsoft n'est pas adoptée par tous les langages de programmation. Dans les langues sans exception, le retour d'une valeur est essentiel. C est l'exemple le plus notable. Dans les langues avec des exceptions, renvoyer des "valeurs de code" pour indiquer des erreurs est une conception terrible. Cela conduit à des ifinstructions (parfois) lourdes au lieu d'une gestion des exceptions (parfois) plus simple. La réponse ("Comment choisir ...") est simple. Non . Je pense que cela ajoute à la conversation. Vous êtes cependant libre d'ignorer cette réponse.
S.Lott
Je ne dis pas que votre opinion ne compte pas mais je dis que votre opinion n'est pas développée. Avec ce commentaire, je suppose que le raisonnement est que lorsque nous pouvons utiliser des exceptions, nous devrions, simplement parce que nous le pouvons? Je ne vois pas le code de statut comme un masquage, plutôt qu'une catégorisation / organisation des cas de flux de code
Mihalis Bagos
1
L'exception est déjà une catégorisation / organisation. Je ne vois pas l'intérêt de remplacer une exception non ambiguë par une valeur de retour qui peut facilement être confondue avec des valeurs de retour "normales" ou "non exceptionnelles". Les valeurs de retour doivent toujours être non exceptionnelles. Mon avis n'est pas très développé: c'est très simple. Ne transformez pas une exception en code numérique. C'était déjà un objet de données parfaitement bon qui sert tous les cas d'utilisation parfaitement bons que nous pouvons imaginer.
S.Lott
3

Je suis d'accord avec S.Lott . Attraper l'exception aussi près que possible de la source peut être une bonne ou une mauvaise idée selon la situation. La clé de la gestion des exceptions est de les intercepter uniquement lorsque vous pouvez y remédier. Les attraper et renvoyer une valeur numérique à la fonction appelante est généralement une mauvaise conception. Vous voulez juste les laisser flotter jusqu'à ce que vous puissiez récupérer.

Je considère toujours la gestion des exceptions comme une étape loin de ma logique d'application. Je me demande, si cette exception est levée à quelle distance sauvegarder la pile d'appels dois-je explorer avant que mon application ne soit dans un état récupérable? Dans de nombreux cas, s'il n'y a rien que je puisse faire dans l'application pour récupérer, cela peut signifier que je ne l'attrape pas jusqu'au niveau supérieur et que je journalise l'exception, échoue le travail et essaie de m'arrêter proprement.

Il n'y a vraiment pas de règle stricte pour déterminer quand et comment configurer la gestion des exceptions, sauf les laisser seuls jusqu'à ce que vous sachiez quoi en faire.

DwayneAllen
la source
1

Les exceptions sont de belles choses. Ils vous permettent de produire une description claire d'un problème d'exécution sans recourir à une ambiguïté inutile. Les exceptions peuvent être typées, sous-typées et peuvent être gérées par type. Ils peuvent être transmis pour être manipulés ailleurs, et s'ils ne peuvent pas être manipulés, ils peuvent être surélevés pour être traités à une couche supérieure de votre application. Ils reviendront également automatiquement de votre méthode sans avoir à invoquer beaucoup de logique folle pour gérer les codes d'erreur obscurcis.

Vous devez lever une exception immédiatement après avoir rencontré des données non valides dans votre code. Vous devez encapsuler les appels à d'autres méthodes dans un try..catch..finalement pour gérer toutes les exceptions qui pourraient être levées, et si vous ne savez pas comment répondre à une exception donnée, vous la lancez à nouveau pour indiquer aux couches supérieures que il y a quelque chose de mal qui devrait être traité ailleurs.

La gestion des codes d'erreur peut être très difficile. Vous vous retrouvez généralement avec beaucoup de duplication inutile dans votre code et / ou beaucoup de logique désordonnée pour gérer les états d'erreur. Être un utilisateur et rencontrer un code d'erreur est encore pire, car le code lui-même ne sera pas significatif et ne fournira pas à l'utilisateur un contexte pour l'erreur. Une exception, d'autre part, peut indiquer à l'utilisateur quelque chose d'utile, comme "Vous avez oublié de saisir une valeur", ou "vous avez entré une valeur non valide, voici la plage valide que vous pouvez utiliser ...", ou "Je ne sais pas savoir ce qui s'est passé, contactez le support technique et dites-leur que je viens de planter et donnez-leur la trace de pile suivante ... ".

Donc, ma question au PO est la suivante: pourquoi diable ne voudriez-vous pas utiliser des exceptions par rapport aux codes d'erreur renvoyés?

S.Robins
la source
0

Toutes les bonnes réponses. Je voudrais également ajouter que le retour d'un code d'erreur au lieu de lever une exception peut compliquer le code de l'appelant. Dire que la méthode A appelle la méthode B appelle la méthode C et C rencontre une erreur. Si C renvoie un code d'erreur, B doit maintenant avoir une logique pour déterminer s'il peut gérer ce code d'erreur. S'il ne le peut pas, il doit le renvoyer à A. Si A ne peut pas gérer l'erreur, que faites-vous? Jetez une exception? Dans cet exemple, le code est beaucoup plus propre si C lève simplement une exception, B n'attrape pas l'exception donc il abandonne automatiquement sans qu'aucun code supplémentaire ne soit nécessaire pour le faire et A peut intercepter certains types d'exceptions tout en laissant les autres continuer l'appel empiler.

Cela rappelle une bonne règle à coder en:

Les lignes de code sont comme des balles dorées. Vous souhaitez en utiliser le moins possible pour faire le travail.

Raymond Saltrelli
la source
0

J'ai utilisé une combinaison des deux solutions: pour chaque fonction de validation, je passe un enregistrement que je remplis avec le statut de validation (un code d'erreur). À la fin de la fonction, si une erreur de validation existe, je lève une exception, de cette façon je ne lève pas d'exception pour chaque champ, mais une seule fois.

J'ai également profité du fait que lever une exception arrêterait l'exécution car je ne souhaite pas que l'exécution se poursuive lorsque les données ne sont pas valides.

Par exemple

procedure Validate(var R:TValidationRecord);
begin
  if Field1 is not valid then
  begin
    R.Field1ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end; 
  if Field2 is not valid then
  begin
    R.Field2ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;
  if Field3 is not valid then
  begin
    R.Field3ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;

  if ErrorFlag then
    ThrowException
end;

Si vous vous basez uniquement sur le booléen, le développeur qui utilise ma fonction devrait en tenir compte en écrivant:

if not Validate() then
  DoNotContinue();

mais il peut oublier et appeler seulement Validate()(je sais qu'il ne devrait pas, mais peut-être qu'il pourrait).

Ainsi, dans le code ci-dessus, j'ai gagné les deux avantages:

  1. Une seule exception dans la fonction de validation.
  2. Une exception, même non capturée, arrêtera l'exécution et apparaîtra au moment du test
Bahaa
la source
0

Il n'y a pas une seule réponse ici - un peu comme il n'y a pas une sorte d'exception HttpException.

Il est très logique que les bibliothèques HTTP sous-jacentes lèvent une exception lorsqu'elles obtiennent une réponse 4xx ou 5xx; la dernière fois que j'ai regardé les spécifications HTTP, c'étaient des erreurs.

Quant à lever cette exception - ou à l'envelopper et à la relancer - je pense que c'est vraiment une question de cas d'utilisation. Par exemple, si vous écrivez un wrapper pour récupérer des données de l'API et les exposer à des applications, vous pouvez décider que sémantiquement une demande de ressource inexistante qui renvoie un HTTP 404 aurait plus de sens pour l'attraper et retourner null. D'un autre côté, une erreur 406 (non acceptable) pourrait valoir la peine de renvoyer une erreur car cela signifie que quelque chose a changé et que l'application devrait planter et brûler et crier à l'aide.

Wyatt Barnett
la source