web-dev-qa-db-fra.com

Devriez-vous lever une exception si les valeurs d'entrée d'une méthode sont hors limites?

Devriez-vous lever une exception si les valeurs d'entrée d'une méthode sont hors limites? par exemple

//no imaginary numbers
public int MySquareRoot(int x)
{
    if (x<0)
    {
    throw new ArgumentOutOfBoundsException("Must be a non-negative integer"); 
    }

    //our implementation here 

}

Maintenant, cette méthode ne doit jamais être appelée avec un nombre non négatif, mais les programmeurs font des erreurs. La levée d'exceptions est-elle la bonne chose à faire?

16
dwjohnston

Oui, je pense que vous devriez lever une exception, pour aider vos collègues programmeurs à remarquer l'erreur au moment de la compilation, en ajoutant également throws ArgumentOutOfBoundsException dans votre déclaration de méthode.

Autrement dit, sauf si vous utilisez des nombres imaginaires dans votre projet, où -1 est un argument parfaitement valide pour votre méthode de racine carrée. :)

20

Si vous pouvez coder votre méthode ou sa signature de telle manière que la condition d'exception ne puisse pas se produire, le faire est sans doute la meilleure approche.

Par exemple, avec l'exemple particulier que vous évoquez, votre langue a-t-elle un type de données entier non signé avec une plage appropriée? Si c'est le cas, utilisez simplement celui-ci plutôt qu'un type entier signé correspondant et lancez une exception pour toute valeur passée inférieure à 0. Dans ce cas, une valeur négative ne peut pas être utilisée à cette position dans un programme bien formé; le compilateur détectera l'erreur bien avant l'exécution, et à tout le moins émettra un avertissement (pour les types signés/non signés, utilisez la discordance), à ​​moins que vous ne vous efforciez de contourner explicitement ce filet de sécurité ; et si vous forcez une variable signée dans une fonction dont la signature de méthode indique un entier non signé uniquement sans vous assurer que la valeur se trouve dans une plage acceptable pour cette fonction, alors je dirais que le programmeur qui le fait mérite d'obtenir une exception levée dans leur visage.

Comme autre exemple, si seul un ensemble de valeurs raisonnablement petit est valide en entrée, peut-être que celles-ci peuvent être exprimées sous la forme d'une énumération avec un ensemble spécifique de valeurs plutôt que d'utiliser une valeur entière simple pour coder chaque valeur possible? Même si sous le capot, l'énumération s'effondre en un entier, l'utilisation d'énumérations protège des erreurs de programmation.

Si vous ne pouvez pas écrire la signature de la méthode de telle manière que la condition d'exception ne puisse pas se produire dans un programme bien formé, alors je dirais que c'est exactement à quoi ArgumentOutOfRangeException , IllegalArgumentException et des types d'exceptions similaires sont destinés. Cela peut également être trivialement intégré dans un flux de travail TDD. Assurez-vous simplement d'indiquer clairement l'erreur; en particulier si vous n'avez pas un accès facile au code source, obtenir une ArgumentOutOfRangeException non spécifique renvoyée peut être très frustrant. Notez la documentation de ces types d'exceptions:

ArgumentOutOfRangeException (.NET): "L'exception levée lorsque la valeur d'un argument est en dehors de la plage de valeurs autorisée définie par la méthode invoquée." ( comme on le voit ici )

IllegalArgumentException (Java): "Lancé pour indiquer qu'une méthode a reçu un argument illégal ou inapproprié." ( comme on le voit ici )

Écrire la méthode pour rendre la condition d'exception impossible au départ aidera encore plus que de lever une exception lors de l'exécution, mais nous devons reconnaître que parfois cela n'est tout simplement pas possible dans la pratique. Cependant, vous devrez toujours vérifier les invariants qui ne sont pas directement liés à la plage d'une valeur de paramètre unique au moment de l'exécution; par exemple, si x + y> 20 est une erreur, mais x et y chacun peut être n'importe quoi <= 20, je ne pense pas qu'il existe un moyen facile d'exprimer cela dans la signature de la méthode.

15
a CVn

Je comprends votre question comme "devez-vous valider les données d'entrée?", par opposition à "devez-vous utiliser des exceptions au lieu de codes d'erreur?".

Lorsque vous acceptez des paramètres d'entrée de l'extérieur - que ce soit une méthode avec des paramètres d'appel, des données d'entrée XML d'un appel de service ou une page Web avec des champs utilisateur - vous avez essentiellement deux choix:

  1. vérifier rigorus si les données d'entrée correspondent au format attendu
  2. faites confiance à votre appelant pour ne vous appeler qu'avec des données correctes

L'ajout de chèques ajoute de la complexité

  1. le développeur initial doit penser à toutes les valeurs valides (ou "éventuellement invalides") et ajouter les vérifications appropriées. Idéalement, cela se fait au début de la méthode, bien que cela doive parfois être fait plus tard (par exemple, si "valeur d'entrée valide" est "l'ID d'un objet réellement existant dans la base de données")
  2. d'autres développeurs essayant de comprendre la logique réelle de la méthode, car elle ajoute plus de lignes à étudier.
  3. surcharge d'exécution pour vérifier toutes les hypothèses

D'un autre côté, la validation des entrées apporte avantages précieux:

  1. Il vous permet de faire des hypothèses plus profondément dans le codage, car elles sont déjà vérifiées
  2. d'autres développeurs peuvent avoir plus à lire, mais rendre les hypothèses sur les données d'entrée explicites également aide à comprendre la logique , et à ce que les cas d'angle soient pris en charge.
  3. Il garantit que aucun état de programme corrompu (plantages, corruption de données, vulnérabilités de sécurité) ne peut se produire.

Donc, le choix d'ajouter des vérifications doit être fait dans le contexte où votre méthode est utilisée.

  • Est-il exposé à des données externes non fiables, comme des sites Web ou des API publiques? Effectuez ensuite une validation approfondie.
  • est-ce une méthode privée dans une boucle interne souvent appelée, protégée par un codage externe? Alors vous ne voulez probablement pas de chèques redondants. Vous voudrez peut-être documenter vos attentes (par exemple JavaDoc), bien que cela soit rarement fait dans la pratique.
  • certains langages fournissent des assertions compilées conditionnellement, qui sont activées dans les versions de débogage pour détecter les erreurs de logique du programme, mais désactivées pour les performances en utilisation productive
  • pour les bibliothèques, vous devez décider à quel point vous faites confiance à vos appelants. Mieux vaut pécher par excès de prudence et ajouter des vérifications, surtout si vous ne voulez pas que vos invariants de bibliothèque soient compromis. Pour les fonctions pertinentes pour la vitesse, cependant, vous pouvez décider différemment (et le documenter).
  • généralement un mélange est fait: une bonne validation sur les méthodes faisant face à l'extérieur, une certaine validation sur les niveaux d'interface, très peu (souvent juste NullPointerChecks) dans le fonctionnement interne d'un programme.

Si votre question était cependant "Les exceptions sont-elles la bonne façon de signaler des données d'entrée non valides?", la réponse est: très probablement oui.

Exceptions impose une surcharge considérable sur les performances d'exécution, mais facilite considérablement le raisonnement sur le déroulement du programme. Cela réduit la programmation défectueuse (erreurs sémantiques), d'autant plus qu'elle vous oblige à y faire face - elles "échouent en toute sécurité" en mettant fin au programme si elles sont ignorées. Ils sont idéaux pour "des situations qui ne devraient pas se produire". Ils peuvent également transporter des métadonnées comme une trace de pile.

codes d'erreur, d'autre part, sont légers, rapides, mais obligent l'appelant à les vérifier explicitement. Si vous ne le faites pas souvent, cela entraîne des failles de programme, qui peuvent aller de la corruption silencieuse des données, des failles de sécurité, à des feux d'artifice de Nice si votre programme se trouve à l'intérieur d'une fusée spatiale.

Un exemple où j'aurais personnellement préféré des codes d'erreur au lieu d'exceptions est la méthode String.parseInt () en Java. Obtenir une chaîne non numérique à partir d'une source d'entrée (par exemple, un utilisateur) n'est pas totalement inattendu et je dois y faire face dans tous les cas - parfois aussi simple que d'utiliser une valeur par défaut. En tant qu'appelant, je n'ai aucun moyen de vérifier si les données provoqueraient une exception (à moins d'implémenter moi-même la logique du validateur), générant ainsi l'exception dans le cadre du flux de programme normal et en utilisant try-catch-blocks est le seul choix ici .

Veuillez noter que les codes d'erreur peuvent être à la fois dans la bande et hors bande:

  • In-band: une valeur spéciale (magique) est déclarée comme marqueur de la condition d'erreur. Souvent, null ou -1 est utilisé ici, par ex. dans les méthodes de recherche de chaînes
  • Hors bande: une valeur distincte est renvoyée, indiquant "aucune erreur" (généralement 0 ou null) ou l'erreur spécifique. Cela peut être fait soit en tant que paramètre de retour supplémentaire/(pour les langues prenant en charge plusieurs valeurs de retour - par exemple, Googles GO prend cela en charge en particulier pour les erreurs), soit comme une exigence pour appeler une méthode spéciale "getError ()".
9
Zefiro

Si j'appelle une fonction, je m'attends à ce qu'elle fasse quelque chose. Si elle ne peut pas faire ce que j'ai demandé, je dois en être averti - la fonction ne doit rien faire ou faire quelque chose de mal.

Je peux être averti en renvoyant une valeur de retour spécifique (par exemple, une fonction sqrt à virgule flottante pourrait retourner NaN pour un mauvais numéro), en définissant une variable d'erreur externe ou en lançant une exception. (Le premier ou le troisième sont préférés.)

Dans votre cas, votre fonction ne peut pas faire ce qui lui est demandé lorsque x est inférieur à zéro. Lancer une exception en disant que l'argument est hors de portée est parfaitement acceptable et bien informatif.

5
paul

Vous devriez faire quelque chose, car cette réponse dit , les exceptions sont utiles, il est bon de basculer rapidement, etc. En fait, j'ajouterais qu'elles aident au moment de la lecture, je dirais qu'elles documentent l'entrée prévue mieux que le premier commentaire de votre exemple.

Cependant, il existe des alternatives. Si j'ai supposé correctement vos langues: Debug.Assert(x > 0) et Contract.Requires( x > 0 );

Cependant, le consensus semble être que l'utilisation d'exceptions dans une version est la meilleure approche.

Les assertions semblent être destinées au débogage et il est plus utile de les expédier avec des exceptions et que les contrats ne sont pas encore tout à fait là et ni l'un ni l'autre n'est bon pour les méthodes publiques. Ce n'est pas anodin et ne se prête pas à un résumé rapide. Je suggère de lire cette page.

Dans l'ensemble, j'aurais tendance à favoriser les exceptions, mais les affirmations peuvent également être un choix décent pour les méthodes internes.

4
Nathan Cooper

Je voudrais ajouter une autre raison pour laquelle lever une exception est le meilleur choix ici: les tests unitaires et TDD.

Chaque framework de test mature devrait être capable d'affirmer qu'une certaine méthode se déclenche en donnant une certaine entrée. En écrivant un test unitaire qui vérifie une telle exception, vous pouvez documenter une entrée valide et non valide pour la méthode tout en validant cette entrée non valide sera en effet rejetée.

C'est beaucoup plus difficile lorsque vous utilisez des assertions à la place (ou un autre mécanisme).

4
lethal-guitar