Les variables d'erreur sont-elles un anti-modèle ou une bonne conception?

44

Afin de gérer plusieurs erreurs possibles qui ne devraient pas interrompre l'exécution, j'ai une errorvariable que les clients peuvent vérifier et utiliser pour générer des exceptions. Est-ce un anti-modèle? Y a-t-il une meilleure façon de gérer cela? Pour un exemple de ceci en action, vous pouvez voir l' API mysqli de PHP . Supposons que les problèmes de visibilité (accesseurs, portée publique et privée, la variable dans une classe ou globale?) Soient gérés correctement.

Mikayla Maki
la source
6
Ce quoi try/ catchexiste pour. De plus, vous pouvez placer votre try/ catchbeaucoup plus loin dans la pile dans un endroit plus approprié pour la traiter (ce qui permet une plus grande séparation des problèmes).
jpmc26
Il convient de garder à l'esprit que si vous utilisez une gestion basée sur des exceptions et obtenez une exception, vous ne voulez pas montrer trop d'informations à l'utilisateur. Utilisez un gestionnaire d'erreurs tel qu'Elmah ou Raygun.io pour l'intercepter et afficher un message d'erreur générique à l'utilisateur. NE JAMAIS montrer une trace de pile ou un message d'erreur spécifique à l'utilisateur, car ils divulguent des informations sur le fonctionnement interne de l'application, qui peuvent faire l'objet d'abus.
Nzall
4
@Nate Vos conseils ne s'appliquent qu'aux applications critiques pour la sécurité, où l'utilisateur n'est absolument pas approuvé. Les messages d'erreur vagues sont eux-mêmes un anti-modèle. Il en va de même pour l'envoi de rapports d'erreur sur le réseau sans le consentement explicite de l'utilisateur.
jeudi
3
@piedar J'ai créé une question distincte où cela peut être discuté plus librement: programmers.stackexchange.com/questions/245255/…
Nzall
26
Un principe général dans la conception des API qui vous amène assez loin est de toujours regarder ce que fait PHP, puis de faire exactement le contraire.
Philipp

Réponses:

65

Si une langue prend en charge les exceptions de manière inhérente, il est préférable de lancer des exceptions et les clients peuvent intercepter l’exception s’ils ne souhaitent pas qu’il en résulte un échec. En fait, les clients de votre code attendent des exceptions et rencontreront de nombreux bogues car ils ne vérifieront pas les valeurs de retour.

L'utilisation d'exceptions si vous avez le choix présente de nombreux avantages.

messages

Les exceptions contiennent des messages d'erreur lisibles par l'utilisateur, qui peuvent être utilisés par les développeurs pour le débogage ou même affichés pour les utilisateurs, le cas échéant. Si le code consommateur ne peut pas gérer l'exception, il peut toujours le consigner pour que les développeurs puissent consulter les journaux sans avoir à s'arrêter à chaque trace pour déterminer la valeur renvoyée et la mapper dans un tableau pour déterminer le résultat. exception réelle.

Avec les valeurs de retour, aucune information supplémentaire ne peut être facilement fournie. Certaines langues prennent en charge les appels de méthode pour obtenir le dernier message d'erreur. Cette préoccupation est donc quelque peu apaisée, mais cela oblige l'appelant à faire des appels supplémentaires et nécessite parfois l'accès à un "objet spécial" contenant ces informations.

Dans le cas de messages d'exception, je fournis autant de contexte que possible, tels que:

Une stratégie du nom "foo" n'a pas pu être extraite pour l'utilisateur "bar", qui a été référencé dans le profil de l'utilisateur.

Comparez cela à un code de retour -85. Lequel préfèrerais tu?

Piles d'appels

Les exceptions ont généralement également des piles d'appels détaillées qui aident à déboguer le code plus rapidement et plus rapidement, et peuvent également être enregistrées par le code appelant si elles le souhaitent. Cela permet aux développeurs d’identifier le problème généralement à la ligne exacte, ce qui le rend très puissant. Encore une fois, comparez cela à un fichier journal avec des valeurs de retour (telles que -85, 101, 0, etc.), laquelle préféreriez-vous?

Échec approche rapide biaisée

Si une méthode appelée quelque part qui échoue, elle lève une exception. Le code appelant doit supprimer explicitement l'exception sinon il échouera. J'ai trouvé cela étonnant, car lors du développement et des tests (et même en production), le code échoue rapidement, obligeant les développeurs à le réparer. Dans le cas de valeurs de retour, si une vérification de la valeur de retour est manquée, l'erreur est ignorée en silence et le bogue fait surface quelque chose d'inattendu, généralement avec un coût beaucoup plus élevé de débogage et de réparation.

Exceptions d'emballage et de désemballage

Les exceptions peuvent être encapsulées dans d'autres exceptions, puis décomposées si nécessaire. Par exemple, votre code peut indiquer ArgumentNullExceptionlequel le code appelant peut être intégré à une ligne UnableToRetrievePolicyExceptioncar cette opération a échoué dans le code appelant. Même si un message semblable à l'exemple que j'ai fourni ci-dessus peut être affiché à l'utilisateur, un code de diagnostic peut décompresser l'exception et indiquer qu'il en ArgumentNullExceptionest à l'origine du problème, ce qui signifie qu'il s'agit d'une erreur de codage dans le code de votre consommateur. Cela pourrait alors déclencher une alerte afin que le développeur puisse corriger le code. De tels scénarios avancés ne sont pas faciles à implémenter avec les valeurs de retour.

Simplicité du code

Celui-ci est un peu plus difficile à expliquer, mais j'ai appris grâce à ce codage avec des valeurs de retour et des exceptions. Le code écrit à l'aide des valeurs de retour passe généralement un appel, puis une série de vérifications sur la valeur de retour. Dans certains cas, il ferait appel à une autre méthode et disposerait désormais d'une autre série de contrôles pour les valeurs renvoyées par cette méthode. À quelques exceptions près, la gestion des exceptions est beaucoup plus simple dans la plupart des cas. Vous avez un bloc try / catch / finally, le moteur d’exécution faisant de son mieux pour exécuter le code dans les blocs finally pour le nettoyage. Même les blocs try / catch / finally imbriqués sont relativement plus faciles à suivre et à gérer que si les options imbriquées / / et les valeurs renvoyées associées provenant de plusieurs méthodes.

Conclusion

Si la plate-forme que vous utilisez prend en charge les exceptions (notamment Java ou .NET), vous devez alors bien supposer qu'il n'y a pas d'autre moyen que de lever des exceptions car ces plateformes ont pour règle de générer des exceptions, et vos clients s'attendent à ce qu'ils alors. Si j'utilisais votre bibliothèque, je ne prendrais pas la peine de vérifier les valeurs de retour car je m'attendais à des exceptions, c'est ainsi que le monde est sur ces plates-formes.

Cependant, s'il s'agissait de C ++, il serait un peu plus difficile à déterminer car une grande base de code existe déjà avec des codes de retour, et un grand nombre de développeurs sont à l'écoute pour renvoyer des valeurs plutôt que des exceptions (par exemple, Windows est en proie à HRESULTs) . En outre, dans de nombreuses applications, cela peut également être un problème de performances (ou du moins, être perçu comme tel).

Omer Iqbal
la source
5
Windows renvoie les valeurs HRESULT à partir des fonctions C ++ afin de maintenir la compatibilité du C dans ses API publiques (et parce que vous entrez dans un monde déchirant en essayant de gérer les exceptions au-delà des frontières). Ne suivez pas aveuglément le modèle du système d'exploitation si vous écrivez une application.
Cody Grey
2
La seule chose que j'ajouterais à cela est la mention d'un couplage plus lâche. Les exceptions vous permettent de gérer de nombreuses situations imprévues à l'endroit le plus approprié. Par exemple, dans une application Web, vous souhaitez renvoyer un 500 sur toute exception pour laquelle votre code n'a pas été préparé, plutôt que de bloquer l'application Web. Vous avez donc besoin d’une sorte d’attrape tout en haut de votre code (ou dans votre framework). Une situation similaire existe dans les interfaces graphiques de bureau. Mais vous pouvez également placer des codes moins généraux à différents endroits du code pour gérer diverses situations d’échec d’une manière appropriée au processus en cours d’essai.
jpmc26
2
@TrentonMaki Si vous parlez d'erreurs de constructeurs C ++, la meilleure réponse est la suivante: parashift.com/c++-faq-lite/ctors-can-throw.html . En bref, jetez une exception, mais n'oubliez pas de nettoyer d'abord les fuites potentielles. Je ne suis au courant d'aucun autre langage où le simple lancement direct d'un constructeur est une mauvaise chose. Je pense que la plupart des utilisateurs d'une API préféreraient intercepter des exceptions plutôt que de vérifier les codes d'erreur.
Ogre Psalm33
3
"De tels scénarios avancés ne sont pas faciles à implémenter avec les valeurs de retour." Sûr que vous pouvez! Tout ce que vous avez à faire est de créer une ErrorStateReturnVariablesuper-classe, et l'une de ses propriétés est InnerErrorState(qui est une instance de ErrorStateReturnVariable), que l'implémentation de sous-classes peut définir pour afficher une chaîne d'erreurs ... oh, attendez. : p
Brian S
5
Le simple fait qu’une langue prenne en charge les exceptions ne fait pas de celles-ci une panacée. Les exceptions introduisent des chemins d'exécution cachés et leurs effets doivent donc être correctement contrôlés; try / catch sont faciles à ajouter, mais il est difficile de bien récupérer
Matthieu M.
21

Les variables d'erreur sont une relique de langages comme C, où les exceptions n'étaient pas disponibles. Aujourd'hui, vous devriez les éviter, sauf lorsque vous écrivez une bibliothèque potentiellement utilisée à partir d'un programme C (ou d'un langage similaire sans la gestion des exceptions).

Bien sûr, si vous avez un type d'erreur qui pourrait être mieux classé comme "avertissement" (= votre bibliothèque peut fournir un résultat valide et l'appelant peut ignorer l'avertissement s'il pense que ce n'est pas important), alors un indicateur d'état dans le formulaire d'une variable peut avoir un sens même dans les langues avec des exceptions. Mais méfiez-vous. Les appelants de la bibliothèque ont tendance à ignorer ces avertissements même s’ils ne le devraient pas. Alors réfléchissez bien avant d'introduire une telle construction dans votre bibliothèque.

Doc Brown
la source
1
Merci pour l'explication! Votre réponse + Omer Iqbal a répondu à ma question.
Mikayla Maki
Une autre façon de gérer les "avertissements" est de lever une exception par défaut et d’avoir une sorte d’indicateur facultatif pour empêcher l’exception d’être levée.
Cyanfish
1
@Cyanfish: oui, mais il faut veiller à ne pas trop dessiner de telles choses, en particulier lors de la création de bibliothèques. Mieux vaut prévoir un mécanisme d’alerte simple et fonctionnel que 2, 3 ou plus.
Doc Brown
Une exception ne devrait être levée que lorsqu'un scénario d'échec, inconnu ou irrécouvrable a été rencontré - circonstances exceptionnelles . Vous devriez vous attendre à un impact sur les performances lors de la construction des exceptions
Gusdor
@Gusdor: absolument, c'est pourquoi un "avertissement" typique ne devrait pas lancer une exception à mon humble avis par défaut. Mais cela dépend aussi un peu du niveau d'abstraction. Parfois, un service ou une bibliothèque ne peut tout simplement pas décider si un événement inhabituel doit être traité comme une exception du point de vue des appelants. Personnellement, dans de telles situations, je préfère que la bibliothèque mette juste un indicateur d’avertissement (pas d’exception), laissez l’appelant tester cet indicateur et lève une exception s’il le juge approprié. C’est ce que j’avais à l’esprit lorsque j’ai écrit ci-dessus: «Mieux vaut prévoir un mécanisme d’alerte dans une bibliothèque».
Doc Brown
20

Il y a plusieurs façons de signaler une erreur:

  • une variable d'erreur à vérifier: C , Go , ...
  • une exception: Java , C # , ...
  • un gestionnaire de "condition": Lisp (seulement?), ...
  • un retour polymorphe: Haskell , ML , Rust , ...

Le problème de la variable d'erreur est qu'il est facile d'oublier de vérifier.

Le problème des exceptions est qu’il crée des chemins cachés d’exécutions et, bien que try / catch soit facile à écrire, il est très difficile d’assurer une récupération correcte dans la clause catch (aucun support des systèmes de type / compilateurs).

Le problème des gestionnaires de conditions est qu’ils ne composent pas bien: si vous exécutez du code dynamique (fonctions virtuelles), il est impossible de prédire quelles conditions doivent être traitées. En outre, si la même condition peut être évoquée à plusieurs endroits, rien ne dit qu’une solution uniforme puisse être appliquée à chaque fois et elle devient rapidement compliquée.

Les retours polymorphes ( Either a ben Haskell) sont ma solution préférée à ce jour:

  • explicite: pas de chemin d'exécution caché
  • explicite: entièrement documenté dans la signature du type de fonction (pas de surprises)
  • difficile à ignorer: vous devez appliquer une correspondance de motif pour obtenir le résultat souhaité et gérer le cas d'erreur

Le seul problème est qu'ils peuvent potentiellement conduire à une vérification excessive; les langages qui les utilisent ont des idiomes pour enchaîner les appels des fonctions qui les utilisent, mais cela peut nécessiter encore un peu plus de dactylographie. En Haskell, ce seraient des monades ; cependant, cela est beaucoup plus effrayant qu'il n'y paraît, voir Programmation ferroviaire .

Matthieu M.
la source
1
Bonne réponse! J'espérais pouvoir obtenir une liste de façons de gérer les erreurs.
Mikayla Maki
Arrghhhh! Combien de fois ai-je vu ceci "Le problème de la variable d'erreur est qu'il est facile d'oublier de vérifier". Le problème avec les exceptions est qu'il est facile d'oublier de l'attraper. Ensuite, votre application se bloque. Votre patron ne veut pas payer pour le réparer, mais vos clients cessent d'utiliser votre application parce qu'ils sont frustrés par les accidents. Tout cela à cause de quelque chose qui n'aurait pas affecté l'exécution du programme si des codes d'erreur étaient renvoyés et ignorés. La seule fois où j'ai jamais vu des gens ignorer les codes d'erreur, c'était quand cela importait peu. Code plus haut dans la chaîne sait que quelque chose s'est mal passé.
Dunk
1
@Dunk: Je ne pense pas que ma réponse fasse l'apologie d'exceptions non plus; cependant, cela pourrait dépendre du type de code que vous écrivez. Mon expérience professionnelle personnelle a tendance à favoriser les systèmes qui échouent en présence d'erreurs car la corruption de données silencieuse est pire (et non détectée) et les données sur lesquelles je travaille sont précieuses pour le client (bien sûr, cela signifie également des correctifs urgents).
Matthieu M.
Ce n'est pas une question de corruption de données silencieuse. Il s'agit de comprendre votre application et de savoir quand et où vous devez vérifier si une opération a réussi ou non. Dans de nombreux cas, la détermination et le traitement peuvent être retardés. Ce ne devrait pas être quelqu'un d'autre qui me dira quand je dois gérer une opération ayant échoué, ce qui est requis par une exception. C'est mon application, je sais quand et où je veux traiter des problèmes pertinents. Si des personnes écrivent des applications susceptibles de corrompre des données, leur travail est vraiment médiocre. Écrire des applications qui plantent (ce que je vois souvent) est également un piètre travail.
Dunk
12

Je pense que c'est affreux. Je suis actuellement en train de refactoriser une application Java qui utilise des valeurs de retour au lieu d'exceptions. Bien que vous ne travailliez peut-être pas du tout avec Java, je pense que cela s’applique néanmoins.

Vous vous retrouvez avec un code comme celui-ci:

String result = x.doActionA();
if (result != null) {
  throw new Exception(result);
}
result = x.doActionB();
if (result != null) {
  throw new Exception(result);
}

Ou ca:

if (!x.doActionA()) {
  throw new Exception(x.getError());
}
if (!x.doActionB()) {
  throw new Exception(x.getError());
}

Je préférerais que les actions jettent des exceptions elles-mêmes. Vous obtenez donc quelque chose du genre:

x.doActionA();
x.doActionB();

Vous pouvez inclure cela dans une capture d'essai et obtenir le message de l'exception, ou vous pouvez choisir d'ignorer l'exception, par exemple lorsque vous supprimez quelque chose qui est peut-être déjà parti. Il conserve également votre trace de pile, si vous en avez une. Les méthodes elles-mêmes deviennent également plus faciles. Au lieu de gérer les exceptions elles-mêmes, ils jettent simplement ce qui a mal tourné.

Code actuel (horrible):

private String doActionA() {
  try {
    someOperationThatCanGoWrong1();
    someOperationThatCanGoWrong2();
    someOperationThatCanGoWrong3();
    return null;
  } catch(Exception e) {
    return "Something went wrong!";
  }
}

Nouveau et amélioré:

private void doActionA() throws Exception {
  someOperationThatCanGoWrong1();
  someOperationThatCanGoWrong2();
  someOperationThatCanGoWrong3();
}

La trace de Strack est conservée et le message est disponible dans l'exception plutôt que dans l'inutile "Quelque chose s'est mal passé!".

Vous pouvez bien entendu fournir de meilleurs messages d'erreur, et vous devriez le faire. Mais ce post est ici parce que le code actuel sur lequel je travaille est pénible, et vous ne devriez pas faire la même chose.

mrjink
la source
1
Un inconvénient cependant, il existe des situations dans lesquelles votre "nouveauté et une amélioration" perdraient le contexte dans lequel l'exception se produit initialement. Par exemple, dans la "version actuelle (horrible)" de doActionA (), la clause catch aurait accès aux variables d'instance et à d'autres informations provenant de l'objet englobant pour donner un message plus utile.
InformedA
1
C'est vrai, mais cela ne se produit pas actuellement, ici. Et vous pouvez toujours intercepter l'exception dans doActionA et l'envelopper dans une autre exception avec un message d'état. Ensuite, vous aurez toujours la trace de la pile et le message utile. throw new Exception("Something went wrong with " + instanceVar, ex);
mrjink
Je suis d'accord, cela pourrait ne pas arriver dans votre situation. Mais vous ne pouvez pas "toujours" mettre les informations dans doActionA (). Pourquoi? L'appelant de doActionA () est peut-être le seul à détenir les informations que vous devez inclure.
InformedA
2
Demandez donc à l'appelant de l'inclure lorsqu'il traite l'exception. La même chose s'applique à la question initiale, cependant. Il n'y a rien que vous puissiez faire maintenant que vous ne pouvez pas faire avec des exceptions, et cela conduit à un code plus propre. Je préfère les exceptions aux booléens renvoyés ou aux messages d'erreur.
Mrjink
Vous faites l'hypothèse généralement fausse selon laquelle une exception se produisant dans l'un des "éléments d'exploitation" peut être gérée et nettoyée de la même manière. Dans la réalité, vous devez capturer et gérer des exceptions pour chaque opération. Ainsi, vous ne lancez pas une exception comme dans votre exemple. De plus, l'utilisation d'exceptions finit par créer un ensemble de blocs try-catch imbriqués ou une série de blocs try-catch. Cela rend souvent le code beaucoup moins lisible. Je ne suis pas opposé aux exceptions, mais j'utilise l'outil approprié pour la situation spécifique. Les exceptions ne sont qu'un outil.
Dunk
5

"Afin de gérer plusieurs erreurs possibles, cela ne devrait pas arrêter l'exécution",

Si vous voulez dire que les erreurs ne doivent pas interrompre l'exécution de la fonction en cours mais doivent être signalées à l'appelant d'une manière ou d'une autre - vous disposez alors de quelques options qui n'ont pas vraiment été mentionnées. Cette affaire est vraiment plus un avertissement qu'une erreur. Lancer / Retourner n'est pas une option car cela met fin à la fonction en cours. Un paramètre ou un retour de message d'erreur unique ne permet qu'au maximum l'une de ces erreurs.

Deux modèles que j'ai utilisés sont:

  • Une collection d'erreurs / avertissements, transmise ou conservée en tant que variable membre. À quoi vous ajoutez des éléments et continuez simplement le traitement. Personnellement, je n'aime pas vraiment cette approche car je sens qu'elle affranchit l'appelant.

  • Transmettez un objet de gestionnaire d'erreur / d'avertissement (ou définissez-le en tant que variable membre). Et chaque erreur appelle une fonction membre du gestionnaire. Ainsi, l’appelant peut décider quoi faire avec de telles erreurs qui ne se terminent pas.

Ce que vous transmettez à ces collections / gestionnaires doit contenir suffisamment de contexte pour que l’erreur soit gérée "correctement" - Une chaîne est généralement trop petite, la transmettre à une instance d’Exception est souvent judicieux - mais parfois mal vu (comme un abus d’Exceptions) .

Le code typique utilisant un gestionnaire d'erreurs pourrait ressembler à ceci

class MyFunClass {
  public interface ErrorHandler {
     void onError(Exception e);
     void onWarning(Exception e);
  }

  ErrorHandler eh;

  public void canFail(int i) {
     if(i==0) {
        if(eh!=null) eh.onWarning(new Exception("canFail shouldn't be called with i=0"));
     }
     if(i==1) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=1 is fatal");
        throw new RuntimeException("canFail called with i=2");
     }
     if(i==2) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=2 is an error, but not fatal"));
     }
  }
}
Michael Anderson
la source
3
+1 pour avoir remarqué que l'utilisateur veut un avertissement plutôt qu'une erreur. Cela vaut peut-être la peine de mentionner le warningspaquet Python , qui donne un autre motif à ce problème.
James_pic
Merci pour la bonne réponse! C’est plus ce que je voulais voir, des modèles supplémentaires pour la gestion des erreurs où le traditionnel try / catch pourrait ne pas suffire.
Mikayla Maki
Il peut être utile de mentionner qu'un objet error-callback transmis peut générer une exception lorsque certaines erreurs ou certains avertissements se produisent (il peut s'agir du comportement par défaut), mais il peut également être utile aux moyens par lesquels il peut demander la fonction d'appel à faire quelque chose. Par exemple, une méthode "handle parsing error" peut donner à l'appelant une valeur que l'analyse doit être réputée avoir renvoyée.
Supercat
5

Il n’ya souvent rien de mal à utiliser ce motif ou ce motif tant que vous utilisez le motif que tout le monde utilise. En développement Objective-C , le modèle préféré consiste à passer un pointeur où la méthode appelée peut déposer un objet NSError. Les exceptions sont réservées aux erreurs de programmation et entraînent un blocage (à moins que des programmeurs Java ou .NET écrivent leur première application iPhone). Et ça marche plutôt bien.

gnasher729
la source
4

La question est déjà résolue, mais je ne peux pas m'en empêcher.

Vous ne pouvez pas vraiment vous attendre à ce qu'Exception fournisse une solution pour tous les cas d'utilisation. Marteau quelqu'un?

Par exemple, si une méthode reçoit une requête et est responsable de la validation de tous les champs passés, et pas seulement le premier, vous devez penser qu'il devrait être possible de indiquer la cause de l'erreur pour plusieurs champs. Il devrait également être possible d'indiquer si la nature de la validation empêche ou non l'utilisateur d'aller plus loin. Un exemple de cela serait un mot de passe peu fort. Vous pouvez afficher un message à l'utilisateur pour lui indiquer que le mot de passe saisi n'est pas très fort, mais qu'il est assez fort.

Vous pourriez faire valoir que toutes ces validations pourraient être levées à titre d'exception à la fin du module de validation, mais qu'elles constitueraient des codes d'erreur dans tout sauf dans le nom.

La leçon à tirer est donc la suivante: les exceptions ont leur place, de même que les codes d'erreur. Choisissez judicieusement.

Alexandre Santos
la source
Je pense que cela implique plutôt un mauvais design. Une méthode qui prend des arguments devrait pouvoir traiter ceux-ci ou non - rien entre les deux. Dans votre exemple, une Validator(interface) doit être injectée dans la méthode en question (ou dans l'objet derrière). En fonction de l'injection, Validatorla méthode procédera avec des mots de passe incorrects - ou non. Le code environnant peut alors essayer un WeakValidatorsi l’utilisateur le demande après, par exemple, un a WeakPasswordExceptionété jeté par le premier essayé StrongValidator.
Jhr
Ah, mais je n'ai pas dit que cela ne pourrait pas être une interface ou un validateur. Je n'ai même pas mentionné JSR303. Et, si vous lisez attentivement, je me suis assuré de ne pas dire un mot de passe faible, mais plutôt que ce n'était pas très fort. Un mot de passe faible serait une raison pour arrêter le flux et demander à l'utilisateur un mot de passe plus fort.
Alexandre Santos
Et que feriez-vous avec un mot de passe assez puissant, mais pas vraiment faible? Vous interrompez le flux et montrez à l'utilisateur un message indiquant que le mot de passe entré n'est pas très fort. Donc avoir un MiddlyStrongValidatorou quelque chose. Et si cela n’interrompait pas vraiment votre flux, il Validatordevait avoir été appelé au préalable, c’est-à-dire avant de poursuivre le flux pendant que l’utilisateur saisit toujours son mot de passe (ou similaire). Mais alors, la validation ne faisait pas partie de la méthode en question. :) Probablement une question de goût après tout ...
jhr
@jhr Dans les validateurs que j'ai écrits, je vais généralement créer un AggregateException(ou un similaire ValidationException) et mettre des exceptions spécifiques pour chaque problème de validation dans InnerExceptions. Par exemple, cela pourrait être BadPasswordException: "Le mot de passe de l'utilisateur est inférieur à la longueur minimale de 6" ou MandatoryFieldMissingException: "Le prénom doit être fourni à l'utilisateur", etc. Ceci n'est pas équivalent à des codes d'erreur. Tous ces messages peuvent être affichés pour l'utilisateur d'une manière qu'ils comprendront, et si un a NullReferenceExceptionété lancé à la place, alors nous avons un bogue.
Omer Iqbal
4

Il existe des cas d'utilisation où les codes d'erreur sont préférables aux exceptions.

Si votre code peut continuer malgré l'erreur, mais qu'il doit être signalé, une exception constitue un choix peu judicieux, car elle met fin au flux. Par exemple, si vous lisez dans un fichier de données et que vous découvrez qu'il contient des données erronées non terminales, il peut être préférable de lire le reste du fichier et de signaler l'erreur plutôt que d'échouer complètement.

Les autres réponses ont expliqué pourquoi les exceptions devraient être préférées aux codes d'erreur en général.

Jack Aidley
la source
Si un avertissement doit être consigné ou quelque chose du genre, qu’il en soit ainsi. Mais si une erreur "nécessite un rapport", ce qui implique que je suppose que vous signalez à l'utilisateur, il n'y a aucun moyen de garantir que le code environnant lira votre code de retour et le rapportera réellement.
Jhr
Non, je veux dire le signaler à l'appelant. C'est à l'appelant de décider si l'utilisateur doit connaître l'erreur ou non, comme s'il s'agissait d'une exception.
Jack Aidley
1
@jhr: Quand y a-t-il une garantie de quoi que ce soit? Le contrat d'une classe peut spécifier que les clients ont certaines responsabilités; si les clients respectent le contrat, ils feront ce qu’il exige. S'ils ne le font pas, le code du client sera la seule conséquence. Si on veut se prémunir contre les erreurs accidentelles de la part du client et avoir le contrôle sur le type renvoyé par une méthode de sérialisation, on pourrait lui faire inclure un indicateur "corruption possible non reconnue" et ne pas autoriser les clients à en lire des données sans appeler de AcknowledgePossibleCorruptionméthode. .
supercat
1
... mais il peut être plus utile de disposer d'une classe d'objets pour stocker des informations sur les problèmes plutôt que de générer des exceptions ou de renvoyer un code d'erreur pass-fail. Il appartiendrait à l'application d'utiliser ces informations de manière appropriée (par exemple, lors du chargement du fichier "Foo", informer l'utilisateur que les données peuvent ne pas être fiables, et l'inviter à choisir un nouveau nom lors de la sauvegarde).
Supercat
1
Il y a une garantie si vous utilisez des exceptions: si vous ne les détectez pas, elles jettent plus haut - dans le pire des cas, jusqu'à l'interface utilisateur. Aucune garantie de ce type si vous utilisez des codes de retour que personne ne lit. Bien sûr, suivez l’API si vous voulez l’utiliser. Je suis d'accord! Mais il y a place à l' erreur, malheureusement ...
JDH
2

Il n'y a définitivement rien de mal à ne pas utiliser les exceptions lorsque les exceptions ne conviennent pas.

Lorsque l'exécution du code ne doit pas être interrompu (par exemple agissant sur l' entrée d'utilisateur qui peut contenir plusieurs erreurs, comme un programme pour compiler ou une forme de processus), je trouve que la collecte des erreurs dans les variables d'erreur comme has_errorset error_messagesest en effet la conception beaucoup plus élégant que de lancer une exception à la première erreur. Il permet de trouver toutes les erreurs dans les entrées de l'utilisateur sans obliger l'utilisateur à le soumettre à nouveau inutilement.

ikh
la source
Intéressant prendre la question. Je pense que le problème de ma question et de ma compréhension est une terminologie incertaine. Ce que vous décrivez n'est pas exceptionnel, mais c'est une erreur. Comment devrions-nous appeler cela?
Mikayla Maki
1

Dans certains langages de programmation dynamiques, vous pouvez utiliser à la fois les valeurs d'erreur et la gestion des exceptions . Ceci est fait en renvoyant un objet d' exception unthrown à la place de la valeur de retour ordinaire, qui peut être vérifiée comme une valeur d'erreur, mais une exception est levée si elle n'est pas cochée.

En Perl 6, cela se fait via fail, qui, si vous perdez la no fatal;portée, retourne un Failureobjet exception non déclenchée spécial .

Dans Perl 5, vous pouvez utiliser Contextual :: Return, vous pouvez le faire avec return FAIL.

Jakub Narębski
la source
-1

À moins qu'il y ait quelque chose de très spécifique, je pense qu'avoir une variable d'erreur pour la validation est une mauvaise idée. Le but semble être de gagner du temps sur la validation (vous pouvez simplement renvoyer la valeur de la variable)

Mais si vous changez quelque chose, vous devez quand même recalculer cette valeur. Je ne peux pas en dire plus sur les arrêts et les lancers d’Exception.

EDIT: Je n'avais pas réalisé qu'il s'agissait d'une question de paradigme logiciel plutôt que d'un cas spécifique.

Permettez-moi de préciser mes points dans un cas particulier où ma réponse aurait un sens.

  1. J'ai une collection d'objets d'entité
  2. J'ai des services Web de style procédural qui fonctionnent avec ces objets d'entité

Il y a deux sortes d'erreurs:

  1. Les erreurs lors du traitement dans la couche service
  2. Les erreurs car il y a des incohérences dans les objets d'entité

Dans la couche de service, il n'y a pas d'autre choix que d'utiliser un objet de résultat comme wrapper, l'équivalent de la variable d'erreur. Simuler une exception via un appel de service sur un protocole tel que http est possible, mais ce n’est certainement pas une bonne chose à faire. Je ne parle pas de ce genre d'erreur et je ne pensais pas que c'était le genre d'erreur qui était posée dans cette question.

Je pensais à la deuxième sorte d'erreur. Et ma réponse concerne ce deuxième type d’erreurs. Dans les objets Entité, il y a des choix pour nous, certains d'entre eux sont

  • en utilisant une variable de validation
  • émet une exception immédiatement lorsqu'un champ est défini de manière incorrecte par le setter

Utiliser une variable de validation revient à avoir une seule méthode de validation pour chaque objet d'entité. En particulier, l’utilisateur peut définir la valeur de manière à conserver le paramètre uniquement, aucun effet secondaire (c’est souvent une bonne pratique) ou bien incorporer la validation dans chaque programme puis enregistrer le résultat dans une variable de validation. L'avantage de ceci serait de gagner du temps, le résultat de la validation est mis en cache dans la variable de validation de sorte que lorsque l'utilisateur appelle validation () plusieurs fois, il n'est pas nécessaire de procéder à plusieurs validations.

La meilleure chose à faire dans ce cas consiste à utiliser une seule méthode de validation sans utiliser aucune validation pour mettre en cache une erreur de validation. Cela aide à garder le setter comme un setter.

InforméA
la source
Je vois de quoi tu parles. Approche intéressante. Je suis content que cela fasse partie de l'ensemble des réponses.
Mikayla Maki