Je développe une bibliothèque destinée à être publiée. Il contient diverses méthodes permettant de manipuler des ensembles d'objets: générer, inspecter, partitionner et projeter les ensembles dans de nouveaux formulaires. Le cas échéant, il s'agit d'une bibliothèque de classe C # contenant des extensions de style LINQ IEnumerable
, à publier sous forme de package NuGet.
Certaines méthodes de cette bibliothèque peuvent recevoir des paramètres d'entrée non satisfaisants. Par exemple, dans les méthodes combinatoires, il existe une méthode pour générer tous les ensembles de n éléments pouvant être construits à partir d’un ensemble source de m éléments. Par exemple, étant donné l'ensemble:
1, 2, 3, 4, 5
et demander des combinaisons de 2 produirait:
1, 2
1, 3
1, 4
etc ...
5, 3
5, 4
Maintenant, il est évidemment possible de demander quelque chose qui ne peut pas être fait, comme donner un ensemble de 3 éléments, puis demander des combinaisons de 4 éléments tout en définissant l'option qui dit qu'il ne peut utiliser chaque élément qu'une seule fois.
Dans ce scénario, chaque paramètre est individuellement valide:
- La collection source n'est pas null et contient des éléments
- La taille des combinaisons demandée est un entier positif différent de zéro
- Le mode demandé (utiliser chaque élément une seule fois) est un choix valide
Cependant, l'état des paramètres, pris ensemble, pose des problèmes.
Dans ce scénario, vous attendriez-vous que la méthode lève une exception (par exemple InvalidOperationException
) ou renvoie une collection vide? Cela me semble valable:
- Vous ne pouvez pas produire de combinaisons de n éléments à partir d'un ensemble de m éléments où n> m si vous ne pouvez utiliser chaque élément qu'une seule fois. Cette opération peut donc être considérée comme impossible
InvalidOperationException
. - L'ensemble des combinaisons de taille n pouvant être produites à partir de m éléments lorsque n> m est un ensemble vide; aucune combinaison ne peut être produite.
L'argument pour un ensemble vide
Ma première préoccupation est qu’une exception empêche l’enchaînement idiomatique de méthodes de type LINQ lorsque vous traitez avec des jeux de données dont la taille peut être inconnue. En d'autres termes, vous voudrez peut-être faire quelque chose comme ceci:
var result = someInputSet
.CombinationsOf(4, CombinationsGenerationMode.Distinct)
.Select(combo => /* do some operation to a combination */)
.ToList();
Si votre jeu d'entrées est de taille variable, le comportement de ce code est imprévisible. Si .CombinationsOf()
une exception someInputSet
contient moins de 4 éléments, ce code échouera parfois au moment de l'exécution sans vérification préalable. Dans l'exemple ci-dessus, cette vérification est triviale, mais si vous l'appelez à mi-chemin d'une chaîne plus longue de LINQ, cela peut devenir fastidieux. S'il retourne un ensemble vide, il result
sera vide, ce qui peut vous satisfaire parfaitement.
L'argument pour une exception
Ma deuxième préoccupation est que renvoyer un ensemble vide peut masquer des problèmes - si vous appelez cette méthode à mi-chemin d’une chaîne de LINQ et qu’elle renvoie silencieusement un ensemble vide, vous risquez de rencontrer des problèmes quelques étapes plus tard ou de vous retrouver avec un objet vide. résultat, et il n’est peut-être pas évident de savoir comment cela s’est passé puisque vous aviez certainement quelque chose dans le jeu d’entrée.
À quoi vous attendez-vous et quel est votre argument?
la source
Réponses:
Renvoyer un ensemble vide
Je m'attendrais à un jeu vide parce que:
Il y a 0 combinaisons de 4 nombres parmi les 3 quand je ne peux utiliser chaque numéro qu'une fois
la source
En cas de doute, demandez à quelqu'un d'autre.
Votre exemple fonction a un très similaire en Python:
itertools.combinations
. Voyons voir comment ça fonctionne:Et ça me convient parfaitement. Je m'attendais à un résultat que je pourrais parcourir et j'en ai un.
Mais, évidemment, si vous demandiez quelque chose de stupide:
Donc, je dirais que si tous vos paramètres sont validés mais que le résultat est un ensemble vide, vous renvoyez un ensemble vide, vous n'êtes pas le seul à le faire.
Comme l'a dit @Bakuriu dans les commentaires, il en va de même pour une
SQL
requête commeSELECT <columns> FROM <table> WHERE <conditions>
. Tant que<columns>
,<table>
,<conditions>
bien formés et formés se réfèrent à des noms existants, vous pouvez construire un ensemble de conditions qui excluent l'autre. La requête résultante ne donnerait aucune ligne au lieu de lancer unInvalidConditionsError
.la source
n
éléments dans une collection pour compter le nombre de façons dont nous pouvons les sélectionner. Si, à un moment donné, il ne reste aucun élément lors de la sélection, il n'y a pas (0) moyen de sélectionner desn
éléments de ladite collection.1.0 / 0
, Python ne le permettrait pas.SELECT
sur une table génère-t-elle une exception lorsque le jeu de résultats est vide? (spoiler: non!). La "forme" d'une requête dépend uniquement de la requête elle-même et de certaines métadonnées (par exemple, la table existe-t-elle et a-t-elle ce champ (avec ce type)?) Une fois que la requête est bien formée et que vous l'exécutez, vous attendez un (éventuellement vide) résultat valide ou une exception car le "serveur" a un problème (par exemple, le serveur de base de données est tombé en panne ou quelque chose du genre).En termes simples:
la source
throw
renvoyer un jeu vide ...Je suis d'accord avec la réponse d' Ewan, mais je souhaite ajouter un raisonnement spécifique.
Vous travaillez avec des opérations mathématiques, il serait donc judicieux de vous en tenir aux mêmes définitions mathématiques. D'un point de vue mathématique, le nombre d'ensembles r d'un ensemble n (c.-à-d. NCr ) est bien défini pour tout r> n> = 0. Il est nul. Par conséquent, renvoyer un ensemble vide serait le cas attendu d'un point de vue mathématique.
la source
Je trouve un bon moyen de déterminer si une exception doit être utilisée consiste à imaginer des personnes impliquées dans la transaction.
Prenant chercher le contenu d'un fichier comme exemple:
S'il vous plaît me chercher le contenu du fichier, "n'existe pas.txt"
une. "Voici le contenu: une collection vide de caractères"
b. "Euh, il y a un problème, ce fichier n'existe pas. Je ne sais pas quoi faire!"
S'il vous plaît me chercher le contenu du fichier, "existe mais est vide.txt"
une. "Voici le contenu: une collection vide de caractères"
b. "Euh, il y a un problème, il n'y a rien dans ce fichier. Je ne sais pas quoi faire!"
Certains seront certainement en désaccord, mais pour la plupart des gens, "Erm, il y a un problème" est logique lorsque le fichier n'existe pas et renvoie "une collection vide de caractères" lorsque le fichier est vide.
Donc, appliquant la même approche à votre exemple:
S'il vous plaît donnez-moi toutes les combinaisons de 4 éléments pour
{1, 2, 3}
une. Il n'y en a pas, voici un ensemble vide.
b. Il y a un problème, je ne sais pas quoi faire.
Encore une fois, "Il y a un problème" aurait du sens si, par exemple,
null
étaient proposés comme ensemble d'éléments, mais "voici un ensemble vide" semble constituer une réponse sensée à la demande ci-dessus.Si le renvoi d'une valeur vide masque un problème (par exemple, un fichier manquant, a
null
), une exception doit généralement être utilisée (à moins que la langue choisieoption/maybe
ne prenne en charge les types, ils ont parfois plus de sens). Sinon, renvoyer une valeur vide simplifiera probablement les coûts et respectera mieux le principe de moindre étonnement.la source
Comme c'est pour une bibliothèque à usage général, mon instinct serait Laisser l'utilisateur final choisir.
Tout comme nous avons
Parse()
etTryParse()
à notre disposition, nous pouvons avoir l'option que nous utilisons en fonction du résultat dont nous avons besoin de la fonction. Vous passeriez moins de temps à écrire et à maintenir un wrapper de fonction pour lever l'exception qu'à argumenter sur le choix d'une version unique de la fonction.la source
Single()
etSingleOrDefault()
immédiatement cintrées à l' esprit. Single lève une exception s'il existe un résultat nul ou supérieur à 1, alors que SingleOrDefault ne lève pas et renvoie à la placedefault(T)
. Peut-être que OP pourrait utiliserCombinationsOf()
etCombinationsOfOrThrow()
.Single
etSingleOrDefault
(ouFirst
etFirstOrDefault
) sont une histoire complètement différente, @RubberDuck. Reprenant mon exemple de mon commentaire précédent. C'est parfaitement bien de ne rien répondre à la question "Quels sont même les nombres premiers supérieurs à deux?" , puisque c’est une réponse mathématiquement juste et raisonnable. Quoi qu'il en soit, si vous demandez "Quel est le premier même plus grand que deux?" il n'y a pas de réponse naturelle. Vous ne pouvez simplement pas répondre à la question, car vous demandez un numéro unique. Ce n'est pas nul (ce n'est pas un nombre unique, mais un ensemble), pas 0. Nous lançons donc une exception.Vous devez valider les arguments fournis lorsque votre fonction est appelée. Et en fait, vous voulez savoir comment gérer les arguments non valides. Le fait que plusieurs arguments dépendent les uns des autres ne compense pas le fait que vous validez les arguments.
Ainsi, je voterais pour l’argumentException fournissant les informations nécessaires à l’utilisateur pour comprendre ce qui ne fonctionnait pas.
Par exemple, vérifiez la
public static TSource ElementAt<TSource>(this IEnumerable<TSource>, Int32)
fonction dans Linq. Ce qui lève une exception ArgumentOutOfRangeException si l'index est inférieur à 0 ou supérieur ou égal au nombre d'éléments de la source. Ainsi, l’index est validé par rapport à l’énumérable fourni par l’appelant.la source
new[] { 1, 2, 3 }.Skip(4).ToList();
obtenez un jeu vide, cela me fait penser que renvoyer un jeu vide est peut-être la meilleure option ici.Vous devez procéder de l’une des manières suivantes (tout en continuant à poser systématiquement des problèmes fondamentaux tels qu’un nombre négatif de combinaisons):
Fournissez deux implémentations, une qui renvoie un ensemble vide lorsque les entrées sont absurdes et l'autre qui renvoie. Essayez de les appeler
CombinationsOf
etCombinationsOfWithInputCheck
. Ou ce que vous aimez. Vous pouvez inverser la procédure pour que le vérificateur d’entrée soit le nom le plus court et la liste l’unCombinationsOfAllowInconsistentParameters
.Pour les méthodes Linq, renvoyez le vide
IEnumerable
sur le principe exact que vous avez décrit. Ajoutez ensuite ces méthodes Linq à votre bibliothèque:Enfin, utilisez ça comme ça:
ou comme ceci:
Veuillez noter que la méthode privée étant différente de la méthode publique, elle est requise pour que le comportement de projection ou d'action se produise lors de la création de la chaîne linq au lieu d'un certain temps ultérieurement, lors de son énumération. Vous voulez le jeter tout de suite.
Notez, cependant, que bien sûr, il doit énumérer au moins le premier élément afin de déterminer s’il existe des éléments. Je pense que c’est un inconvénient potentiel qui est principalement atténué par le fait que tout futur spectateur peut facilement dire qu’une
ThrowIfEmpty
méthode doit énumérer au moins un élément et ne devrait donc pas en être surprise. Mais tu ne sais jamais. Vous pourriez rendre cela plus expliciteThrowIfEmptyByEnumeratingAndReEmittingFirstItem
. Mais cela ressemble à une surdose gigantesque.Je pense que # 2 est tout à fait génial! Le code d'appel contient maintenant le pouvoir, et le lecteur suivant du code comprendra exactement ce qu'il fait et ne devra pas gérer d'exceptions inattendues.
la source
Je peux voir des arguments pour les deux cas d'utilisation - une exception est intéressante si le code en aval attend des ensembles contenant des données. D'un autre côté, un ensemble vide est tout simplement génial si on s'y attend.
Je pense que cela dépend des attentes de l'appelant s'il s'agit d'une erreur ou d'un résultat acceptable - je transférerais donc le choix à l'appelant. Peut-être présenter une option?
.CombinationsOf(4, CombinationsGenerationMode.Distinct, Options.AllowEmptySets)
la source
Il existe deux approches pour décider s’il n’ya pas de réponse évidente:
Écrivez le code en supposant d’abord une option, puis l’autre. Pensez à celui qui fonctionnerait le mieux en pratique.
Ajoutez un paramètre booléen "strict" pour indiquer si vous souhaitez que les paramètres soient strictement vérifiés ou non. Par exemple, Java
SimpleDateFormat
a unesetLenient
méthode pour tenter d'analyser les entrées qui ne correspondent pas complètement au format. Bien sûr, vous devez décider quelle est la valeur par défaut.la source
Sur la base de votre propre analyse, renvoyer l'ensemble vide semble clairement correct - vous l'avez même identifié comme quelque chose que certains utilisateurs peuvent souhaiter et ne sont pas tombés dans le piège d'interdire certaines utilisations, car vous ne pouvez pas imaginer que les utilisateurs veuillent l'utiliser. de cette façon.
Si vous pensez vraiment que certains utilisateurs voudront forcer les retours non vides, donnez-leur un moyen de demander ce comportement plutôt que de le forcer à tout le monde. Par exemple, vous pourriez:
AssertNonempty
chèque qu'ils peuvent mettre dans leurs chaînes.la source
Cela dépend vraiment de ce que vos utilisateurs s'attendent à obtenir. Pour un exemple (peu apparenté) si votre code effectue la division, vous pouvez générer une exception ou renvoyer
Inf
ouNaN
lorsque vous divisez par zéro. Ni a raison ni tort, cependant:Inf
dans une bibliothèque Python, les gens vous attaqueront pour avoir caché des erreursDans votre cas, je choisirais la solution la moins étonnante pour les utilisateurs finaux. Puisque vous développez une bibliothèque contenant des ensembles, un ensemble vide semble être une chose à laquelle vos utilisateurs s’attendraient, donc renvoyer cela semble être une chose sensée à faire. Mais je me trompe peut-être: vous avez une bien meilleure compréhension du contexte que quiconque, alors si vous vous attendez à ce que vos utilisateurs fassent en sorte que l'ensemble ne soit pas toujours vide, vous devriez lever immédiatement une exception.
Les solutions permettant à l'utilisateur de choisir (comme l'ajout d'un paramètre "strict") ne sont pas définitives, car elles remplacent la question initiale par une nouvelle question équivalente: "Quelle valeur
strict
doit être la valeur par défaut?"la source
Il est de conception courante (en mathématiques) que lorsque vous sélectionnez des éléments sur un ensemble, vous ne trouvez aucun élément et vous obtenez donc un ensemble vide . Bien sûr, vous devez être cohérent avec les mathématiques si vous allez de cette façon:
Ensemble de règles communes:
Votre question est très subtile:
Il se peut que l’entrée de votre fonction doive respecter un contrat : dans ce cas, toute entrée non valide devrait déclencher une exception, la fonction ne fonctionne pas avec les paramètres habituels.
Il se peut que l’entrée de votre fonction doive se comporter exactement comme un ensemble et puisse donc renvoyer un ensemble vide.
Maintenant, si j'étais en vous, j'irais de la manière "établie", mais avec un gros "MAIS".
Supposons que vous avez une collection qui "par hypotesis" ne devrait avoir que des étudiantes:
À présent, votre collection n'est plus un "ensemble pur", car vous avez un contrat dessus et vous devez donc appliquer votre contrat avec une exception.
Lorsque vous utilisez vos fonctions "set" d'une manière pure, vous ne devriez pas lancer d'exceptions en cas d'ensemble vide, mais si vous avez des collections qui ne sont plus des "ensembles purs", vous devriez alors lancer des exceptions là où cela convient .
Vous devez toujours faire ce qui vous semble plus naturel et cohérent: pour moi, un ensemble doit respecter des règles définies, tandis que les éléments non définis doivent avoir leurs règles bien pensées.
Dans votre cas, cela semble une bonne idée de faire:
Mais ce n'est pas vraiment une bonne idée, nous avons maintenant un état non valide qui peut se déclarerait lors de l' exécution à des moments aléatoires, mais qui est implicite dans le problème que nous ne pouvons pas le retirer, que nous pouvons déplacer l'exception dans une méthode utilitaire (il est exactement le même code, déplacé à des endroits différents), mais c'est faux et la meilleure chose à faire est de s'en tenir aux règles habituelles .
Ajouter une nouvelle complexité simplement pour montrer que vous pouvez écrire des méthodes de requêtes linq ne semble pas valoir votre problème , je suis à peu près sûr que si OP peut nous en dire plus sur son domaine, nous pourrions probablement trouver l'endroit où l'exception est vraiment nécessaire (si il est possible que le problème ne nécessite aucune exception).
la source
FemaleClass
sauf si elle ne comporte que des femmes (elle ne peut pas être créée publiquement, seule la classe Builder peut distribuer une instance) et éventuellement au moins une femme. . Ensuite, votre codeFemaleClass
n'a pas besoin de gérer les exceptions car l'objet est dans le mauvais état.