Afin de gérer plusieurs erreurs possibles qui ne devraient pas interrompre l'exécution, j'ai une error
variable 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.
design-patterns
object-oriented
exceptions
patterns-and-practices
anti-patterns
Mikayla Maki
la source
la source
try
/catch
existe pour. De plus, vous pouvez placer votretry
/catch
beaucoup plus loin dans la pile dans un endroit plus approprié pour la traiter (ce qui permet une plus grande séparation des problèmes).Réponses:
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:
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
ArgumentNullException
lequel le code appelant peut être intégré à une ligneUnableToRetrievePolicyException
car 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 enArgumentNullException
est à 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).
la source
ErrorStateReturnVariable
super-classe, et l'une de ses propriétés estInnerErrorState
(qui est une instance deErrorStateReturnVariable
), que l'implémentation de sous-classes peut définir pour afficher une chaîne d'erreurs ... oh, attendez. : pLes 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.
la source
Il y a plusieurs façons de signaler une erreur:
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 b
en Haskell) sont ma solution préférée à ce jour: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 .
la source
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:
Ou ca:
Je préférerais que les actions jettent des exceptions elles-mêmes. Vous obtenez donc quelque chose du genre:
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):
Nouveau et amélioré:
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.
la source
throw new Exception("Something went wrong with " + instanceVar, ex);
"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
la source
warnings
paquet Python , qui donne un autre motif à ce problème.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.
la source
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.
la source
Validator
(interface) doit être injectée dans la méthode en question (ou dans l'objet derrière). En fonction de l'injection,Validator
la méthode procédera avec des mots de passe incorrects - ou non. Le code environnant peut alors essayer unWeakValidator
si l’utilisateur le demande après, par exemple, un aWeakPasswordException
été jeté par le premier essayéStrongValidator
.MiddlyStrongValidator
ou quelque chose. Et si cela n’interrompait pas vraiment votre flux, ilValidator
devait 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 ...AggregateException
(ou un similaireValidationException
) et mettre des exceptions spécifiques pour chaque problème de validation dans InnerExceptions. Par exemple, cela pourrait êtreBadPasswordException
: "Le mot de passe de l'utilisateur est inférieur à la longueur minimale de 6" ouMandatoryFieldMissingException
: "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 aNullReferenceException
été lancé à la place, alors nous avons un bogue.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.
la source
AcknowledgePossibleCorruption
méthode. .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_errors
eterror_messages
est 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.la source
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 lano fatal;
portée, retourne unFailure
objet exception non déclenchée spécial .Dans Perl 5, vous pouvez utiliser Contextual :: Return, vous pouvez le faire avec
return FAIL
.la source
À 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.
Il y a deux sortes d'erreurs:
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
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.
la source