Mon cas spécifique ici est que l'utilisateur peut passer une chaîne dans l'application, qu'elle analyse et l'assigne à des objets structurés. Parfois, l'utilisateur peut taper quelque chose d'invalide. Par exemple, leurs commentaires peuvent décrire une personne, mais ils peuvent dire que leur âge est "pomme". Le comportement correct dans ce cas est d'annuler la transaction et d'indiquer à l'utilisateur qu'une erreur s'est produite et qu'il devra réessayer. Il peut être nécessaire de signaler toutes les erreurs que nous pouvons trouver dans l'entrée, pas seulement la première.
Dans ce cas, j'ai soutenu que nous devrions lancer une exception. Il a exprimé son désaccord en disant: "Les exceptions devraient être exceptionnelles: il est prévu que l'utilisateur puisse saisir des données non valides, ce n'est donc pas un cas exceptionnel" Je ne savais pas vraiment comment argumenter, car, par définition, il semble avoir raison.
Mais je crois comprendre que c’est la raison pour laquelle Exceptions a été inventée. Auparavant, vous deviez examiner le résultat pour voir si une erreur s'était produite. Si vous omettez de vérifier, de mauvaises choses peuvent arriver sans que vous vous en rendiez compte.
Sans exception, chaque niveau de la pile doit vérifier le résultat des méthodes qu'il appelle et si un programmeur oublie de vérifier l'un de ces niveaux, le code pourrait accidentellement être utilisé et enregistrer des données non valides (par exemple). Cela semble plus sujet aux erreurs de cette façon.
Quoi qu'il en soit, n'hésitez pas à corriger tout ce que j'ai dit ici. Ma question principale est de savoir si quelqu'un dit que les exceptions doivent être exceptionnelles, comment savoir si mon cas est exceptionnel?
la source
Réponses:
Des exceptions ont été inventées pour faciliter la gestion des erreurs avec moins d'encombrement du code. Vous devriez les utiliser dans les cas où ils facilitent la gestion des erreurs avec moins d'encombrement du code. Cette activité "exceptions uniquement dans des circonstances exceptionnelles" découle d'une époque où la gestion des exceptions était considérée comme une perte de performances inacceptable. Ce n'est plus le cas dans la grande majorité du code, mais les gens énoncent toujours la règle sans s'en rappeler la raison.
Surtout en Java, qui est peut-être le langage le plus cher aux exceptions jamais conçu, vous ne devriez pas vous sentir mal à l'aise d'utiliser des exceptions lorsque cela simplifie votre code. En fait, la
Integer
classe Java n'a pas le moyen de vérifier si une chaîne est un entier valide sans générer potentiellement unNumberFormatException
.En outre, bien que vous ne puissiez pas compter uniquement sur la validation de l'interface utilisateur, gardez à l'esprit que si votre interface utilisateur est correctement conçue, par exemple en utilisant une roulette pour entrer des valeurs numériques courtes, une valeur non numérique le faisant passer à l'arrière-plan serait véritablement un élément clé. condition exceptionnelle.
la source
throw new ...
. Ou lancez des exceptions personnalisées, où fillInStackTrace () est écrasé. Ensuite, vous ne devriez pas remarquer de dégradation des performances, sans parler de hits .if (foo() == ERROR) { return ERROR; } else { // continue }
à tous les niveaux. Si vous lancez une exception non contrôlée, il n'y a pas de "bruit en cas d'erreur". De même, si vous transmettez des fonctions en tant qu'arguments, l'utilisation d'un code d'erreur peut modifier la signature de la fonction en un type incompatible, même si l'erreur ne peut pas se produire.Quand faut-il lever une exception? En ce qui concerne le code, je pense que l'explication suivante est très utile:
Une exception est lorsqu'un membre ne parvient pas à terminer la tâche qu'il est supposé effectuer comme indiqué par son nom . (Jeffry Richter, CLR via C #)
Pourquoi est-ce utile? Cela suggère que cela dépend du contexte lorsque quelque chose doit être traité comme une exception ou non. Au niveau des appels de méthode, le contexte est donné par (a) le nom, (b) la signature de la méthode et (b) le code client, qui utilise ou est censé utiliser la méthode.
Pour répondre à votre question, vous devriez jeter un coup d’œil sur le code, où l’entrée utilisateur est traitée. Cela pourrait ressembler à quelque chose comme ça:
Le nom de la méthode suggère-t-il qu'une validation est effectuée? Non. Dans ce cas, un PersonData non valide doit générer une exception.
Supposons que la classe ait une autre méthode qui ressemble à ceci:
Le nom de la méthode suggère-t-il qu'une validation est effectuée? Oui. Dans ce cas, un PersonData non valide ne devrait pas déclencher une exception.
Pour mettre les choses ensemble, les deux méthodes suggèrent que le code client devrait ressembler à ceci:
Lorsqu'il n'est pas clair si une méthode doit générer une exception, il est peut-être dû à un nom ou une signature de méthode mal choisi. Peut-être que la conception de la classe n'est pas claire. Parfois, vous devez modifier la conception du code pour obtenir une réponse claire à la question de savoir si une exception doit être levée ou non.
la source
struct
"ValidationResult" et structuré mon code comme vous le décrivez.Validate
(renvoyant False si invalide) et une fois pendantSave
(levant une exception spécifique et bien documentée si invalide). Bien entendu, le résultat de la validation pourrait être mis en cache à l'intérieur de l'objet, mais cela ajouterait une complexité supplémentaire, car le résultat de la validation devrait être invalidé lors des modifications.Validate()
soit appelée dans laSave()
méthode et que des détails spécifiques de laValidationResult
puissent être utilisés pour construire un message approprié pour l'exception.Sur cet argument:
Vous devez vous attendre à toute exception que vous attrapez, car vous avez décidé de l'attraper. Et donc, par cette logique, vous ne devriez jamais lancer une exception que vous avez réellement l'intention d'attraper.
Par conséquent, je pense que "les exceptions devraient être exceptionnelles" est une règle empirique terrible.
Ce que vous devez faire dépend de la langue. Différentes langues ont des conventions différentes sur le moment où des exceptions doivent être levées. Python, par exemple, lève des exceptions pour tout et quand je suis en Python, je vous suis. C ++, de son côté, jette relativement peu d'exceptions, et je fais de même. Vous pouvez traiter C ++ ou Java comme Python et générer des exceptions pour tout, mais votre travail ne correspond pas à la manière dont le langage s'attend à être utilisé.
Je préfère l'approche de Python, mais je pense que c'est une mauvaise idée d'y intégrer d'autres langues.
la source
"exceptions should be exceptional" is a terrible rule of thumb.
Bien dit! C'est une de ces choses que les gens répètent sans y penser.Je pense toujours à des choses comme accéder à un serveur de base de données ou à une API Web en pensant aux exceptions. Vous vous attendez à ce que l'API serveur / Web fonctionne, mais dans des cas exceptionnels, cela pourrait ne pas arriver (serveur en panne) Une demande Web peut généralement être rapide, mais dans des circonstances exceptionnelles (forte charge), elle peut expirer. Ceci est quelque chose hors de votre contrôle.
Les données d’entrée de vos utilisateurs sont sous votre contrôle, car vous pouvez vérifier ce qu’ils envoient et en faire ce que vous voulez. Dans votre cas, je validerais la saisie de l'utilisateur avant même d'essayer de la sauvegarder. Et j’ai tendance à penser que les utilisateurs qui fournissent des données non valides doivent être attendus et que votre application doit en tenir compte en validant l’entrée et en fournissant le message d’erreur convivial.
Cela dit, j’utilise des exceptions dans la plupart de mes modificateurs de domaine, où il ne devrait y avoir aucune chance que des données non valides soient stockées. Cependant, il s’agit d’une dernière ligne de défense et j’ai tendance à construire mes formulaires d’entrée avec de riches règles de validation. , de sorte qu'il n'y a pratiquement aucune chance de déclencher cette exception de modèle de domaine. Ainsi, lorsqu'un passeur attend une chose et en obtient une autre, il s'agit d'une situation exceptionnelle, qui n'aurait pas dû se produire dans des circonstances ordinaires.
EDIT (autre chose à considérer):
Lorsque vous envoyez des données fournies par l'utilisateur à la base de données, vous savez à l'avance ce que vous devez ou ne pas entrer dans vos tables. Cela signifie que les données peuvent être validées par rapport au format attendu. C'est quelque chose que vous pouvez contrôler. Ce que vous ne pouvez pas contrôler, c’est que votre serveur tombe en panne au milieu de votre requête. Donc, vous savez que la requête est correcte et que les données sont filtrées / validées, vous essayez la requête et elle échoue toujours, il s'agit d'une situation exceptionnelle.
De même, avec les demandes Web, vous ne pouvez pas savoir si la demande expire ou si la connexion échoue avant que vous ne l'envoyiez. Donc, cela justifie également une approche try / catch, car vous ne pouvez pas demander au serveur s'il fonctionnera quelques millisecondes plus tard lorsque vous envoyez la demande.
la source
FileNotFoundException
quand la mauvaise entrée sera donnée (par exemple, un nom de fichier inexistant). C’est le seul moyen valable de menacer cette erreur. Que pouvez-vous faire de plus sans pour autant renvoyer des codes d’erreur?Référence
Du programmeur pragmatique:
Ils examinent ensuite l’exemple d’ouverture d’un fichier en lecture, et le fichier n’existe pas - cela devrait-il lever une exception?
Plus tard, ils expliquent pourquoi ils ont choisi cette approche:
En ce qui concerne votre situation
Votre question se résume à "Les erreurs de validation peuvent-elles générer des exceptions?" La réponse est que cela dépend du lieu où se déroule la validation.
Si la méthode en question se trouve dans une section du code où il est supposé que les données d'entrée ont déjà été validées, les données d'entrée non valides doivent générer une exception. Si le code est conçu de manière à ce que cette méthode reçoive la saisie exacte entrée par un utilisateur, des données non valides sont attendues et aucune exception ne doit être déclenchée.
la source
Il y a beaucoup de pontification philosophique ici, mais d'une manière générale, les conditions exceptionnelles sont simplement les conditions que vous ne pouvez pas ou ne voulez pas gérer (autres que le nettoyage, le signalement des erreurs, etc.) sans intervention de l'utilisateur. En d'autres termes, ce sont des conditions irrécupérables.
Si vous remettez un chemin d'accès à un programme, avec l'intention de traiter ce fichier d'une manière ou d'une autre, et que le fichier spécifié par ce chemin n'existe pas, il s'agit d'une condition exceptionnelle. Vous ne pouvez rien faire à ce sujet dans votre code, si ce n’est le signaler à l’utilisateur et lui permettre de spécifier un chemin de fichier différent.
la source
Vous devez prendre en compte deux préoccupations:
vous discutez d'un problème unique - appelons-le,
Assigner
puisqu'il s'agit d'affecter des entrées à des objets structurés - et vous exprimez la contrainte que ses entrées soient validesune interface utilisateur bien implémentée présente un autre problème: la validation des entrées de l'utilisateur et un retour constructif sur les erreurs (appelons cette partie
Validator
)Du point de vue du
Assigner
composant, le lancement d'une exception est tout à fait raisonnable, car vous avez exprimé une contrainte qui a été violée.Du point de vue de l' expérience utilisateur , l'utilisateur ne devrait pas parler directement à cela
Assigner
en premier lieu. Ils devraient lui parler via leValidator
.Maintenant, dans la
Validator
saisie utilisateur non valide n’est pas un cas exceptionnel, c’est vraiment le cas qui vous intéresse le plus. Donc ici une exception ne serait pas appropriée, et c’est aussi là que vous voudriez identifier toutes les erreurs plutôt que renflouer le premier.Vous remarquerez que je n'ai pas mentionné comment ces préoccupations sont mises en œuvre. Il semble que vous parlez de la
Assigner
et votre collègue parle d'une combinaisonValidator+Assigner
. Une fois que vous réalisez qu'il y a deux préoccupations distinctes (ou séparables), vous pouvez au moins en discuter de manière sensée.Pour répondre au commentaire de Renan, je suppose simplement qu'une fois que vous avez identifié vos deux préoccupations distinctes, il est évident que les cas doivent être considérés comme exceptionnels dans chaque contexte.
En fait, s'il n'est pas évident de savoir si quelque chose doit être considéré comme exceptionnel, je dirais que vous n'avez probablement pas fini d'identifier les préoccupations indépendantes dans votre solution.
Je suppose que cela fait la réponse directe à
continue à simplifier jusqu'à ce que ce soit évident . Lorsque vous comprenez bien une pile de concepts simples que vous comprenez bien, vous pouvez clairement les raisonner pour les recomposer en code, en classes, en bibliothèques ou autre.
la source
D'autres ont bien répondu, mais voici ma réponse courte. L'exception est la situation où quelque chose dans l'environnement a mal tourné, que vous ne pouvez pas contrôler et que votre code ne peut pas aller de l'avant. Dans ce cas, vous devrez également informer l'utilisateur de ce qui ne va pas, de la raison pour laquelle vous ne pouvez pas aller plus loin et de la résolution.
la source
Je n’ai jamais été un grand partisan du conseil selon lequel il ne faut lancer des exceptions que dans des cas exceptionnels, en partie parce qu’il ne dit rien (c’est comme si on disait de ne manger que des aliments comestibles), mais aussi est très subjectif, et il est souvent difficile de savoir ce qui constitue un cas exceptionnel et ce qui ne l’est pas.
Cependant, il y a de bonnes raisons pour ce conseil: le lancement et la capture des exceptions sont lents, et si vous exécutez votre code dans le débogueur de Visual Studio avec le paramètre de vous avertir chaque fois qu'une exception est levée, vous pouvez être spammé par dizaines. sinon des centaines de messages bien avant que vous arriviez au problème.
Donc, en règle générale, si:
alors votre code ne devrait jamais lancer une exception, même une qui sera interceptée plus tard. Pour intercepter des données non valides, vous pouvez utiliser des validateurs au niveau de l'interface utilisateur ou dans un code tel que
Int32.TryParse()
dans la couche de présentation.Pour toute autre chose, vous devriez vous en tenir au principe selon lequel une exception signifie que votre méthode ne peut pas faire ce que son nom dit. En général, il est déconseillé d'utiliser des codes de retour pour indiquer un échec (sauf si le nom de votre méthode l'indique clairement, par exemple
TryParse()
) pour deux raisons. Premièrement, la réponse par défaut à un code d'erreur est d'ignorer la condition d'erreur et de continuer malgré tout. deuxièmement, vous pouvez très facilement vous retrouver avec certaines méthodes utilisant des codes de retour et d'autres méthodes utilisant des exceptions, en oubliant lesquelles. J'ai même vu des bases de code où deux implémentations interchangeables différentes de la même interface adoptent des approches différentes ici.la source
Les exceptions devraient représenter des conditions que le code d'appel immédiat ne sera probablement pas préparé à gérer, même si la méthode d'appel le permet. Considérons, par exemple, le code qui lit certaines données d'un fichier, peut légitimement supposer que tout fichier valide se termine par un enregistrement valide et qu'il n'est pas nécessaire d'extraire des informations d'un enregistrement partiel.
Si la routine de lecture de données n'utilisait pas d'exceptions mais indiquait simplement si la lecture avait réussi ou non, le code appelant devrait ressembler à ceci:
etc. dépensant trois lignes de code pour chaque travail utile. En revanche, si
readInteger
lève une exception lorsque la fin d’un fichier est rencontrée et si l’appelant peut simplement transmettre l’exception, le code devient:Beaucoup plus simple et plus propre, en mettant beaucoup plus l'accent sur le cas où les choses fonctionnent normalement. Notez que dans les cas où l'appelant immédiat serait être ESPÉRANT gérer une condition, une méthode qui renvoie un code d'erreur sera souvent plus utile que celui qui jette une exception. Par exemple, pour additionner tous les entiers d'un fichier:
contre
Le code qui demande les entiers s'attend à ce que l'un de ces appels échoue. Avoir le code utilise une boucle sans fin qui s'exécutera jusqu'à ce que cela se produise est beaucoup moins élégant que d'utiliser une méthode qui indique les échecs via sa valeur de retour.
Comme les classes ne savent souvent pas à quelles conditions leurs clients s'attendent ou ne s'attendent pas, il est souvent utile de proposer deux versions de méthodes qui peuvent échouer de manière attendue par certains appelants et par d'autres non. Cela permettra à ces méthodes d’être utilisées proprement avec les deux types d’appelants. Notez également que même les méthodes "try" devraient générer des exceptions si l'appelant ne s'y attend pas. Par exemple,
tryReadInteger
ne devrait pas lancer d’exception s’il rencontre une condition de fin de fichier propre (si l’appelant ne s’y attendait pas, il aurait utiliséreadInteger
). D'autre part, il devrait probablement lever une exception si les données ne peuvent pas être lues, par exemple parce que la clé USB contenant ces données était débranchée. Bien que de tels événements devraient toujours être reconnus comme une possibilité, il est peu probable que le code d’appel immédiat soit prêt à faire quoi que ce soit d’utile en réponse; il ne devrait certainement pas être signalé de la même manière que ce serait une condition de fin de fichier.la source
La chose la plus importante dans l'écriture d'un logiciel est de le rendre lisible. Toutes les autres considérations sont secondaires, y compris le rendre efficace et correct. S'il est lisible, le reste peut être traité lors de la maintenance, et s'il n'est pas lisible, mieux vaut le jeter. Par conséquent, vous devriez lancer des exceptions lorsque cela améliore la lisibilité.
Lorsque vous écrivez un algorithme, pensez simplement à la personne du futur qui le lira. Lorsque vous arrivez à un endroit où il pourrait y avoir un problème potentiel, demandez-vous si le lecteur veut voir comment vous gérez ce problème maintenant ou s'il préfèrerait simplement utiliser l'algorithme.
J'aime penser à une recette de gâteau au chocolat. Quand on vous dit d'ajouter les œufs, vous avez le choix: vous pouvez soit supposer que vous avez des œufs et continuer avec la recette, soit vous pouvez commencer à expliquer comment vous pouvez obtenir des œufs si vous n'en avez pas. Cela pourrait remplir tout un livre de techniques de chasse au poulet sauvage, le tout pour vous aider à faire un gâteau. C'est bien, mais la plupart des gens ne voudront pas lire cette recette. La plupart des gens préfèrent simplement supposer que les œufs sont disponibles et continuer avec la recette. C'est un jugement que les auteurs doivent faire lorsqu'ils écrivent des recettes.
Il ne peut y avoir de règles garanties sur ce qui constitue une bonne exception et quels problèmes doivent être traités immédiatement, car cela nécessite de lire dans l'esprit de votre lecteur. Le mieux que vous puissiez faire est de suivre des règles empiriques, et "les exceptions ne sont que dans des circonstances exceptionnelles" est un très bon exemple Habituellement, lorsqu'un lecteur lit votre méthode, il cherche ce qu'il fera 99% du temps. Il préfère ne pas être encombré de cas bizarres comme traiter les utilisateurs qui saisissent des entrées illégales et d'autres événements qui ne se produisent presque jamais. Ils veulent voir le flux normal de votre logiciel disposé directement, instruction après instruction, comme si aucun problème ne se produisait.
la source
C'est pourquoi vous ne pouvez pas lancer d'exception ici. Une exception interrompt immédiatement le processus de validation. Il y aurait donc beaucoup de travail à faire pour y arriver.
Un mauvais exemple:
Méthode de validation pour la
Dog
classe utilisant des exceptions:Comment l'appeler:
Le problème ici est que le processus de validation, pour obtenir toutes les erreurs, nécessiterait de sauter les exceptions déjà trouvées. Ce qui précède pourrait fonctionner, mais il s’agit clairement d’un abus des exceptions . Le type de validation qui vous a été demandé devrait avoir lieu avant que la base de données ne soit touchée. Il n’est donc pas nécessaire de revenir en arrière. Et, les résultats de la validation risquent d’être des erreurs de validation (espérons-le nuls, cependant).
La meilleure approche est:
Appel de méthode:
Méthode de validation:
Pourquoi? Il y a des tonnes de raisons, et la plupart ont été mentionnées dans les autres réponses. Pour le dire simplement: il est beaucoup plus simple de lire et de comprendre les autres. Deuxièmement, voulez-vous montrer à l'utilisateur des traces de pile pour lui expliquer qu'il l'a
dog
mal configuré ?Si , lors de la validation dans le deuxième exemple, une erreur survient toujours , même si votre validateur a validé les
dog
problèmes avec zéro problème, le lancement d'une exception est la bonne chose . Comme: pas de connexion à la base de données, l'entrée de la base de données a été modifiée par quelqu'un d'autre entre temps, ou autre.la source