J'ai récemment commencé à travailler dans un endroit avec des développeurs beaucoup plus âgés (environ 50 ans et plus). Ils ont travaillé sur des applications critiques de l’aviation où le système ne pouvait pas tomber en panne. En conséquence, le programmeur le plus âgé a tendance à coder de cette façon.
Il a tendance à mettre un booléen dans les objets pour indiquer si une exception doit être levée ou non.
Exemple
public class AreaCalculator
{
AreaCalculator(bool shouldThrowExceptions) { ... }
CalculateArea(int x, int y)
{
if(x < 0 || y < 0)
{
if(shouldThrowExceptions)
throwException;
else
return 0;
}
}
}
(Dans notre projet, la méthode peut échouer car nous essayons d'utiliser un périphérique réseau qui ne peut pas être présent à ce moment-là. L'exemple de zone n'est qu'un exemple de l'indicateur d'exception)
Pour moi, cela ressemble à une odeur de code. L'écriture de tests unitaires devient légèrement plus complexe car vous devez tester l'indicateur d'exception à chaque fois. De plus, si quelque chose ne va pas, ne voudriez-vous pas savoir tout de suite? L'appelant ne devrait-il pas avoir la responsabilité de déterminer comment continuer?
Sa logique / raisonnement est que notre programme doit faire une chose, montrer des données à l'utilisateur. Toute autre exception qui ne nous empêche pas de le faire doit être ignorée. Je conviens qu’ils ne devraient pas être ignorés, mais devraient faire l’objet d’une bulle, être gérés par la personne appropriée, et ne pas avoir à traiter avec des drapeaux pour cela.
Est-ce une bonne façon de gérer les exceptions?
Edit : Juste pour donner plus de contexte à la décision de conception, je suppose que c’est parce que si ce composant échoue, le programme peut toujours fonctionner et s’acquitter de sa tâche principale. Ainsi, nous ne voudrions pas lancer une exception (et ne pas la gérer?) Et la supprimer du programme lorsque, pour l'utilisateur, cela fonctionne bien
Edit 2 : Pour donner encore plus de contexte, dans notre cas, la méthode est appelée pour réinitialiser une carte réseau. Le problème se pose lorsque la carte réseau est déconnectée et reconnectée, une adresse IP différente lui est attribuée. Réinitialisation générera une exception car nous essayons de réinitialiser le matériel avec l'ancienne adresse IP.
Réponses:
Le problème avec cette approche est que, même si des exceptions ne sont jamais levées (et donc, l’application ne se bloque jamais en raison d’exceptions non capturées), les résultats renvoyés ne sont pas nécessairement corrects et il se peut que l’utilisateur ne sache jamais qu’il ya un problème avec les données (ou quel est ce problème et comment le corriger).
Pour que les résultats soient corrects et significatifs, la méthode appelante doit rechercher des nombres spéciaux dans le résultat, c'est-à-dire des valeurs de retour spécifiques utilisées pour signaler les problèmes survenus lors de l'exécution de la méthode. Les nombres négatifs (ou zéro) renvoyés pour des quantités définies positives (comme la surface) en sont un excellent exemple dans un code plus ancien. Si la méthode appelante ne sait pas (ou n'oublie pas!) De vérifier ces numéros spéciaux, le traitement peut continuer sans que personne ne se rende compte de l'erreur. Les données sont ensuite affichées à l'utilisateur, montrant une zone de 0, que l'utilisateur sait être incorrecte, mais qui n'ont aucune indication sur ce qui a mal tourné, où et pourquoi. Ils se demandent alors si l'une des autres valeurs est fausse ...
Si l’exception était levée, le traitement s’arrêterait, l’erreur serait (idéalement) consignée et l’utilisateur pourrait en être averti d’une manière ou d’une autre. L'utilisateur peut alors réparer ce qui ne va pas et réessayer. Une gestion correcte des exceptions (et des tests!) Garantira que les applications critiques ne se bloquent pas ou ne finissent pas dans un état non valide.
la source
Non, je pense que c'est une très mauvaise pratique. Lancer une exception ou renvoyer une valeur est un changement fondamental de l'API, qui modifie la signature de la méthode et lui donne un comportement tout à fait différent du point de vue de l'interface.
En général, lorsque nous concevons des classes et leurs API, nous devrions considérer que
il peut y avoir plusieurs instances de la classe avec différentes configurations flottant dans le même programme au même moment, et,
En raison de l'injection de dépendance et de nombreuses autres pratiques de programmation, un client consommateur peut créer les objets et les confier à un autre utilisateur, ce qui entraîne souvent une séparation entre les créateurs et les utilisateurs d'objets.
Voyons maintenant ce que l’appelant doit faire pour utiliser une instance qui lui a été attribuée, par exemple, pour appeler la méthode de calcul: l’appelant devra à la fois vérifier la surface zéro et capturer les exceptions - ouch! Les considérations relatives aux tests ne concernent pas seulement la classe elle-même, mais également la gestion des erreurs des appelants ...
Nous devrions toujours rendre les choses aussi simples que possible pour le client consommateur; cette configuration booléenne dans le constructeur qui modifie l'API d'une méthode d'instance est le contraire qui fait que le programmeur client consommateur (peut-être vous ou votre collègue) tombe dans le gouffre du succès.
Pour offrir les deux API, il est bien mieux et plus normal de fournir deux classes différentes: une classe générant toujours une erreur et une autre renvoyant toujours 0 en cas d'erreur, ou bien deux méthodes différentes avec une seule classe. De cette manière, le client consommateur peut facilement savoir exactement comment vérifier et gérer les erreurs.
En utilisant deux classes différentes ou deux méthodes différentes, vous pouvez utiliser beaucoup plus facilement les utilisateurs de méthodes de recherche IDE et les fonctions de refactorisation, etc., car les deux cas d'utilisation ne sont plus combinés. La lecture, l'écriture, la maintenance, les revues et les tests de codes sont également plus simples.
Sur une autre note, j'estime personnellement que nous ne devrions pas prendre les paramètres de configuration booléens, où les appelants passent tous simplement une constante . Un tel paramétrage de configuration associe deux cas d'utilisation distincts sans aucun avantage réel.
Examinez votre base de code et voyez si une variable (ou une expression non constante) est utilisée pour le paramètre de configuration boolean dans le constructeur! J'en doute.
Et une autre considération est de demander pourquoi le calcul de la zone peut échouer. Le mieux serait peut-être de jeter le constructeur, si le calcul ne peut pas être fait. Toutefois, si vous ne savez pas si le calcul peut être effectué jusqu'à ce que l'objet soit initialisé davantage, envisagez peut-être d'utiliser différentes classes pour différencier ces états (non prêt à calculer une surface ou prêt à calculer).
J'ai lu que votre situation d'échec est orientée vers la communication à distance, donc peut ne pas s'appliquer; Seulement un peu de matière à réflexion.
Oui je suis d'accord. Il semble prématuré pour l'appelé de décider que la zone 0 est la bonne réponse dans les conditions d'erreur (d'autant plus que 0 est une zone valide et qu'il est donc impossible de faire la différence entre une erreur et le 0 réel, même si elle ne s'applique pas à votre application).
la source
C'est une introduction intéressante, ce qui me donne l'impression que la motivation derrière cette conception est d'éviter de jeter des exceptions dans certains contextes "parce que le système pourrait tomber en panne" . Mais si le système "peut tomber en panne à cause d'une exception", cela indique clairement que
Donc, si le programme qui l'utilise
AreaCalculator
est bogué, votre collègue préfère ne pas le faire "planter tôt", mais renvoyer une valeur erronée (en espérant que personne ne le remarque, ou en espérant que personne ne fasse quelque chose d'important avec cela). En réalité, cela masque une erreur et, d’après mon expérience, cela conduira tôt ou tard à des bogues de suivi pour lesquels il devient difficile de trouver la cause fondamentale.À mon humble avis, écrire un programme qui ne plante pas dans toutes les circonstances mais qui présente des données erronées ou des résultats de calcul erronés ne vaut généralement pas mieux que de laisser le programme planter. La seule approche appropriée consiste à donner à l'appelant une chance de remarquer l'erreur, de la gérer, de lui permettre de décider si l'utilisateur doit être informé du mauvais comportement et s'il est prudent de poursuivre tout traitement ou s'il est plus sûr. pour arrêter complètement le programme. Ainsi, je recommanderais l’un des suivants:
il est difficile d’ignorer le fait qu’une fonction peut générer une exception. La documentation et les normes de codage sont vos amis, et les révisions de code régulières devraient permettre une utilisation correcte des composants et une gestion correcte des exceptions.
formez l'équipe à attendre et à gérer les exceptions lorsqu'elles utilisent des composants de "boîte noire" et à prendre en compte le comportement global du programme.
si pour certaines raisons vous pensez que vous ne pouvez pas obtenir le code appelant (ou les développeurs qui l'écrivent) pour utiliser correctement la gestion des exceptions, vous pouvez, en dernier recours, concevoir une API avec des variables de sortie d'erreur explicites et aucune exception, comme
il est donc très difficile pour l’appelant d’ignorer que la fonction risque d’échouer. Mais c'est IMHO extrêmement moche en C #; c'est une vieille technique de programmation défensive de C qui ne fait pas exception à la règle et il ne devrait normalement pas être nécessaire de travailler de cette façon.
la source
try
/catch
intérieur d' une boucle si vous avez besoin de traitement pour continuer si un élément échoue.Toute fonction avec n paramètres sera plus difficile à tester qu'une fonction avec n-1 paramètres. Étendez cela à l'absurde et l'argument devient que les fonctions ne devraient pas avoir de paramètres du tout, car cela les rend plus faciles à tester.
Bien que ce soit une bonne idée d’écrire du code facile à tester, c’est une idée terrible de placer la simplicité du test avant l’écriture de code utile pour ceux qui doivent l’appeler. Si l'exemple dans la question comporte un commutateur qui détermine si une exception est levée ou non, il est possible que le nombre d'appelants souhaitant ce comportement ait mérité de l'ajouter à la fonction. Là où la limite entre complexe et trop complexe se situe est un jugement; quiconque essaie de vous dire qu'il y a une ligne de démarcation qui s'applique dans toutes les situations devrait être regardé avec suspicion.
Cela dépend de votre définition du mal. L'exemple de la question définit "incorrect" comme "étant donné une dimension inférieure à zéro et
shouldThrowExceptions
vraie". Etre donné une dimension si moins que zéro n'est pas faux quandshouldThrowExceptions
est faux parce que le commutateur induit un comportement différent. C'est tout simplement pas une situation exceptionnelle.Le vrai problème ici est que le commutateur a été mal nommé car il ne décrit pas ce que la fonction fait. Si on lui avait donné un meilleur nom
treatInvalidDimensionsAsZero
, aurais-tu posé cette question?L'appelant ne détermine comment continuer. Dans ce cas, il le fait à l'avance en définissant ou en effaçant
shouldThrowExceptions
et la fonction se comporte en fonction de son état.L'exemple est pathologiquement simple car il effectue un seul calcul et retourne. Si vous le rendez un peu plus complexe, tel que le calcul de la somme des racines carrées d'une liste de numéros, le lancement d'exceptions peut donner aux appelants des problèmes qu'ils ne peuvent pas résoudre. Si je transmets une liste de
[5, 6, -1, 8, 12]
et que la fonction jette une exception sur le-1
, je n'ai aucun moyen de dire à la fonction de continuer car elle aura déjà été annulée et jeté la somme. Si la liste est un ensemble de données énorme, générer une copie sans aucun nombre négatif avant d'appeler la fonction risque de ne pas être pratique. Je suis donc obligé de dire à l'avance comment les nombres non valides doivent être traités, soit sous la forme d'un "ignorez-les simplement." "changer ou peut-être fournir un lambda qui est appelé à prendre cette décision.Encore une fois, il n’existe pas de solution unique. Dans cet exemple, la fonction a vraisemblablement été écrite dans une spécification indiquant comment traiter les dimensions négatives. La dernière chose que vous souhaitiez faire est de réduire le rapport signal sur bruit de vos journaux en les remplissant de messages indiquant "normalement, une exception serait levée ici, mais l'appelant a dit de ne pas déranger."
Et en tant que programmeur beaucoup plus âgé, je vous demanderais de bien vouloir quitter votre pelouse. ;-)
la source
our program needs to do 1 thing, show data to user. Any other exception that doesn't stop us from doing so should be ignored
état d'esprit peut amener l'utilisateur à prendre une décision en se basant sur des données erronées (car le programme doit en réalité le faire 1 chose - aider l'utilisateur à prendre des décisions éclairées) 2. des cas similaires comme ceux-cibool ExecuteJob(bool throwOnError = false)
sont généralement extrêmement sujettes aux erreurs et conduisent à un code qu'il est difficile de raisonner en le lisant simplement.Les codes critiques et «normaux» en matière de sécurité peuvent donner lieu à des idées très différentes sur la «bonne pratique». Il y a beaucoup de chevauchement - certaines choses sont risquées et devraient être évitées dans les deux cas - mais il y a toujours des différences significatives. Si vous ajoutez une exigence pour garantir la réactivité, ces écarts deviennent assez importants.
Ceux-ci se rapportent souvent à des choses que vous attendez:
Pour git, la mauvaise réponse pourrait être très mauvaise par rapport à: prendre trop de temps / abandonner / se suspendre ou même se planter (ce sont en réalité des problèmes qui ne concernent pas, par exemple, la modification accidentelle du code d’enregistrement).
Toutefois, un tableau de bord dont le calcul de la force g doit caler et empêcher un calcul de la vitesse de l’air peut être inacceptable.
Certains sont moins évidents:
Si vous avez beaucoup testé , les résultats de premier ordre (comme les bonnes réponses) ne vous inquiétez pas autant. Vous savez que vos tests auront couvert cela. Cependant, s'il y avait un état caché ou un flux de contrôle, vous ne savez pas que cela ne sera pas la cause de quelque chose de beaucoup plus subtil. C'est difficile à exclure avec des tests.
Être manifestement en sécurité est relativement important. Peu de clients se demanderont si la source qu'ils achètent est sûre ou non. Si vous êtes dans le marché de l'aviation, d'autre part ...
Comment cela s'applique-t-il à votre exemple:
Je ne sais pas. Il existe un certain nombre de processus de réflexion pouvant comporter des règles principales telles que "aucun code de production" adoptées dans un code critique pour la sécurité qui seraient assez ridicules dans des situations plus habituelles.
Certaines concernent l’intégration, certaines la sécurité et peut-être d’autres… Certaines sont bonnes (performances serrées / limites de mémoire nécessaires), d’autres sont mauvaises (nous ne gérons pas les exceptions correctement, il est donc préférable de ne pas la risquer). La plupart du temps, même savoir pourquoi ils l'ont fait ne répondra pas vraiment à la question. Par exemple, s’il s’agit de faciliter la vérification du code et d’améliorer le code, est-ce une bonne pratique? Vous ne pouvez vraiment pas dire. Ce sont des animaux différents, et doivent être traités différemment.
Cela dit, cela me semble un peu suspect, MAIS :
Les logiciels critiques pour la sécurité et les décisions en matière de conception de logiciels ne devraient probablement pas être pris par des inconnus lors de l'échange de pile-à-vis des logiciels. Il y a peut-être une bonne raison de faire cela même si cela fait partie d'un mauvais système. Ne lisez pas trop dans autre chose que comme "matière à réflexion".
la source
Parfois, lancer une exception n'est pas la meilleure méthode. Notamment en raison du dépilage de pile, mais parfois parce que capturer une exception est problématique, en particulier le long des coutures de langage ou d'interface.
Un bon moyen de gérer cela consiste à renvoyer un type de données enrichi. Ce type de données a assez d'état pour décrire tous les chemins heureux et tous les chemins malheureux. Le fait est que si vous interagissez avec cette fonction (membre / global / sinon), vous serez obligé de gérer le résultat.
Cela étant dit, ce type de données enrichi ne devrait pas forcer l'action. Imaginez dans votre région par exemple quelque chose comme
var area_calc = new AreaCalculator(); var volume = area_calc.CalculateArea(x, y) * z;
. Cela semble utilevolume
devrait contenir la surface multipliée par la profondeur - ce qui pourrait être un cube, un cylindre, etc.Mais que se passe-t-il si le service area_calc est en panne? Puis
area_calc .CalculateArea(x, y)
renvoyé un type de données riche contenant une erreur. Est-il légal de multiplier cela parz
? C'est une bonne question. Vous pouvez forcer les utilisateurs à gérer la vérification immédiatement. Cela brise toutefois la logique avec la gestion des erreurs.contre
La logique essentielle est répartie sur deux lignes et divisée par le traitement des erreurs dans le premier cas, tandis que le second cas contient toute la logique pertinente sur une ligne, suivie du traitement des erreurs.
Dans ce second cas, il
volume
s'agit d'un type de données riche. Ce n'est pas juste un chiffre. Cela rend le stockage plus volumineux etvolume
nécessitera toujours une enquête pour détecter une condition d'erreur. De plus, il est possiblevolume
que d'autres calculs soient fournis avant que l'utilisateur ne décide de gérer l'erreur, ce qui lui permet de se manifester à plusieurs endroits différents. Cela peut être bon ou mauvais en fonction des spécificités de la situation.Alternativement, il
volume
pourrait ne s'agir que d'un simple type de données, d'un simple nombre, mais qu'adviendra-t-il de la condition d'erreur? Il se peut que la valeur convertisse implicitement si elle est dans un état heureux. En cas de malheur, il peut renvoyer une valeur par défaut / une erreur (pour la zone 0 ou -1, cela peut sembler raisonnable). Sinon, une exception peut être levée de ce côté de la frontière interface / langue.contre.
En transmettant une valeur mauvaise ou éventuellement mauvaise, il incombe beaucoup à l'utilisateur de valider les données. C'est une source de bogues, car pour le compilateur, la valeur de retour est un entier légitime. Si quelque chose n'a pas été vérifié, vous le découvrirez en cas de problème. Le second cas mêle le meilleur des deux mondes en permettant aux exceptions de gérer les chemins malheureux, tandis que les chemins heureux suivent un traitement normal. Malheureusement, cela oblige l'utilisateur à gérer les exceptions avec sagesse, ce qui est difficile.
Pour être clair, un chemin malheureux est un cas inconnu de la logique métier (le domaine de l’exception), ne pas valider est un chemin heureux, car vous savez comment le gérer par les règles commerciales (le domaine des règles).
La solution ultime serait celle qui autorise tous les scénarios (dans des limites raisonnables).
Quelque chose comme:
la source
Je vais répondre du point de vue de C ++. Je suis à peu près sûr que tous les concepts de base sont transférables en C #.
On dirait que votre style préféré est "jetez toujours des exceptions":
Cela peut poser problème pour le code C ++, car la gestion des exceptions est lourde : le cas d'échec est exécuté lentement, l'allocation de mémoire par le cas d'échec (qui parfois n'est même pas disponible) et rend généralement les choses moins prévisibles. La lourdeur d'EH est l'une des raisons pour lesquelles vous entendez des gens dire des choses telles que "n'utilisez pas d'exceptions pour contrôler le flux".
Ainsi, certaines bibliothèques (telles que
<filesystem>
) utilisent ce que C ++ appelle une "double API" ou ce que C # appelle leTry-Parse
modèle (merci Peter pour le conseil!)Vous pouvez voir tout de suite le problème des "doubles API": beaucoup de duplication de code, aucune indication pour les utilisateurs quant à savoir quelle API est la "bonne" à utiliser, et l'utilisateur doit faire un choix difficile entre les messages d'erreur utiles (
CalculateArea
) et speed (TryCalculateArea
) parce que la version la plus rapide prend notre"negative side lengths"
exception utile et l'aplatit en une inutilefalse
- "quelque chose s'est mal passé, ne me demandez pas quoi ou où". (Certaines API doubles utilisent un type d'erreur plus expressif, tel queint errno
ou C ++std::error_code
, mais cela ne vous dit toujours pas où l'erreur s'est produite, mais simplement qu'elle s'est produite quelque part.)Si vous ne pouvez pas décider de la manière dont votre code doit se comporter, vous pouvez toujours lancer la décision à l'appelant!
C’est essentiellement ce que fait votre collègue. sauf qu'il factorise le "gestionnaire d'erreur" dans une variable globale:
Déplacer des paramètres importants de paramètres de fonction explicites vers un état global est presque toujours une mauvaise idée. Je ne le recommande pas. (Le fait qu'il ne s'agisse pas d' un état global dans votre cas, mais simplement d' un État membre au niveau de l' instance atténue légèrement le problème, mais pas beaucoup.)
De plus, votre collègue limite inutilement le nombre de comportements possibles de gestion des erreurs. Plutôt que de permettre une erreur de traitement des erreurs, il en a décidé deux:
C’est probablement le «mauvais côté» de l’une de ces stratégies possibles. Vous avez retiré toute la flexibilité de l'utilisateur final en le forçant à utiliser l'un de vos deux rappels de gestion des erreurs; et vous avez tous les problèmes d'état global partagé; et vous payez toujours pour cette branche conditionnelle partout.
Enfin, une solution commune en C ++ (ou n’importe quel langage avec compilation conditionnelle) consisterait à obliger l’utilisateur à prendre la décision pour l’ensemble du programme, globalement, au moment de la compilation, afin que le chemin de code non utilisé puisse être entièrement optimisé:
Un exemple de quelque chose qui fonctionne de cette façon est la
assert
macro en C et C ++, qui conditionne son comportement sur la macro du préprocesseurNDEBUG
.la source
std::optional
deTryCalculateArea()
, il est simple d’unifier l’implémentation des deux parties de la double interface dans un seul modèle de fonction avec un indicateur de compilation.std::expected
. Avec justestd::optional
, à moins que je ne comprenne mal votre solution proposée, elle souffrirait toujours de ce que j’avais dit: l’utilisateur doit faire un choix difficile entre les messages d’erreur utiles et la rapidité, car la version plus rapide prend notre"negative side lengths"
exception utile et l’aplatit de manière inutilefalse
- " quelque chose s'est mal passé, ne me demandez pas quoi ou où. "std::error_code *ec
parcourt tous les niveaux de l'API, puis au fond l'équivalent moral deif (ec == nullptr) throw something; else *ec = some error code
. (Cela résume le réelif
en quelque chose appeléErrorHandler
, mais c'est la même idée de base.)Je pense qu'il faut mentionner d'où votre collègue tire son modèle.
De nos jours, C # a le motif TryGet
public bool TryThing(out result)
. Cela vous permet d'obtenir votre résultat, tout en vous permettant de savoir si ce résultat est même une valeur valide. (Par exemple, toutes lesint
valeurs sont des résultats valables pourMath.sum(int, int)
, mais si la valeur devait déborder, ce résultat particulier pourrait être illégal). C'est un modèle relativement nouveau cependant.Avant le
out
mot - clé, vous deviez soit lever une exception (coûteuse, et l'appelant doit l'attraper ou tuer tout le programme), créer une structure spéciale (la classe avant que la classe ou les génériques ne soient vraiment une chose) pour chaque résultat représentant la valeur. et les erreurs possibles (logiciel fastidieux pour créer et gonfler le logiciel), ou renvoyer une valeur "d'erreur" par défaut (qui pourrait ne pas avoir été une erreur).L’approche que votre collègue utilise donne aux utilisateurs le privilège d’exception lorsqu’ils testent / déboguent de nouvelles fonctionnalités, tout en leur offrant la sécurité et les performances au moment de l’exécution (les performances étaient un problème critique il y a environ 30 ans) consistant à renvoyer une valeur d'erreur par défaut. Il s’agit maintenant du modèle dans lequel le logiciel a été écrit et du modèle attendu, il est donc naturel de continuer ainsi, même s’il existe de meilleures méthodes. Très probablement, ce modèle a été hérité de l’âge du logiciel, ou un modèle que vos collèges n’ont tout simplement pas développé (les vieilles habitudes sont difficiles à rompre).
Les autres réponses expliquent déjà pourquoi cela est considéré comme une mauvaise pratique. Je terminerai donc en vous recommandant de lire le modèle TryGet (peut-être aussi une encapsulation des promesses qu'un objet doit faire à son appelant).
la source
out
mot - clé, vous écriviez unebool
fonction qui prend un pointeur sur le résultat, c’est-à-dire unref
paramètre. Vous pouviez le faire dans VB6 en 1998. Leout
mot-clé vous achète simplement une certitude, à la compilation, que le paramètre est attribué au retour de la fonction, c'est tout ce que vous avez à faire. Il est un modèle agréable et utile bien.Il y a des moments où vous voulez faire son approche, mais je ne considérerais pas que ce soit la situation "normale". La clé pour déterminer dans quel cas vous vous trouvez est la suivante:
Vérifiez les exigences. Si vos exigences indiquent réellement que vous avez un seul travail, à savoir montrer des données à l'utilisateur, il a raison. Cependant, selon mon expérience, la plupart des fois, l'utilisateur se soucie également des données affichées. Ils veulent les données correctes. Certains systèmes veulent juste échouer discrètement et laisser un utilisateur comprendre que quelque chose ne va pas, mais je les considère comme une exception à la règle.
La question clé que je voudrais poser après une défaillance est la suivante: "Le système est-il dans un état où les attentes de l'utilisateur et les invariants du logiciel sont valides?" Si tel est le cas, alors revenez et continuez. Concrètement, ce n'est pas ce qui se passe dans la plupart des programmes.
En ce qui concerne l'indicateur lui-même, l'indicateur d'exceptions est généralement considéré comme une odeur de code, car un utilisateur doit savoir en quelque sorte le mode du module afin de comprendre le fonctionnement de la fonction. Si elle est en
!shouldThrowExceptions
mode, l'utilisateur a besoin de savoir qu'ils sont responsables de la détection des erreurs et de maintenir les attentes et les invariants quand ils se produisent. Ils sont également responsables sur-le-champ, à la ligne où la fonction est appelée. Un drapeau comme celui-ci est généralement très déroutant.Cependant, cela arrive. Considérez que de nombreux processeurs permettent de modifier le comportement en virgule flottante dans le programme. Un programme qui souhaite des normes plus assouplies peut le faire simplement en changeant un registre (qui est en réalité un drapeau). Le truc est que vous devez être très prudent, pour éviter de marcher accidentellement sur les orteils. Le code vérifie souvent le drapeau actuel, le définit sur le paramètre souhaité, effectue les opérations, puis le réinitialise. De cette façon, personne ne sera surpris par le changement.
la source
Cet exemple spécifique a une fonctionnalité intéressante qui peut affecter les règles ...
Ce que je vois ici est un contrôle préalable . Une vérification de précondition qui échoue implique un bogue plus haut dans la pile d'appels. La question est donc de savoir si ce code est responsable de la notification des bugs localisés ailleurs?
Une partie de la tension est due au fait que cette interface présente une obsession primitive -
x
ety
est supposée représenter de vraies mesures de longueur. Dans un contexte de programmation où les types spécifiques de domaines constituent un choix raisonnable, nous rapprocherions en fait de la vérification de la condition préalable de la source des données. meilleur sens pour le contexte.Cela dit, je ne vois absolument rien qui cloche dans le fait d’avoir deux stratégies différentes pour gérer un chèque en échec. Ma préférence serait d'utiliser la composition pour déterminer quelle stratégie est utilisée; l'indicateur de fonctionnalité serait utilisé dans la racine de la composition, plutôt que dans la mise en œuvre de la méthode de bibliothèque.
Le National Traffic and Safety Board est vraiment bon; Je pourrais suggérer des techniques d'implémentation alternatives aux graybeards, mais je ne suis pas enclin à discuter avec eux de la conception de cloisons dans le sous-système de rapport d'erreurs.
Plus largement: quel est le coût pour l'entreprise? Casser un site Web coûte beaucoup moins cher qu'un système essentiel à la vie.
la source
Les méthodes gèrent des exceptions ou ne le font pas, il n’est pas nécessaire d’indicateurs dans des langages comme C #.
Si quelque chose ne va pas dans ... code, cette exception devra être traitée par l'appelant. Si personne ne gère l'erreur, le programme se terminera.
Dans ce cas, si quelque chose de mauvais se produit dans ... code, Method1 gère le problème et le programme doit être exécuté.
C'est à vous de décider où vous gérez les exceptions. Vous pouvez certainement les ignorer en attrapant et en ne faisant rien. Mais, je voudrais m'assurer que vous ignorez seulement certains types d'exceptions spécifiques auxquelles vous pouvez vous attendre. Ignorer (
exception ex
) est dangereux car certaines exceptions ne doivent pas être ignorées, comme les exceptions système concernant le manque de mémoire, etc.la source
Cette approche rompt la philosophie "échec rapide, échec dur".
Pourquoi tu veux échouer vite:
Inconvénients de ne pas échouer vite et fort:
Enfin, la gestion des erreurs basée sur les exceptions présente l’avantage de pouvoir donner un message ou un objet "hors bande", vous pouvez facilement vous connecter à la journalisation des erreurs, aux alertes, etc. sans écrire une seule ligne supplémentaire dans votre code de domaine. .
Il existe donc non seulement des raisons techniques simples, mais également des raisons "système" qui rendent très utile l’échec rapide.
À la fin de la journée, ne pas rater le plus vite possible à l’heure actuelle, où la gestion des exceptions est légère et très stable est à moitié criminelle. Je comprends parfaitement pourquoi il est bon de penser qu’il est bon de supprimer les exceptions, mais cela n’est tout simplement plus applicable.
Surtout dans votre cas particulier, où vous donnez même la possibilité de lancer ou non des exceptions: cela signifie que l'appelant doit décider de toute façon . Il n’ya donc aucun inconvénient à ce que l’appelant attrape l’exception et la traite de manière appropriée.
Un point à venir dans un commentaire:
Un échec rapide et difficile ne signifie pas que toute votre application se bloque. Cela signifie que l'erreur se produit localement au moment où l'erreur se produit. Dans l'exemple du PO, la méthode de bas niveau qui calcule une zone ne doit pas remplacer silencieusement une erreur par une valeur erronée. Cela devrait clairement échouer.
Certains appelants de la chaîne doivent évidemment détecter cette erreur / exception et la gérer de manière appropriée. Si cette méthode était utilisée dans un avion, cela devrait probablement entraîner l’allumage d’une LED d’erreur, ou tout au moins afficher «une zone de calcul d’erreur» au lieu d’une zone erronée.
la source