On m'a dit que les exceptions ne devraient être utilisées que dans des cas exceptionnels. Comment savoir si mon cas est exceptionnel?

99

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?

Daniel Kaplan
la source
3
doublon possible? Quand lancer une exception . Bien que c'était fermé là-bas, mais je pense que cela convient ici. C'est encore un peu de philosophie, certaines personnes et certaines communautés ont tendance à considérer les exceptions comme une sorte de contrôle de flux.
Thorsten Müller
8
Lorsque les utilisateurs sont stupides, ils fournissent une entrée non valide. Lorsque les utilisateurs sont intelligents, ils jouent en fournissant une entrée non valide. Par conséquent, une entrée d'utilisateur non valide n'est pas une exception.
mouviciel
7
Aussi, ne confondez pas une exception , qui est un mécanisme très spécifique en Java et .NET, avec une erreur qui est un terme beaucoup plus générique. La gestion des erreurs ne se limite pas à lancer des exceptions. Cette discussion aborde les nuances entre les exceptions et les erreurs .
Eric King
4
"Exceptional"! = "Cela se produit rarement"
ConditionRacer
3
Je trouve que les exceptions Vexing d' Eric Lippert sont un conseil décent.
Brian

Réponses:

87

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 Integerclasse Java n'a pas le moyen de vérifier si une chaîne est un entier valide sans générer potentiellement un NumberFormatException.

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.

Karl Bielefeldt
la source
10
Bon coup, là. En fait, dans l'application réelle que j'ai conçue, l'impact sur les performances a fait une différence et j'ai dû la modifier pour ne pas générer d'exceptions pour certaines opérations d'analyse.
Robert Harvey
17
Je ne dis pas qu'il n'y a pas encore de cas où la performance est une raison valable, mais ces cas sont l'exception (jeu de mots) plutôt que la règle.
Karl Bielefeldt le
11
@RobertHarvey Le problème en Java est de lancer des objets exception préfabriqués, plutôt que 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 .
Ingo
3
+1: Exactement ce que j'allais répondre. Utilisez-le quand cela simplifie le code. Les exceptions peuvent donner un code beaucoup plus clair où vous n'avez pas besoin de vérifier les valeurs de retour à chaque niveau de la pile d'appels. (Comme pour tout le reste, cependant, si vous l'utilisez mal, votre code risque de devenir un horrible foutoir.)
Leo
4
@Brendan Supposons qu'une exception se produise et que le code de traitement des erreurs se situe à 4 niveaux inférieurs dans la pile d'appels. Si vous utilisez un code d'erreur, les 4 fonctions situées au-dessus du gestionnaire doivent avoir le type du code d'erreur comme valeur de retour et vous devez créer une chaîne 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.
Doval
72

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:

public void Save(PersonData personData) {  }

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:

public ValidationResult Validate(PersonData personData) {  }

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:

ValidationResult validationResult = personRegister.Validate(personData);
if (validationResult.IsValid())
{
    personRegister.Save(personData)
}
else
{
    // Throw an exception? To answer this look at the context!
    // That is: (a) Method name, (b) signature and
    // (c) where this method is (expected) to be used.
}

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.

Theo Lenndorff
la source
Hier encore, j'ai créé un struct"ValidationResult" et structuré mon code comme vous le décrivez.
paul
4
Il n’est pas utile de répondre à votre question, mais j’aimerais simplement souligner que vous avez implicitement ou délibérément suivi le principe de séparation Commande-requête ( en.wikipedia.org/wiki/Command-query_separation ). ;-)
Theo Lenndorff le
Bonne idée! Un inconvénient: dans votre exemple, la validation est en réalité effectuée deux fois: une fois pendant Validate(renvoyant False si invalide) et une fois pendant Save(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.
Heinzi
@ Heinzi je suis d'accord. Elle pourrait être refactorisée de sorte qu'elle Validate()soit appelée dans la Save()méthode et que des détails spécifiques de la ValidationResultpuissent être utilisés pour construire un message approprié pour l'exception.
Phil
3
C'est mieux que la réponse acceptée, je pense. Lancer lorsque l'appel ne peut pas faire la chose qu'il était censé faire.
Andy
31

Les exceptions doivent être exceptionnelles: il est prévu que l'utilisateur saisisse des données non valides, il ne s'agit donc pas d'un cas exceptionnel.

Sur cet argument:

  • On s'attend à ce qu'un fichier n'existe pas. Ce n'est donc pas un cas exceptionnel.
  • On s'attend à ce que la connexion au serveur soit perdue. Ce n'est donc pas un cas exceptionnel.
  • On s'attend à ce que le fichier de configuration soit corrompu, ce qui ne constitue pas un cas exceptionnel.
  • Il est prévu que votre demande peut parfois tomber, alors ce n'est pas un cas exceptionnel.

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.

Winston Ewert
la source
1
@gn, je sais. Mon idée était que vous devriez suivre les conventions du langage (dans ce cas Java) même si elles ne sont pas vos préférées.
Winston Ewert
6
+1 "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.
Andres F.
2
Le terme "attendu" est défini non pas par un argument ou une convention subjectif, mais par le contrat de l'API / de la fonction (cela peut être explicite, mais simplement implicite). Différentes fonctions / API / sous-systèmes peuvent avoir différentes attentes. Par exemple, pour certaines fonctionnalités de niveau supérieur, un fichier non existant doit être traité (il peut le signaler à un utilisateur via une interface graphique), pour d'autres fonctions de niveau inférieur probablement pas (et devrait donc jeter une exception). Cette réponse semble passer à côté de ce point important ....
mardi
1
@ mikera, oui une fonction devrait (uniquement) jeter les exceptions définies dans son contrat. Mais ce n'est pas la question. La question est de savoir comment vous décidez ce que ce contrat devrait être. Je maintiens que la règle de base, "les exceptions devraient être exceptionnelles" n'est pas utile pour prendre cette décision.
Winston Ewert
1
@supercat, je ne pense pas que ce qui compte vraiment soit le plus répandu. Je pense que la question critique est d'avoir un défaut sain d'esprit. Si je ne gère pas explicitement la condition d'erreur, mon code prétend-il que rien ne s'est passé ou est-ce que je reçois un message d'erreur utile?
Winston Ewert
30

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.

Ivan Pintar
la source
8
Mais pourquoi? Pourquoi les exceptions sont-elles moins utiles pour traiter les problèmes plus attendus?
Winston Ewert le
6
@Pinetree, vérifier l'existence d'un fichier avant de l'ouvrir est une mauvaise idée. Le fichier peut cesser d’exister entre la vérification et l’ouverture, il ne peut disposer d’autorisation lui permettant de l’ouvrir, et le fait de vérifier son existence, puis d’ouvrir le fichier, nécessitera deux appels système onéreux. Il est préférable d’essayer d’ouvrir le fichier et de s’en tenir à l’échec.
Winston Ewert
10
Autant que je sache, pratiquement tous les échecs possibles sont mieux gérés, comme une récupération après l'échec plutôt que d'essayer de vérifier le succès avant de commencer. Que vous utilisiez ou non des exceptions ou quelque chose d'autre indique que l'échec est un problème distinct. Je préfère les exceptions parce que je ne peux pas les ignorer accidentellement.
Winston Ewert le
11
Je ne partage pas votre idée selon laquelle, étant donné que des données utilisateur non valides sont attendues, elles ne peuvent pas être considérées comme exceptionnelles. Si j'écris un analyseur et que quelqu'un lui fournit des données non analysables, il s'agit d'une exception. Je ne peux pas continuer à analyser. La façon dont l'exception est gérée est une autre question.
ConditionRacer
4
File.ReadAllBytes jettera a FileNotFoundExceptionquand 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?
oɔɯǝɹ
16

Référence

Du programmeur pragmatique:

Nous pensons que les exceptions devraient rarement être utilisées dans le cadre du flux normal d'un programme. les exceptions doivent être réservées aux événements imprévus. Supposons qu'une exception non capturée mettra fin à votre programme et demandez-vous: "Ce code sera-t-il toujours exécuté si je supprime tous les gestionnaires d'exceptions?" Si la réponse est "non", alors peut-être que des exceptions sont utilisées dans des circonstances non exceptionnelles.

Ils examinent ensuite l’exemple d’ouverture d’un fichier en lecture, et le fichier n’existe pas - cela devrait-il lever une exception?

Si le fichier aurait dû être là, une exception est justifiée. [...] D'autre part, si vous ne savez pas si le fichier doit exister ou non, il ne semble pas exceptionnel si vous ne le trouvez pas et un retour d'erreur est approprié.

Plus tard, ils expliquent pourquoi ils ont choisi cette approche:

[Une] exception représente un transfert de contrôle immédiat et non local. C'est une sorte de cascade goto. Les programmes qui utilisent des exceptions dans le cadre de leur traitement normal souffrent de tous les problèmes de lisibilité et de maintenabilité du code spaghetti classique. Ces programmes interrompent l’encapsulation: les routines et leurs appelants sont plus étroitement couplés via la gestion des exceptions.

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.

Mike Partridge
la source
11

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.

Robert Harvey
la source
1
+1, très proche de ce que j'allais dire. Je dirais que c'est davantage une question de portée et que cela n'a vraiment rien à voir avec l'utilisateur. Un bon exemple de ceci est la différence entre les deux fonctions .Net int.Parse et int.TryParse, la première n'a d'autre choix que de lancer une exception sur une mauvaise entrée, la dernière ne devrait jamais lancer une exception
jmoreno le
1
@ jmoreno: Donc, vous utiliseriez TryParse lorsque le code pourrait faire quelque chose à propos de la condition imparable, et Parse lorsqu'il ne le pouvait pas.
Robert Harvey
7

Vous devez prendre en compte deux préoccupations:

  1. vous discutez d'un problème unique - appelons-le, Assignerpuisqu'il s'agit d'affecter des entrées à des objets structurés - et vous exprimez la contrainte que ses entrées soient valides

  2. une 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 Assignercomposant, 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 Assigneren premier lieu. Ils devraient lui parler via le Validator.

Maintenant, dans la Validatorsaisie 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 Assigneret votre collègue parle d'une combinaison Validator+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 à

... comment savoir si mon cas est exceptionnel?

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.

Inutile
la source
-1 Oui, il y a deux préoccupations, mais cela ne répond pas à la question "Comment savoir si mon cas est exceptionnel?"
RMalke
Le fait est que le même cas peut être exceptionnel dans un contexte, et non dans un autre. Identifier le contexte dont vous parlez réellement (plutôt que de les confondre) répond à la question.
Inutile
... en fait, peut-être que non - j'ai plutôt répondu à votre question dans ma réponse.
Inutile
4

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.

Manoj R
la source
3

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:

  • votre code est sans bug, et
  • les services dont il dépend sont tous disponibles, et
  • votre utilisateur utilise votre programme de la manière dont il était destiné à être utilisé (même si une partie de l'entrée qu'il fournit est invalide)

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.

confitures
la source
2

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:

temp = dataSource.readInteger();
if (temp == null) return null;
field1 = (int)temp;
temp = dataSource.readInteger();
if (temp == null) return null;
field2 = (int)temp;
temp = dataSource.readString();
if (temp == null) return null;
field3 = temp;

etc. dépensant trois lignes de code pour chaque travail utile. En revanche, si readIntegerlève une exception lorsque la fin d’un fichier est rencontrée et si l’appelant peut simplement transmettre l’exception, le code devient:

field1 = dataSource.readInteger();
field2 = dataSource.readInteger();
field3 = dataSource.readString();

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:

do
{
  temp = dataSource.tryReadInteger();
  if (temp == null) break;
  total += (int)temp;
} while(true);

contre

try
{
  do
  {
    total += (int)dataSource.readInteger();
  }
  while(true);
}
catch endOfDataSourceException ex
{ // Don't do anything, since this is an expected condition (eventually)
}

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, tryReadIntegerne 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.

supercat
la source
2

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.

Géo
la source
2

Il peut être nécessaire de signaler toutes les erreurs que nous pouvons trouver dans l'entrée, pas seulement la première.

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 Dogclasse utilisant des exceptions:

void validate(Set<DogValidationException> previousExceptions) {
    if (!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        DogValidationException disallowedName = new DogValidationException(Problem.DISALLOWED_DOG_NAME);
        if (!previousExceptions.contains(disallowedName)){
            throw disallowedName;
        }
    }
    if (this.legs < 4) {
        DogValidationException invalidDog = new DogValidationException(Problem.LITERALLY_INVALID_DOG);
        if (!previousExceptions.contains(invalidDog)){
            throw invalidDog;
        }
    }
    // etc.
}

Comment l'appeler:

Set<DogValidationException> exceptions = new HashSet<DogValidationException>();
boolean retry;
do {
    retry = false;
    try {
        dog.validate(exceptions);
    } catch (DogValidationException e) {
        exceptions.add(e);
        retry = true;
    }
} while (retry);

if(exceptions.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

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:

Set<Problem> validationResults = dog.validate();
if(validationResults.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

Méthode de validation:

Set<Problem> validate() {
    Set<Problem> result = new HashSet<Problem>();
    if(!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        result.add(Problem.DISALLOWED_DOG_NAME);
    }
    if(this.legs < 4) {
        result.add(Problem.LITERALLY_INVALID_DOG);
    }
    // etc.
    return result;
}

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 dogmal configuré ?

Si , lors de la validation dans le deuxième exemple, une erreur survient toujours , même si votre validateur a validé les dogproblè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.

Matthias Ronge
la source