Je suis en train de développer un nouveau langage de programmation pour répondre à certains besoins de l'entreprise. Ce langage est destiné aux utilisateurs novices. Donc, il n'y a pas de support pour la gestion des exceptions dans la langue, et je ne m'attendrais pas à ce qu'ils l'utilisent même si je l'ajoutais.
J'ai atteint le point où je dois implémenter l'opérateur de division et je me demande comment gérer au mieux une erreur de division par zéro?
Il me semble n'avoir que trois façons possibles de traiter cette affaire.
- Ignorer l'erreur et produire
0
le résultat. Enregistrer un avertissement si possible. - Ajouter
NaN
comme valeur possible pour les nombres, mais cela soulève des questions sur la façon de gérer lesNaN
valeurs dans d'autres domaines de la langue. - Terminez l'exécution du programme et signalez à l'utilisateur qu'une erreur grave s'est produite.
L'option n ° 1 semble la seule solution raisonnable. L'option n ° 3 n'est pas pratique car ce langage sera utilisé pour exécuter la logique comme un cron nocturne.
Quelles sont mes alternatives pour gérer une erreur de division par zéro, et quels sont les risques associés à l'option # 1.
la source
reject "Foo"
été mis en œuvre, mais simplement qu'il rejette un document s'il contient le mot cléFoo
. J'essaie de rendre le langage aussi facile à lire en utilisant des termes familiers. Donner à un utilisateur son propre langage de programmation lui permet d'ajouter des règles métier sans dépendre du personnel technique.Réponses:
Je déconseillerais fortement la position n ° 1, car ignorer les erreurs est un anti-motif dangereux. Cela peut conduire à des bugs difficiles à analyser. Définir le résultat d’une division par zéro à 0 n’a aucun sens, et la poursuite de l’exécution du programme avec une valeur insensée va poser problème. Surtout quand le programme fonctionne sans surveillance. Lorsque l'interprète de programme s'aperçoit qu'il y a une erreur dans le programme (et qu'une division par zéro est presque toujours une erreur de conception), il est préférable de l'abandonner et de tout conserver tel quel, plutôt que de remplir votre base de données de manière erronée.
En outre, il est peu probable que vous réussissiez à suivre à la lettre ce modèle. Tôt ou tard, vous rencontrerez des situations d'erreur qui ne peuvent pas être ignorées (par exemple, manque de mémoire ou débordement de pile) et vous devrez quand même implémenter un moyen de terminer le programme.
L'option n ° 2 (utiliser NaN) consisterait en un peu de travail, mais pas autant que vous ne le pensez. La manière de gérer NaN dans différents calculs est bien documentée dans la norme IEEE 754; vous pouvez donc faire exactement ce que fait la langue dans laquelle votre interprète est écrit.
Au fait: Nous essayons de créer un langage de programmation utilisable par des non-programmeurs depuis 1964 (Dartmouth BASIC). Jusqu'ici, nous avons échoué. Mais bonne chance quand même.
la source
PHP
a été une mauvaise influence sur moi.NaN
dans la langue d'un débutant, mais en général, bonne réponse.Ce n'est pas une bonne idée. Du tout. Les gens vont commencer à en dépendre et, si vous le corrigez un jour, vous casserez beaucoup de code.
Vous devez manipuler NaN comme les runtimes d’autres langues le font: Tout calcul ultérieur donne également NaN et chaque comparaison (même NaN == NaN) donne faux.
Je pense que cela est acceptable, mais pas nécessairement nouveau venu.
C’est la meilleure solution à mon avis. Avec cette information en main, les utilisateurs devraient être capables de gérer le 0. Vous devez fournir un environnement de test, en particulier s’il est prévu de fonctionner une fois par nuit.
Il y a aussi une quatrième option. Faites de la division une opération ternaire. N'importe lequel de ces deux fonctionnera:
la source
NaN == NaN
êtrefalse
, alors vous devrez ajouter uneisNaN()
fonction afin que les utilisateurs sont en mesure de détecterNaN
s.isNan(x) => x != x
. Néanmoins, lorsqueNaN
votre code de programmation apparaît, vous ne devez pas commencer à ajouter desisNaN
vérifications, mais plutôt rechercher la cause et y effectuer les vérifications nécessaires. Il est donc importantNaN
de propager pleinement.NaN
s sont principalement contre-intuitifs. Dans la langue d'un débutant, ils sont morts à l'arrivée.1/0
: vous devez faire quelque chose avec. Il n'y a pas de résultat éventuellement utile autre queInf
ouNaN
- quelque chose qui propagera l'erreur plus loin dans le programme. Sinon, la seule solution consiste à arrêter avec une erreur à ce stade.Arrêtez l'application en cours avec un parti pris extrême. (Tout en fournissant des informations de débogage adéquates)
Ensuite, éduquez vos utilisateurs pour qu'ils identifient et gèrent les conditions dans lesquelles le diviseur peut être nul (valeurs entrées par l'utilisateur, etc.).
la source
Dans Haskell (et similaire à Scala), au lieu de jeter des exceptions (ou le retour des références null) les types d'enveloppe
Maybe
etEither
peuvent être utilisés. AvecMaybe
l'utilisateur a une chance de tester si la valeur qu'il a obtenue est "vide", ou il pourrait fournir une valeur par défaut lors du "déroulement".Either
est similaire, mais peut être utilisé retourne un objet (une chaîne d'erreur, par exemple) décrivant le problème s'il en existe un.la source
error "some message"
fonction en cours d'évaluation.Haskell
ne permet pas aux expressions pures de renvoyer des exceptions.D'autres réponses ont déjà examiné les mérites relatifs de vos idées. J'en propose une autre: utiliser une analyse de flux de base pour déterminer si une variable peut être égale à zéro. Ensuite, vous pouvez simplement interdire la division par des variables potentiellement nulles.
Sinon, utilisez une fonction d'assertion intelligente qui établit des invariants:
Cela équivaut à renvoyer une erreur d'exécution (vous contournez entièrement des opérations non définies), mais présente l'avantage que le chemin du code n'a même pas besoin d'être touché pour que la défaillance potentielle soit exposée. Cela peut se faire un peu comme une vérification de type ordinaire, en évaluant toutes les branches d'un programme avec des environnements de frappe imbriqués pour suivre et vérifier les invariants:
En outre, il s’étend naturellement à la portée et à la
null
vérification, si votre langue possède de telles fonctionnalités.la source
def foo(a,b): return a / ord(sha1(b)[0])
. L'analyseur statique ne peut pas inverser SHA-1. Clang a ce type d'analyse statique et son excellent pour la recherche de bugs peu profonds, mais il y a beaucoup de cas qu'il ne peut pas gérer.Le numéro 1 (insérer undebuggable zero) est toujours mauvais. Le choix entre # 2 (propager NaN) et # 3 (tuer le processus) dépend du contexte et devrait idéalement être un paramètre global, comme dans Numpy.
Si vous effectuez un grand calcul intégré, propager NaN est une mauvaise idée car il finira par se propager et infecter l'ensemble de votre calcul - lorsque vous regardez les résultats le matin et constatez qu'ils sont tous NaN, vous ' Je dois jeter les résultats et recommencer quand même. Il aurait été préférable que le programme se termine, que vous receviez un appel au milieu de la nuit et que vous le répariez - en termes de nombre d'heures perdues, au moins.
Si vous faites beaucoup de petits calculs, pour la plupart indépendants (comme des calculs parallèles avec réduction de la carte ou parallèlement embarrassants) et que vous pouvez tolérer qu'un pourcentage d'entre eux soit inutilisable en raison de NaN, c'est probablement la meilleure option. Terminer le programme et ne pas faire les 99% qui seraient bons et utiles en raison du 1% qui sont mal formés et se divisent par zéro pourrait être une erreur.
Une autre option, liée aux NaN: la même spécification à virgule flottante IEEE définit Inf et -Inf, et celles-ci sont propagées différemment de NaN. Par exemple, je suis à peu près sûr que Inf> tout nombre et -Inf <tout nombre, ce qui serait ce que vous vouliez si votre division par zéro se produisait, car le zéro était supposé être un petit nombre. Si vos entrées sont arrondies et souffrent d’erreurs de mesure (comme des mesures physiques à la main), la différence entre deux grandes quantités peut avoir pour résultat zéro. Sans la division par zéro, vous auriez obtenu un nombre élevé, et peut-être ne vous souciez-vous pas de sa taille. Dans ce cas, In et -Inf sont des résultats parfaitement valides.
Cela peut être formellement correct aussi - dites simplement que vous travaillez dans les réels étendus.
la source
Bien sûr, c’est pratique: c’est la responsabilité des programmeurs d’écrire un programme qui a du sens. Diviser par 0 n'a aucun sens. Par conséquent, si le programmeur effectue une division, il lui incombe également de vérifier au préalable que le diviseur n'est pas égal à 0. Si le programmeur n'effectue pas cette vérification de validation, il doit alors se rendre compte de cette erreur dès que possibles, et les résultats de calculs dénormalisés (NaN) ou incorrects (0) ne seront d'aucun secours à cet égard.
L’option 3 se trouve être celle que je vous aurais recommandée, d’ailleurs, pour être la plus simple, la plus honnête et la plus mathématiquement correcte.
la source
Cela me semble une mauvaise idée d’exécuter des tâches importantes ("Nightly Cron") dans un environnement où les erreurs sont ignorées. C'est une idée terrible d'en faire une fonctionnalité. Ceci exclut les options 1 et 2.
L'option 3 est la seule solution acceptable. Les exceptions ne doivent pas nécessairement faire partie du langage, mais font partie de la réalité. Votre message de résiliation doit être aussi spécifique et informatif que possible concernant l’erreur.
la source
IEEE 754 a en fait une solution bien définie pour votre problème. Gestion des exceptions sans utiliser
exceptions
http://en.wikipedia.org/wiki/IEEE_floating_point#Exception_handlingDe cette façon, toutes vos opérations ont un sens mathématique.
\ lim_ {x \ to 0} 1 / x = Inf
À mon avis, suivre IEEE 754 est tout à fait logique car cela garantit que vos calculs sont aussi corrects que sur un ordinateur et que vous êtes également cohérent avec le comportement des autres langages de programmation.
Le seul problème qui se pose est que Inf et NaN vont contaminer vos résultats et vos utilisateurs ne sauront pas exactement d'où vient le problème. Regardez une langue comme Julia qui le fait très bien.
L'erreur de division est propagée correctement par les opérations mathématiques, mais l'utilisateur ne sait pas nécessairement de quelle opération provient l'erreur.
edit:
Je n'ai pas vu la deuxième partie de la réponse de Jim Pivarski qui est essentiellement ce que je dis ci-dessus. Ma faute.la source
SQL, facilement le langage le plus utilisé par les non-programmeurs, est le n ° 3, quelle que soit la valeur de celui-ci. Dans mon expérience d’observation et d’aide auprès des non-programmeurs qui écrivent du SQL, ce comportement est généralement bien compris et facilement compensé (par une instruction case ou similaire). Le message d'erreur que vous obtenez a tendance à être assez direct, par exemple, dans Postgres 9, vous obtenez "ERREUR: division by zero".
la source
Je pense que le problème est "ciblé sur les utilisateurs novices. -> Donc, il n'y a pas de soutien pour ..."
Pourquoi pensez-vous que la gestion des exceptions est problématique pour les utilisateurs novices?
Quel est le pire? Vous avez une fonction "difficile" ou vous ne savez pas pourquoi quelque chose est arrivé? Que pourrait confondre plus? Un crash avec un core dump ou "Erreur fatale: Diviser par zéro"?
Au lieu de cela, je pense qu’il est préférable de viser de très bonnes erreurs de message. Donc, faites plutôt: "Mauvais calcul, Divisez 0/0" (c.-à-d.: Affichez toujours les DONNÉES qui causent le problème, pas seulement le type de problème). Regardez comment PostgreSql fait les erreurs de message, qui sont géniales à mon humble avis.
Cependant, vous pouvez rechercher d'autres moyens de travailler avec des exceptions telles que:
http://dlang.org/exception-safe.html
J'ai aussi rêvé de construire un langage, et dans ce cas, je pense que combiner un événement Maybe / Optionnel avec des exceptions normales pourrait être le meilleur:
la source
À mon avis, votre langage devrait fournir un mécanisme générique de détection et de traitement des erreurs. Les erreurs de programmation doivent être détectées au moment de la compilation (ou le plus tôt possible) et doivent normalement mener à la fin du programme. Les erreurs résultant de données inattendues ou erronées, ou de conditions externes imprévues, doivent être détectées et mises à disposition pour une action appropriée, tout en permettant au programme de continuer autant que possible.
Les actions plausibles incluent (a) terminer (b) demander à l'utilisateur une action (c) consigner l'erreur (d) remplacer une valeur corrigée (e) définir un indicateur à tester dans le code (f) invoquer une routine de traitement d'erreur. Lesquels de ces choix sont disponibles et par quels moyens vous devez faire des choix.
D'après mon expérience, les erreurs de données courantes telles que les conversions défectueuses, la division par zéro, le dépassement de capacité et les valeurs hors limites sont bénignes et doivent par défaut être gérées en substituant une valeur différente et en définissant un indicateur d'erreur. Le (non-programmeur) utilisant ce langage verra les données erronées et comprendra rapidement la nécessité de vérifier les erreurs et de les traiter.
[Pour un exemple, considérons une feuille de calcul Excel. Excel ne met pas fin à votre feuille de calcul à cause d’un nombre excessif ou autre. La cellule reçoit une valeur étrange et vous allez découvrir pourquoi et la réparer.]
Donc, pour répondre à votre question: vous ne devriez certainement pas terminer. Vous pouvez substituer NaN mais vous ne devriez pas rendre cela visible, assurez-vous simplement que le calcul est terminé et génère une valeur élevée étrange. Et définissez un indicateur d'erreur afin que les utilisateurs qui en ont besoin puissent déterminer qu'une erreur s'est produite.
Divulgation: J'ai créé une telle implémentation de langage (Powerflex) et abordé exactement ce problème (et bien d'autres) dans les années 1980. Il n’ya eu que peu ou pas de progrès sur les langues pour les non-programmeurs au cours des 20 dernières années environ, et vous attirerez des tas de critiques pour essayer, mais j’espère vraiment que vous réussirez.
la source
J'ai aimé l'opérateur ternaire où vous fournissez une valeur alternative au cas où le dénominateur est 0.
Une autre idée que je n'ai pas vue est de produire une valeur "invalide" générale. Un général "cette variable n'a pas de valeur parce que le programme a fait quelque chose de mal", qui porte une trace de pile complète avec lui-même. Ensuite, si vous utilisez cette valeur n'importe où, le résultat est à nouveau invalide, avec la nouvelle opération tentée en premier (c'est-à-dire que si la valeur non valide apparaît dans une expression, l'expression entière devient invalide et aucun appel de fonction n'est tenté; une exception serait Opérateurs être booléens - vrai ou invalide est vrai et faux et invalide est faux - il peut y avoir d'autres exceptions à cela aussi. Une fois que cette valeur n'est plus référencée nulle part, vous enregistrez une longue et belle description de la chaîne entière dans laquelle les choses ne vont pas et vous continuez comme si de rien n'était. Peut-être envoyer la trace par courrier électronique au responsable du projet ou quelque chose du genre.
Quelque chose comme la monade peut-être fondamentalement. Cela fonctionnera aussi avec tout ce qui peut échouer et vous pouvez permettre aux gens de construire leurs propres invalides. Et le programme continuera à fonctionner tant que l'erreur ne sera pas trop profonde, ce qui est vraiment recherché ici, à mon avis.
la source
Une division par zéro a deux raisons fondamentales.
Pour 1. vous devez informer les utilisateurs qu'ils ont commis une erreur, car ce sont eux qui en sont responsables et qui savent le mieux comment remédier à la situation.
Pour 2. Ce n'est pas la faute de l'utilisateur, vous pouvez pointer du doigt un algorithme, une implémentation matérielle, etc. Une solution raisonnable consiste donc à poursuivre les opérations de manière raisonnable.
Je peux voir que la personne qui pose cette question a demandé le cas 1. Vous devez donc communiquer avec l'utilisateur. En utilisant n'importe quel standard de virgule flottante, Inf, -Inf, Nan, IEEE ne convient pas à cette situation. Stratégie fondamentalement fausse.
la source
Interdit dans la langue. C’est-à-dire qu’il est interdit d’autoriser la division par un nombre jusqu’à ce qu’il soit prouvé que ce n’est pas zéro, généralement en le testant d’abord. C'est à dire.
la source
int
autorise les valeurs zéro, mais GCC peut toujours déterminer où, dans le code, les intes spécifiques ne peuvent pas être zéro.Lorsque vous écrivez un langage de programmation, vous devez en tirer parti et rendre obligatoire l’inclusion d’une action pour le schéma par zéro. a <= n / c: 0 div-by-zero-action
Je sais que ce que je viens de suggérer consiste essentiellement à ajouter un "goto" à votre PL.
la source