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?
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. :)
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.
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:
L'ajout de chèques ajoute de la complexité
D'un autre côté, la validation des entrées apporte avantages précieux:
Donc, le choix d'ajouter des vérifications doit être fait dans le contexte où votre méthode est utilisée.
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:
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.
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.
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).