J'ai une méthode où toute la logique est effectuée à l'intérieur d'une boucle foreach qui itère sur le paramètre de la méthode:
public IEnumerable<TransformedNode> TransformNodes(IEnumerable<Node> nodes)
{
foreach(var node in nodes)
{
// yadda yadda yadda
yield return transformedNode;
}
}
Dans ce cas, l'envoi d'une collection vide donne une collection vide, mais je me demande si c'est imprudent.
Ma logique ici est que si quelqu'un appelle cette méthode, alors il a l'intention de transmettre des données et ne transmettrait une collection vide à ma méthode que dans des circonstances erronées.
Devrais-je attraper ce comportement et lancer une exception, ou est-il préférable de renvoyer la collection vide?
c#
exceptions
collections
parameters
Nick Udell
la source
la source
null
mais pas si elle est vide.Réponses:
Les méthodes utilitaires ne doivent pas jeter sur des collections vides. Vos clients API vous détesteraient pour cela.
Une collection peut être vide. une "collection qui ne doit pas être vide" est conceptuellement une chose beaucoup plus difficile à travailler.
La transformation d'une collection vide a un résultat évident: la collection vide. (Vous pouvez même économiser des déchets en retournant le paramètre lui-même.)
Il existe de nombreuses circonstances dans lesquelles un module conserve des listes d'éléments remplis ou non. Devoir vérifier la vacuité avant chaque appel
transform
est ennuyeux et a le potentiel de transformer un algorithme simple et élégant en désordre odieux.Les méthodes d’utilité devraient toujours s’efforcer d’être libérales dans leurs intrants et conservatrices dans leurs extrants.
Pour toutes ces raisons, pour l'amour de Dieu, gérez correctement la collection vide. Rien n'est plus exaspérant qu'un module d'assistance qui pense savoir ce que vous voulez mieux que vous.
la source
null
dans la collection vide. Les fonctions avec des limitations implicites sont pénibles.Populate1(input); output1 = TransformNodes(input); Populate2(input); output2 = TransformNodes(input);
si Populate1 laissait la collection vide et que vous la renvoyiez pour le premier appel TransformNodes, output1 et input seraient la même collection. ensemble d'entrées dans output2.Je vois deux questions importantes qui déterminent la réponse à cette question:
1. Retour significatif
En gros, si vous pouvez retourner quelque chose de significatif, ne lancez pas d'exception. Laissez l'appelant gérer le résultat. Donc, si votre fonction ...
En général, mon biais de PF m'indique «rendre quelque chose de significatif» et null peut avoir une signification valable dans ce cas.
2. style
Votre code général (ou le code du projet ou celui de l’équipe) favorise-t-il un style fonctionnel? Si non, des exceptions seront attendues et traitées. Si oui, envisagez de renvoyer un type d'option . Avec un type d'option, vous renvoyez une réponse significative ou Aucune / Rien . Dans mon troisième exemple, ci-dessus, Nothing ne serait pas une bonne réponse en style de PF. Le fait que la fonction renvoie clairement à l'appelant un type d'option plus qu'une réponse significative peut ne pas être possible et que l'appelant doit être prêt à y faire face. Je pense que cela donne plus de choix à l'appelant (si vous pardonnez le jeu de mots).
F # est l' endroit où tous les frais .Net enfants font ce genre de chose , mais C # ne soutenir ce style.
tl; dr
Conservez les exceptions pour les erreurs inattendues dans votre propre chemin de code, sans entrée totalement prévisible (et légale) de la part de quelqu'un d’autre.
la source
Comme toujours, ça dépend.
Est-il important que la collection soit vide?
La plupart des codes de traitement des collections diraient probablement "non"; une collection peut contenir n'importe quel nombre d'éléments, y compris zéro.
Maintenant, si vous avez une sorte de collection où il est "invalide" de ne pas contenir d’éléments, alors c’est une nouvelle exigence et vous devez décider quoi faire à ce sujet.
Empruntez une logique de test du monde de la base de données: test pour zéro élément, un élément et deux éléments. Cela convient aux cas les plus importants (élimination de toute condition de jointure interne ou cartésienne mal formée).
la source
java.util.Collection
, mais unecom.foo.util.NonEmptyCollection
classe personnalisée , qui peut conserver systématiquement cet invariant et vous empêcher de passer à un état invalide.Pour une bonne conception, acceptez autant que possible les variations de vos entrées. Les exceptions doivent seulement être levée lorsque (entrée inacceptable est des erreurs présentées ou inattendues se produire lors du traitement) et le programme ne peut pas continuer de manière prévisible en conséquence.
Dans ce cas, une collection vide doit être attendue et votre code doit le gérer (ce qui est déjà le cas). Ce serait une violation de tout ce qui est bon si votre code lançait une exception ici. Cela reviendrait à multiplier 0 par 0 en mathématiques. Il est redondant, mais absolument nécessaire pour que cela fonctionne comme il le fait.
Passons maintenant à l'argument de collection null. Dans ce cas, une collection nulle est une erreur de programmation: le programmeur a oublié d'attribuer une variable. Dans ce cas, une exception pourrait être levée, car vous ne pouvez pas le traiter de manière significative dans une sortie, et tenter de le faire introduirait un comportement inattendu. Cela équivaudrait à une division par zéro en mathématiques - cela n'a aucun sens.
la source
Il est beaucoup plus difficile de voir la bonne solution lorsque vous regardez votre fonction de manière isolée. Considérez votre fonction comme une partie d’ un problème plus vaste . Une solution possible à cet exemple ressemble à ceci (en Scala):
Tout d'abord, vous divisez la chaîne par des chiffres non numériques, filtrez les chaînes vides, convertissez les chaînes en entiers, puis filtrez pour ne conserver que les nombres à quatre chiffres. Votre fonction pourrait être la
map (_.toInt)
dans le pipeline.Ce code est assez simple car chaque étape du pipeline ne gère qu'une chaîne vide ou une collection vide. Si vous insérez une chaîne vide au début, vous obtenez une liste vide à la fin. Vous n'êtes pas obligé de vous arrêter et de rechercher
null
une exception après chaque appel.Bien entendu, cela suppose qu'une liste de sortie vide n'a pas plus d'un sens. Si vous devez différencier une sortie vide provoquée par une entrée vide de celle générée par la transformation elle-même, cela change complètement les choses.
la source
Cette question concerne en réalité des exceptions. Si vous le regardez de cette façon et que vous ignorez la collection vide en tant que détail d'implémentation, la réponse est simple:
1) Une méthode doit lever une exception quand elle ne peut pas continuer: soit incapable de faire la tâche désignée, soit retourne la valeur appropriée.
2) Une méthode devrait intercepter une exception lorsqu'elle est capable de continuer malgré l'échec.
Ainsi, votre méthode d'assistance ne devrait pas être "utile" et lever une exception à moins qu'elle ne puisse pas faire son travail avec une collection vide. Laissez l'appelant déterminer si les résultats peuvent être traités.
Que cela retourne une collection vide ou null, c'est un peu plus difficile mais pas beaucoup: les collections nullables doivent être évitées si possible. Le but d’une collection nullable serait d’indiquer (comme en SQL) que vous n’avez pas l’information - une collection d’enfants, par exemple, peut être nulle si vous ne savez pas si quelqu'un en a une, mais vous n'en avez pas. sais qu'ils ne le font pas. Mais si cela est important pour une raison quelconque, il vaut probablement la peine d'être suivi par une variable supplémentaire.
la source
La méthode est nommée
TransformNodes
. Dans le cas d'une collection vide en entrée, récupérer une collection vide est naturel et intuitif, et prend tout son sens mathématique.Si la méthode était nommée
Max
et conçue pour renvoyer l'élément maximum, il serait alors naturel de jeterNoSuchElementException
une collection vide, car le maximum de rien n'a aucun sens mathématique.Si la méthode a été nommée
JoinSqlColumnNames
et conçue pour renvoyer une chaîne où les éléments sont joints par une virgule à utiliser dans les requêtes SQL, il serait alors judicieux de créerIllegalArgumentException
une collection vide, car l'appelant finirait par obtenir une erreur SQL s'il utilisait la chaîne dans une requête SQL directement sans autres vérifications, et au lieu de rechercher une chaîne vide renvoyée, il aurait plutôt dû rechercher une collection vide.la source
Max
de rien est souvent l'infini négatif.Revenons en arrière et utilisons un exemple différent qui calcule la moyenne arithmétique d'un tableau de valeurs.
Si le tableau d'entrée est vide (ou nul), pouvez-vous raisonnablement répondre à la demande de l'appelant? Non. Quelles sont vos options? Eh bien, vous pourriez:
Je leur dis de leur donner l'erreur s'ils vous ont donné une entrée invalide et que la demande ne peut pas être complétée. Je veux dire une erreur dès le premier jour pour qu'ils comprennent les exigences de votre programme. Après tout, votre fonction n'est pas en mesure de répondre. Si l'opération peut échouer (par exemple, copier un fichier), votre API doit leur indiquer une erreur à laquelle ils peuvent remédier.
Cela peut donc définir comment votre bibliothèque traite les requêtes mal formées et les requêtes susceptibles d’échouer.
Il est très important que votre code soit cohérent dans la façon dont il traite ces classes d’erreurs.
La catégorie suivante consiste à décider de la manière dont votre bibliothèque traite les requêtes non-sens. Pour en revenir à un exemple similaire à la vôtre - Utilisons une fonction qui détermine si un fichier existe sur un chemin:
bool FileExistsAtPath(String)
. Si le client passe une chaîne vide, comment gérez-vous ce scénario? Que diriez-vous d'un tableau vide ou nul passé àvoid SaveDocuments(Array<Document>)
? Choisissez votre bibliothèque / base de code et soyez cohérent. Il m'est arrivé de considérer ces erreurs comme des erreurs et d’interdire aux clients de faire des requêtes insensées en les signalant comme des erreurs (via une assertion). Certaines personnes vont fortement résister à cette idée / action. Je trouve cette détection d'erreur très utile. C'est très bien pour localiser les problèmes dans les programmes - avec un bon endroit pour le programme incriminé. Les programmes sont beaucoup plus clairs et corrects (considérez l’évolution de votre base de code), et ne gravez pas de cycles dans des fonctions qui ne font rien. Le code est plus petit / plus propre de cette manière, et les vérifications sont généralement transférées aux emplacements où le problème peut être introduit.la source
if (!FileExists(path)) { ...create it!!!... }
bugs - dont beaucoup seraient capturés avant d'être validés, les lignes de la rectitude n'étant pas floues.En règle générale, une fonction standard doit être capable d'accepter la liste la plus large d'entrées et de donner un retour sur celle-ci. Il existe de nombreux exemples dans lesquels des programmeurs utilisent des fonctions d'une manière que le concepteur n'avait pas planifiée. devrait pouvoir accepter non seulement des collections vides, mais également un large éventail de types d'entrée et renvoyer gracieusement les retours, même s'il s'agit d'un objet d'erreur, quelle que soit l'opération effectuée sur l'entrée ...
la source
writePaycheckToEmployee
ne devrait pas accepter un nombre négatif en entrée ... mais si "feedback" signifie "fait tout ce qui pourrait se passer ensuite", alors oui, chaque fonction fera quelque chose qu'elle fera ensuite.