Exception vs résultat vide lorsque les entrées sont techniquement valides mais non satisfaisantes

108

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 someInputSetcontient 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 resultsera 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?

anaximandre
la source
66
Puisque l'ensemble vide est mathématiquement correct, il est probable que lorsque vous l'obtiendrez, c'est ce que vous voulez. Les définitions et les conventions mathématiques sont généralement choisies pour des raisons de cohérence et de commodité afin que les choses fonctionnent correctement avec elles.
asmeurer
5
@asmeurer Ils sont choisis pour que les théorèmes soient cohérents et pratiques. Ils ne sont pas choisis pour faciliter la programmation. (C'est parfois un avantage secondaire, mais parfois, ils rendent aussi la programmation plus difficile.)
jpmc26
7
@ jpmc26 "Ils sont choisis de manière à ce que les théorèmes soient cohérents et pratiques" - s'assurer que votre programme fonctionne toujours comme prévu correspond essentiellement à la démonstration d'un théorème.
artem
2
@ jpmc26 Je ne comprends pas pourquoi vous avez parlé de programmation fonctionnelle. Prouver l'exactitude des programmes impératifs est tout à fait possible, toujours avantageux, et peut également être effectué de manière informelle - réfléchissez un peu et utilisez un sens mathématique commun lorsque vous écrivez votre programme, et vous passerez moins de temps à tester. Statistiquement prouvé sur un échantillon d'un ;-)
artem
5
@DmitryGrigoryev 1/0 en tant qu'indéfini est beaucoup plus mathématiquement correct que 1/0 en tant qu'infini.
Asmeurer

Réponses:

145

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

Ewan
la source
5
Mathématiquement, oui, mais c'est aussi très probablement une source d'erreur. Si ce type d'entrée est attendu, il peut être préférable d'exiger que l'utilisateur intercepte l'exception dans une bibliothèque à usage général.
Casey Kuball
56
Je ne suis pas d'accord pour dire que c'est une cause d'erreur "très probable". Supposons, par exemple, que vous mettiez en œuvre un algorithme naïf "matchmaknig" à partir d'un jeu d'entrées assez large. Vous pouvez demander toutes les combinaisons de deux éléments, trouver le "meilleur" match parmi eux, puis supprimer les deux éléments et recommencer avec le nouvel ensemble plus petit. Finalement, votre jeu sera vide et il ne restera plus aucune paire à prendre en compte: une exception à ce stade est obstructive. Je pense qu'il y a beaucoup de façons de se retrouver dans une situation similaire.
Amalloy
11
En fait, vous ne savez pas s'il s'agit d'une erreur aux yeux de l'utilisateur. L'ensemble vide est quelque chose que l'utilisateur doit vérifier, si nécessaire. if (result.Any ()) DoOchose (result.First ()); else DoSomethingElse (); est beaucoup mieux que d'essayer {result.first (). dosomething ();} catch {DoSomethingElse ();}
Guran
4
@TripeHound C'est finalement ce qui a motivé la décision: demander au développeur qui utilise cette méthode de vérifier, puis de lancer, a un impact beaucoup moins important que le lancement d'une exception dont ils ne veulent pas, en termes d'effort de développement, de performance du programme et simplicité du déroulement du programme.
Anaximandre
6
@Darthfett Essayez de comparer ceci à une méthode d'extension LINQ existante: Where (). Si j'ai une clause: Where (x => 1 == 2) Je ne reçois pas d'exception, je reçois un ensemble vide.
Necoras
79

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:

>>> import itertools
>>> input = [1, 2, 3, 4, 5]
>>> list(itertools.combinations(input, 2))
[(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (4, 5)]
>>> list(itertools.combinations(input, 5))
[(1, 2, 3, 4, 5)]
>>> list(itertools.combinations(input, 6))
[]

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:

>>> list(itertools.combinations(input, -1))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: r must be non-negative

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 SQLrequête comme SELECT <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 un InvalidConditionsError.

Mathias Ettinger
la source
4
Réduit parce que ce n'est pas idiomatique dans l'espace linguistique C # / Linq (voir la réponse de sbecker pour savoir comment des problèmes similaires en dehors des limites sont traités dans le langage). Si c'était une question Python, ce serait un +1 de ma part.
James Snell
32
@JamesSnell Je vois à peine comment cela se rapporte au problème des hors-jeu. Nous ne sélectionnons pas des éléments par index pour les réorganiser, nous sélectionnons des 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 des néléments de ladite collection.
Mathias Ettinger
1
Néanmoins, Python n'est pas un bon oracle pour les questions en C #: par exemple, C # le permettrait 1.0 / 0, Python ne le permettrait pas.
Dmitry Grigoryev
2
@MathiasEttinger Les instructions de Python concernant l'utilisation et les exceptions des exceptions sont très différentes de celles de C #. Par conséquent, l'utilisation du comportement des modules Python en tant qu'indication n'est pas une bonne mesure pour C #.
Ant P
2
Au lieu de choisir Python, nous pourrions simplement choisir SQL. Une requête bien formée 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).
Bakuriu
72

En termes simples:

  • S'il y a une erreur, vous devriez lever une exception. Cela peut impliquer de faire les choses par étapes plutôt que dans un seul appel chaîné afin de savoir exactement où l'erreur s'est produite.
  • S'il n'y a pas d'erreur mais que le jeu résultant est vide, ne déclenchez pas d'exception, retournez le jeu vide. Un ensemble vide est un ensemble valide.
Tulains Córdova
la source
23
+1, 0 est un nombre parfaitement valide et extrêmement important. Il y a tellement de cas dans lesquels une requête peut légitimement renvoyer zéro hits que la levée d'une exception serait extrêmement ennuyeuse.
Kilian Foth
19
bon conseil, mais votre réponse est-elle claire?
Ewan
54
Je ne comprends toujours pas si cette réponse suggère que l'OP devrait throwrenvoyer un jeu vide ...
James Snell
7
Votre réponse dit que je peux faire l’un ou l’autre choix, selon qu’il y ait ou non une erreur, mais vous ne précisez pas si vous considérez l’exemple de scénario comme une erreur. Dans le commentaire ci-dessus, vous dites que je devrais renvoyer un ensemble vide "s'il n'y a pas de problèmes", mais encore une fois, des conditions non satisfaisantes pourraient être considérées comme un problème, et vous continuez en affirmant que je ne soulève pas d'exceptions au moment opportun. Alors que dois-je faire?
Anaximandre
3
Ah, je vois - vous avez peut-être mal compris; Je n'enchaine rien, j'écris une méthode que je m'attends à utiliser dans une chaîne. Je me demande si le futur développeur hypothétique qui utilise cette méthode voudrait l'inclure dans le scénario ci-dessus.
Anaximandre
53

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.

sigy
la source
9
C'est un bon point. Si la bibliothèque était de niveau supérieur, comme choisir des combinaisons de couleurs pour créer une palette, il est alors logique de générer une erreur. Parce que vous savez qu'une palette sans couleurs n'est pas une palette. Mais un ensemble sans entrées est toujours un ensemble et le calcul le définit comme égal à un ensemble vide.
Captain Man
1
Bien mieux que la réponse d'Ewans, il cite une pratique mathématique. +1
user949300
24

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:

  1. 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!"

  2. 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:

  1. 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 choisie option/maybene 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.

David Arno
la source
4
Ce conseil est bon, mais ne s'applique pas à ce cas. Un meilleur exemple: combien de jours après le 30 janvier ?: 1 Combien de jours après le 30 février?: 0 Combien de jours après le 30 février ?: Exception : février: Exception
Guran
9

Comme c'est pour une bibliothèque à usage général, mon instinct serait Laisser l'utilisateur final choisir.

Tout comme nous avons Parse()et TryParse()à 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.

James Snell
la source
3
+1 parce que j'aime toujours l'idée de choix et que ce modèle a tendance à encourager les programmeurs à valider leurs propres entrées. La simple existence d'une fonction TryFoo indique clairement que certaines combinaisons d'entrées peuvent poser problème et si la documentation explique ces problèmes potentiels, le codeur peut les rechercher et gérer les entrées non valides de manière plus propre qu'elles ne le pourraient en captant simplement une exception. ou répondant à un ensemble vide. Ou ils peuvent être paresseux. De toute façon, la décision leur appartient.
aleppke
19
Non, un ensemble vide est la réponse mathématiquement correcte. Faire autre chose, c'est redéfinir des mathématiques bien établies, ce qui ne devrait pas être fait sur un coup de tête. Pendant que nous y sommes, allons-nous redéfinir la fonction d’exponentiation pour renvoyer une erreur si l’exposant est égal à zéro, car nous décidons que nous n’aimons pas la convention voulant que tout nombre élevé à la puissance "zéro" soit égal à 1?
Wildcard
1
J'étais sur le point de répondre à quelque chose de similaire. Les méthodes d'extension LINQ Single()et SingleOrDefault()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 place default(T). Peut-être que OP pourrait utiliser CombinationsOf()et CombinationsOfOrThrow().
RubberDuck
1
@Wildcard - vous parlez de deux résultats différents pour la même entrée, ce qui n'est pas la même chose. Le PO n'est intéressé qu'à choisir a) le résultat ou b) en lançant une exception qui n'est pas un résultat.
James Snell
4
Singleet SingleOrDefault(ou Firstet FirstOrDefault) 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.
Paul Kertscher
4

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.

sbecker
la source
3
+1 pour avoir cité un exemple de situation similaire dans la langue.
James Snell
13
Bizarrement, votre exemple m'a fait réfléchir et m'a conduit à quelque chose qui, à mon avis, constitue un puissant contre-exemple. Comme je l'ai noté, cette méthode est conçue pour ressembler à LINQ, afin que les utilisateurs puissent la chaîner avec d'autres méthodes LINQ. Si vous 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.
Anaximandre
14
Ce n'est pas un problème de sémantique de langage, la sémantique du domaine problématique fournit déjà la réponse correcte, c'est-à-dire de renvoyer l'ensemble vide. Faire autre chose enfreint le principe de moindre étonnement pour quelqu'un qui travaille dans le domaine des problèmes combinatoires.
Ukko
1
Je commence à comprendre les arguments pour retourner un ensemble vide. Pourquoi ça aurait du sens. Au moins pour cet exemple particulier.
sbecker
2
@sbecker, pour ramener à la maison point: Si la question était « Comment beaucoup de combinaisons sont là des ... » alors « zéro » serait une réponse tout à fait valable. Dans le même ordre d'idées, "Quelles sont les combinaisons telles que ..." est un ensemble vide comme réponse tout à fait valable. Si la question était: "Quelle est la première combinaison telle que ...", alors et alors seulement une exception serait appropriée, étant donné que la question n'est pas susceptible de réponse. Voir aussi le commentaire de Paul K .
Wildcard
3

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):

  1. 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 CombinationsOfet CombinationsOfWithInputCheck. 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’un CombinationsOfAllowInconsistentParameters.

  2. Pour les méthodes Linq, renvoyez le vide IEnumerablesur le principe exact que vous avez décrit. Ajoutez ensuite ces méthodes Linq à votre bibliothèque:

    public static class EnumerableExtensions {
       public static IEnumerable<T> ThrowIfEmpty<T>(this IEnumerable<T> input) {
          return input.IfEmpty<T>(() => {
             throw new InvalidOperationException("An enumerable was unexpectedly empty");
          });
       }
    
       public static IEnumerable<T> IfEmpty<T>(
          this IEnumerable<T> input,
          Action callbackIfEmpty
       ) {
          var enumerator = input.GetEnumerator();
          if (!enumerator.MoveNext()) {
             // Safe because if this throws, we'll never run the return statement below
             callbackIfEmpty();
          }
          return EnumeratePrimedEnumerator(enumerator);
       }
    
       private static IEnumerable<T> EnumeratePrimedEnumerator<T>(
          IEnumerator<T> primedEnumerator
       ) {
          yield return primedEnumerator.Current;
          while (primedEnumerator.MoveNext()) {
             yield return primedEnumerator.Current;
          }
       }
    }

    Enfin, utilisez ça comme ça:

    var result = someInputSet
       .CombinationsOf(4, CombinationsGenerationMode.Distinct)
       .ThrowIfEmpty()
       .Select(combo => /* do some operation to a combination */)
       .ToList();

    ou comme ceci:

    var result = someInputSet
       .CombinationsOf(4, CombinationsGenerationMode.Distinct)
       .IfEmpty(() => _log.Warning(
          $@"Unexpectedly received no results when creating combinations for {
             nameof(someInputSet)}"
       ))
       .Select(combo => /* do some operation to a combination */)
       .ToList();

    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 ThrowIfEmptymé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 explicite ThrowIfEmptyByEnumeratingAndReEmittingFirstItem. 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.

ErikE
la source
Explique ce que tu veux dire, s'il te plait. Comment quelque chose dans mon post "ne se comporte pas comme un ensemble" et pourquoi est-ce une mauvaise chose?
ErikE
Vous faites essentiellement la même chose que dans ma réponse, au lieu d’envelopper la requête que vous avez effectuée pour interroger une condition If, le résultat ne change pas uu
GameDeveloper
Je ne vois pas en quoi ma réponse est "fondamentalement identique" à votre réponse. Ils me semblent complètement différents.
ErikE
Fondamentalement, vous appliquez simplement la même condition que "ma mauvaise classe". Mais vous ne dites pas ça ^^. Acceptez-vous que vous imposiez l’existence d’un élément dans le résultat de la requête?
GameDeveloper
Vous allez devoir parler plus clairement pour les programmeurs manifestement stupides qui ne comprennent toujours pas ce dont vous parlez. Quelle classe est mauvaise? Pourquoi est-ce mauvais? Je ne fais rien imposer. J'autorise l'utilisateur de la classe à décider du comportement final au lieu du créateur de la classe. Cela permet d'utiliser un paradigme de codage cohérent dans lequel les méthodes Linq ne lancent pas au hasard en raison d'un aspect de calcul qui ne constitue pas vraiment une violation des règles normales, comme si vous demandiez -1 éléments à la fois.
ErikE
2

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)

Christian Sauer
la source
10
Pendant que je comprends où vous voulez en venir avec ceci, j'essaie d'éviter que les méthodes ayant beaucoup d'options passées ne modifient leur comportement. Je ne suis toujours pas satisfait à 100% du mode enum qui existe déjà; Je n'aime vraiment pas l'idée d'un paramètre d'option qui modifie le comportement d'exception d'une méthode. Je me sens comme si une méthode doit lancer une exception, elle doit lancer; si vous voulez masquer cette exception, c'est votre décision en tant que code d'appel, et vous ne pouvez pas forcer mon code à utiliser la bonne option de saisie.
Anaximandre
Si l'appelant s'attend à un ensemble qui n'est pas vide, c'est à l'appelant de gérer un ensemble vide ou de lever une exception. En ce qui concerne l'appelé, un ensemble vide est une réponse parfaite. Et vous pouvez avoir plus d'un appelant, avec des opinions différentes sur le point de savoir si les postes vides conviennent ou non.
gnasher729
2

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 SimpleDateFormata une setLenientmé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.

JollyJoker
la source
2

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:

  • Faites-en une option de configuration sur n'importe quel objet effectuant l'action pour l'utilisateur.
  • Faites-en un indicateur que l'utilisateur peut éventuellement transmettre à la fonction.
  • Fournissez un AssertNonemptychèque qu'ils peuvent mettre dans leurs chaînes.
  • Faites deux fonctions, une qui affirme non vide et une qui ne le fait pas.

la source
cela ne semble rien offrir de substantiel sur les points soulevés et expliqués dans les 12 réponses précédentes
gnat
1

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 Infou NaNlorsque vous divisez par zéro. Ni a raison ni tort, cependant:

  • si vous revenez Infdans une bibliothèque Python, les gens vous attaqueront pour avoir caché des erreurs
  • si vous générez une erreur dans une bibliothèque Matlab, les gens vous reprochent de ne pas avoir traité les données avec des valeurs manquantes.

Dans 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 strictdoit être la valeur par défaut?"

Dmitry Grigoryev
la source
2
J'ai pensé utiliser l'argument NaN dans ma réponse et je partage votre opinion. Nous ne pouvons pas en dire plus sans connaître le domaine de l'OP
GameDeveloper
0

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:

  • Set.Foreach (prédicat); // retourne toujours vrai pour les ensembles vides
  • Set.Exists (prédicat); // retourne toujours faux pour les ensembles vides

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:

class FemaleClass{

    FemaleStudent GetAnyFemale(){
        var femaleSet= mySet.Select( x=> x.IsFemale());
        if(femaleSet.IsEmpty())
            throw new Exception("No female students");
        else
            return femaleSet.Any();
    }
}

À 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:

List SomeMethod( Set someInputSet){
    var result = someInputSet
        .CombinationsOf(4, CombinationsGenerationMode.Distinct)
        .Select(combo => /* do some operation to a combination */)
        .ToList();

    // the only information here is that set is empty => there are no combinations

    // BEWARE! if 0 here it may be invalid input, but also a empty set
    if(result.Count == 0)  //Add: "&&someInputSet.NotEmpty()"

    // we go a step further, our API require combinations, so
    // this method cannot satisfy the API request, then we throw.
         throw new Exception("you requsted impossible combinations");

    return result;
}

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).

Développeur de jeu
la source
Le problème est que vous utilisez des objets définis mathématiquement pour résoudre un problème, mais le problème lui-même peut ne pas nécessiter un objet défini mathématiquement en tant que réponse (en fait, renvoyer une liste). Tout dépend du niveau le plus élevé du problème, quelle que soit la façon dont vous le résolvez, cela s'appelle encapsulation / dissimulation d'informations.
GameDeveloper
La raison pour laquelle votre "SomeMethod" ne devrait plus se comporter comme un ensemble est que vous demandez une information "hors des ensembles", plus précisément dans la partie commentée "&& someInputSet.NotEmpty ()"
GameDeveloper
1
NON! Vous ne devriez pas lancer une exception dans votre classe féminine. Vous devez utiliser le modèle Builder qui impose de ne pas obtenir une instance de FemaleClasssauf 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 code FemaleClassn'a pas besoin de gérer les exceptions car l'objet est dans le mauvais état.
ErikE
Vous supposez que la classe est si simple que vous pouvez imposer ses conditions préalables simplement par le constructeur, en réalité. Votre argument est imparfait. Si vous interdisez de ne plus avoir de femmes, alors vous ne pouvez pas avoir de méthode pour supprimer les étudiants de la classe ou votre méthode doit lever une exception lorsqu'il reste 1 élève. Vous venez de déplacer mon problème ailleurs. Le fait est qu'il y aura toujours un état de condition préalable / de fonction qui ne peut pas être maintenu "par conception" et que l'utilisateur va rompre: c'est la raison pour laquelle des exceptions existent! C’est un truc assez théorique, j’espère que le vote négatif ne vous appartient pas.
GameDeveloper
Le vote par voix basse ne m'appartient pas, mais si c'était le cas, j'en serais fier . Je vote avec précaution mais avec conviction. Si la règle de votre classe est qu'elle DOIT contenir un élément et que tous les éléments doivent remplir une condition spécifique, il est donc déconseillé de laisser la classe dans un état incohérent. Déplacer le problème ailleurs est exactement mon intention! Je veux que le problème se produise au moment de la construction, pas au moment de l’utilisation. L'utilisation d'une classe Builder au lieu d'un lancement de constructeur est que vous pouvez créer un état très complexe dans de nombreuses pièces et parties grâce à des incohérences.
ErikE