Devrais-je lancer une exception en cas de valeur significative en dehors de la plage ou la gérer moi-même?

42

J'ai écrit une structure qui représente les coordonnées de latitude / longitude. Leurs valeurs vont de -180 à 180 pour les longtitudes et de 90 à -90 pour les latitudes.

Si un utilisateur de cette structure me donne une valeur en dehors de cette plage, j'ai 2 options:

  1. Lancer une exception (argument hors limites)
  2. Convertir la valeur en contrainte

Parce qu'une coordonnée de -185 a une signification (elle peut très facilement être convertie en +175 car ce sont des coordonnées polaires), je pourrais l'accepter et la convertir.

Est-il préférable de lancer une exception pour dire à l'utilisateur que son code m'a donné une valeur qu'il ne devrait pas avoir?

Edit: De plus, je connais la différence entre lat / lng et les coordonnées, mais je voulais simplifier cela pour faciliter la discussion - ce n’était pas la plus brillante des idées

K. Gkinis
la source
13
L'utilisateur doit-il être autorisé à insérer une valeur en dehors de la plage? Si la réponse est non, jetez une exception. Si les règles ne sont pas aussi strictes, effectuez la conversion, mais indiquez explicitement dans la documentation que la conversion peut avoir lieu. Sachez également que dans certaines langues, la gestion des exceptions est assez coûteuse.
Andy
C #, est-ce important? Cela ne supporte pas nativement les contraintes si c'est ce que vous voulez dire.
K. Gkinis
2
Il me semble donc assez clair que la bonne approche consiste à autoriser l’utilisateur à saisir tout élément en dehors de la plage et à émettre une exception lorsqu’il le fait (si la norme indique que la valeur abs ne doit pas être supérieure à 180, valeur supérieure à celle indiquée ci-dessus). une violation claire). Soit dit en passant, C # est en fait l’un des langages où les exceptions sont assez coûteuses. Ne les utilisez donc que dans des situations exceptionnelles, ce qui signifie que ne pas l’attraper casserait votre application, dans des situations comme celle-ci.
Andy
2
J'ai tendance à ne pas formuler d'hypothèses sur ce que l'utilisateur "voulait" dire en transmettant des valeurs de paramètre particulières, en particulier celles que mon code ne prend pas en charge. Cela ressemble à un cas similaire.
JᴀʏMᴇᴇ
2
Les coordonnées Web Mercator ne se situent pas entre -180 et 180 ni entre -90 et 90. Il s’agit de latitude / longitude (et il existe même plusieurs systèmes de coordonnées pour cela). Les projections de Mercator se situent généralement dans des centaines de milliers ou des millions et comportent des unités de «mètres» (même pas strictement, puisque la longueur de chaque unité couvre une distance réelle croissante lorsque vous vous approchez des pôles), et non de degrés. Même en termes de degrés, il est limité à ± 85,051129 degrés, car la projection devient infiniment large aux pôles. (J'ai soumis une modification corrigeant ceci, car ce n'est pas le cœur de la question.)
jpmc26

Réponses:

51

Si le cœur de votre question est la suivante ...

Si un code client transmet un argument dont la valeur n'est pas valide pour la chose modélisée par ma structure de données, devrais-je la rejeter ou la convertir en quelque chose de sensé?

... alors ma réponse générale serait "rejeter", car cela aidera à attirer l’attention sur les bogues potentiels du code client qui font en sorte que la valeur non valide apparaisse dans le programme et atteigne votre constructeur. Attirer l'attention sur les bogues est généralement une propriété souhaitée dans la plupart des systèmes, du moins pendant le développement (à moins que ce ne soit une propriété souhaitée de votre système que de se débrouiller en cas d'erreur).

La question est de savoir si vous êtes réellement confronté à ce cas .

  • Si votre structure de données est conçue pour modéliser les coordonnées polaires en général, acceptez la valeur, car les angles hors de la plage -180 et +180 ne sont pas vraiment invalides. Elles sont parfaitement valables et ont toujours un équivalent compris entre -180 et +180 (et si vous souhaitez les convertir pour cibler cette plage, n'hésitez pas, le code du client n’a généralement pas besoin de soins). .

  • Si votre structure de données modélise explicitement les coordonnées Web Mercator (en fonction de la question dans sa forme initiale), il est préférable de suivre les dispositions mentionnées dans le cahier des charges (ce que je ne connais pas, je ne le dirai donc pas). . Si la spécification de la chose que vous modélisez indique que certaines valeurs ne sont pas valides, rejetez-les. S'il est dit qu'ils peuvent être interprétés comme quelque chose de sensé (et qu'ils sont donc valables), acceptez-les.

Le mécanisme que vous utilisez pour indiquer si les valeurs ont été acceptées ou non dépend des caractéristiques de votre langue, de sa philosophie générale et de vos exigences de performance. Donc, vous pouvez lancer une exception (dans le constructeur) ou renvoyer une version nullable de votre structure (via une méthode statique qui appelle un constructeur privé) ou renvoyer un booléen et transmettre votre structure à l'appelant en tant que outparamètre méthode statique qui appelle un constructeur privé), etc.

Theodoros Chatzigiannakis
la source
12
Une longitude en dehors de la plage -180 à +180 devrait probablement être considérée comme acceptable, mais une latitude en dehors de la plage -90 à +90 semblerait absurde. Une fois que l'on atteint le pôle Nord ou le pôle Sud, il n'est plus défini d'aller plus loin vers le nord ou le sud.
Supercat
4
@supercat J'ai tendance à être d'accord avec vous, mais comme je ne sais pas quelles valeurs ne sont pas valides dans la spécification Web Mercator, j'essaie de ne tirer aucune conclusion difficile. Le PO connaît mieux que moi le problème.
Theodoros Chatzigiannakis
1
@supercat: commence où le méridien atteint l'équateur. se déplacer le long du méridien en fonction de la latitude. Si la latitude est> 90, continuez simplement dans le même grand cercle. Aucun problème. Documentez-le et laissez le reste au client.
Kevin Cline
4
@supercat C'est pire que ça. Dans la projection Web Mercator, ± 90 pixels sont projetés sur une largeur infinie. La norme l’interrompt donc réellement à ± 85,051129. En outre, lat / long! = Coordonnées Web mercator. Les coordonnées Mercator utilisent une variation de mètres, pas de degrés. (Au fur et à mesure que vous vous rapprochez des pôles, chaque "mètre" correspond en réalité à un morceau de sol de plus en plus grand.) Les coordonnées des OP sont purement lat / long. Web Mercator n’a rien à voir avec eux, c’est-à-dire qu’ils peuvent s'afficher au-dessus d’une carte de base Web Mercator et qu’une bibliothèque spatiale se projette à leur insu.
jpmc26
1
Je devrais dire "l'équivalent de ± 85.051129", car ce n'est pas la coordonnée réelle.
Jpmc26
10

Ça dépend beaucoup. Mais vous devriez décider de faire quelque chose et le documenter .

La seule mauvaise chose à faire pour votre code est d'oublier de considérer que la saisie de l'utilisateur peut être en dehors de la plage attendue et d'écrire du code qui a un comportement accidentel. Parce qu'alors, certaines personnes émettront une hypothèse erronée sur le comportement de votre code, ce qui causera des bogues, alors que d'autres finiront par dépendre du comportement que votre code aura accidentellement (même si ce comportement est totalement dingue) et vous causerez donc plus de bugs. quand vous résolvez le problème plus tard.

Dans ce cas, je peux voir les arguments de toute façon. Si quelqu'un se déplace de +10 degrés à partir de 175 degrés, il devrait se retrouver à -175. Si vous normalisez toujours les entrées utilisateur et que vous considérez ainsi 185 comme équivalent à -175, le code client ne peut pas agir de manière erronée lorsqu'il ajoute 10 degrés; ça a toujours le bon effet. Si vous traitez 185 comme une erreur , vous obligez tous les cas où le code client ajoute degrés par rapport à mettre dans la logique de normalisation (ou au moins ne pas oublier d'appeler votre procédure de normalisation), vous aurez réellement la causebugs (même si espérons facile à attraper ceux qui seront rapidement écrasés). Mais si un numéro de longitude est saisi par l'utilisateur, écrit littéralement dans le programme ou calculé selon une procédure destinée à toujours figurer en [-180, 180), une valeur située en dehors de cet intervalle risque très probablement d'indiquer une erreur. "le convertir pourrait cacher des problèmes.

Mon idéal dans ce cas serait probablement de définir un type qui représente le bon domaine. Utilisez un type abstrait (ne laissez pas le code client simplement accéder aux nombres bruts qu'il contient) et fournissez à la fois une fabrique de normalisation et une fabrique de validation (afin que le client puisse faire le compromis). Mais quelle que soit la valeur de ce type, 185 doit être identique à -175 vu par votre API publique (qu’ils soient convertis à la construction ou que vous fournissiez une égalité, des accesseurs et d’autres opérations qui ignorent la différence d’une manière ou d’une autre). .

Ben
la source
3

Si le choix d'une solution ne vous intéresse pas vraiment, vous pouvez laisser l'utilisateur décider.

Étant donné que votre structure est un objet de valeur en lecture seule et créée par une méthode / un constructeur, vous pouvez fournir deux surcharges basées sur les options dont dispose l'utilisateur:

  • Lancer une exception (argument hors limites)
  • Convertir la valeur en contrainte

De même, ne laissez jamais l'utilisateur avoir une structure non valide à transmettre à vos autres méthodes, réglez-le correctement lors de la création.

Edit: basé sur les commentaires, je suppose que vous utilisez c #.

Filipe Borges
la source
Merci pour la contribution! J'y penserai, même si je crains que cela ne remette en cause l'objectif du constructeur de lanceurs d'exception, si on pouvait simplement l'éviter. C'est intéressant quand même!
K. Gkinis
Si vous voulez utiliser cette idée, il serait préférable de définir une interface et d’avoir deux implémentations qui jettent ou convertissent. Il serait difficile de surcharger un constructeur de manière sensée pour travailler comme vous l'avez décrit.
2
@ Snowman: Oui, il serait difficile de surcharger un constructeur avec les mêmes types d'arguments, mais il ne serait pas difficile d'avoir deux méthodes statiques et un constructeur privé.
wchargin
1
@ K.Gkinis: L'objectif du constructeur de levée d'exception n'est pas de "s'assurer que l'application meurt". Après tout, le client peut toujours avoir catchvos exceptions. Comme d’autres l’ont dit, c’est pour permettre au client de se limiter s’il le souhaite. Vous ne contournez vraiment rien.
wchargin
Cette suggestion complique le code sans bénéfice. La méthode doit convertir une entrée non valide ou non. Avoir deux surcharges rend le code plus complexe sans résoudre le dilemme.
JacquesB
0

Cela dépend si l'entrée provient directement d'un utilisateur via une interface utilisateur ou du système.

Entrée via une interface utilisateur

Il s’agit d’une question d’expérience utilisateur sur la gestion des entrées non valides. Je ne connais pas votre cas particulier, mais en général, il y a quelques options:

  • Avertir l'utilisateur de l'erreur et lui demander de la corriger avant de continuer (la plus courante)
  • Convertissez automatiquement dans la plage valide (si possible), mais avertissez l'utilisateur du changement et laissez-le vérifier avant de poursuivre.
  • Convertissez silencieusement dans la plage valide et continuez.

Le choix dépend des attentes de vos utilisateurs et de l’importance des données. Par exemple, Google corrige automatiquement l'orthographe dans les requêtes, mais le risque est faible, car une modification peu utile ne pose pas de problème et est facile à corriger (et même dans ce cas, il est clairement indiqué sur la page de résultat que la requête a été modifiée). D'autre part, si vous entrez les coordonnées d'un missile nucléaire, vous souhaiterez peut-être une validation d'entrée plus rigide et aucune solution silencieuse des données non valides. Donc, il n'y a pas de réponse universelle.

Plus important encore, vous devez vous demander si la correction d'une saisie présente un avantage pour l'utilisateur. Pourquoi un utilisateur entrerait-il des données invalides? Il est facile de voir comment quelqu'un pourrait faire une faute d'orthographe, mais pourquoi quelqu'un entrerait-il une longitude de -185? Si l'utilisateur voulait vraiment dire +175, il aurait probablement tapé +175. Je pense qu'il est fort probable qu'une longitude non valide soit simplement une erreur de frappe, et l'utilisateur voulait dire -85 ou quelque chose d'autre. Dans ce cas, la conversion en silence est mauvaise et inutile . L’approche la plus conviviale pour votre application consisterait probablement à alerter l’utilisateur de la valeur non valide et à lui demander de la corriger elle-même.

Entrée via une API

Si l'entrée provient d'un autre système ou sous-système, il n'y a pas de doute. Vous devriez lancer une exception. Vous ne devriez jamais rendre silencieuse la conversion d'une entrée non valide d'un autre système, car cela pourrait masquer des erreurs ailleurs dans le système. Si l'entrée est "corrigée", cela devrait se produire dans la couche d'interface utilisateur et non plus profondément dans le système.

JacquesB
la source
0

Vous devriez lancer une exception.

Dans l'exemple que vous avez donné, en envoyant 185 et en convertissant cela en -175, il peut être utile dans certains cas de fournir cette fonctionnalité. Mais que faire si l'appelant envoie 1 million? Veulent-ils vraiment convertir cela? Il semble plus probable que ce soit une erreur. Donc, si vous devez lever une exception pour 1 000 000 mais pas pour 185, vous devez alors prendre une décision concernant un seuil arbitraire pour le lancement d'une exception. Ce seuil va vous faire trébucher un jour, car une application appelante envoie des valeurs autour du seuil.

Mieux vaut jeter l'exception pour les valeurs hors de la plage.

Brendan
la source
-1

L'option la plus pratique pour un développeur serait un support d'erreur de compilation lors de la plate-forme, pour les valeurs hors limites. Dans ce cas, la plage doit également faire partie de la signature de la méthode, tout comme le type des paramètres. De la même manière que votre utilisateur d'API ne peut pas transmettre une chaîne si la signature de votre méthode est définie pour accepter un entier , l'utilisateur ne devrait pas pouvoir transmettre une valeur sans vérifier si la valeur se situe dans la plage indiquée dans la signature de la méthode. S'il n'est pas coché, il devrait obtenir une erreur de compilation et ainsi, une erreur d'exécution pourrait être évitée.

Mais actuellement, très peu de compilateurs / plates-formes supportent ce type de vérification du temps de compilation. C'est donc un casse-tête pour les développeurs. Mais idéalement, votre méthode devrait simplement générer une exception significative pour les valeurs non prises en charge et la documenter clairement.

BTW, j'aime vraiment le modèle d'erreur proposé par Joe Duffy ici .

Gulshan
la source
Comment fourniriez-vous des erreurs de compilation lors de la saisie de l'utilisateur?
JacquesB
@JacquesB Le compilateur émettra une erreur si l'entrée utilisateur n'est pas vérifiée pour être dans la plage après l'insertion, quelle que soit la valeur réelle se trouvant dans la plage.
Gulshan
Mais vous devez encore décider si vous souhaitez rejeter l'entrée ou convertir en une plage valide. Cela ne répond donc pas à la question initiale.
JacquesB
@JacquesB Je devrais dire ceci, au début - j'ai toujours pensé que, OP développe une API et que l'interface utilisateur est développée par quelqu'un d'autre, consommant son API. Je me suis trompé sur ce point. Je viens juste de relire la question et je m'en suis rendu compte. Tout ce que j'essaie de dire, c'est que la validation devrait être effectuée chez les consommateurs d'API et sinon, le compilateur devrait renvoyer une erreur.
Gulshan
Alors ... vous devez créer une nouvelle classe contenant les entrées et la valider. Quand vous construisez l'objet de classe à partir des entrées brutes, que se passe-t-il? Lancer une exception? Passer silencieusement? Vous venez de déplacer le problème.
Djechlin
-1

La valeur par défaut devrait être de lancer une exception. Vous pouvez également autoriser une option telle que strict=falseet exercer la contrainte en fonction du drapeau, où strict=trueest bien sûr la valeur par défaut. Ceci est assez commun:

  • Java DateFormatsupporte clément .
  • L'analyseur JSON de Gson prend également en charge un mode clément .
  • Etc.
Djechlin
la source
Cela complique le code sans bénéfice.
JacquesB
@JacquesB lançant une exception par défaut ou autorisant strict=false?
Djechlin
@JacquesB J'ai ajouté quelques exemples d'API prenant en charge les modes strict et indulgent. Veuillez jeter un coup d'œil.
Djechlin
-2

Pour moi, la meilleure pratique consiste à ne jamais modifier les entrées de l'utilisateur. L'approche que je prends habituellement consiste à séparer la validation de l'exécution.

  • Avoir une classe simple qui utilise simplement les paramètres donnés.
  • Utilisez un décorateur pour fournir une couche de validation qui peut être modifiée à volonté sans affecter la classe d'exécution (ou injectez un validateur si cette approche est trop difficile).
David A
la source
Le "utilisateur" auquel la question fait référence est le développeur qui utilise l'API, pas l'utilisateur final ...
Jay Elston