Exceção vs conjunto de resultados vazio quando as entradas são tecnicamente válidas, mas insatisfatórias

108

Estou desenvolvendo uma biblioteca destinada a lançamento público. Ele contém vários métodos para operar em conjuntos de objetos - gerando, inspecionando, particionando e projetando os conjuntos em novas formas. Caso seja relevante, é uma biblioteca de classes C # contendo extensões no estilo LINQ IEnumerable, a ser lançada como um pacote NuGet.

Alguns dos métodos nesta biblioteca podem receber parâmetros de entrada insatisfatórios. Por exemplo, nos métodos combinatórios, existe um método para gerar todos os conjuntos de n itens que podem ser construídos a partir de um conjunto de origem de m itens. Por exemplo, dado o conjunto:

1, 2, 3, 4, 5

e pedir combinações de 2 produziria:

1, 2
1, 3
1, 4
etc ...
5, 3
5, 4

Agora, obviamente, é possível pedir algo que não pode ser feito, como fornecer um conjunto de 3 itens e, em seguida, solicitar combinações de 4 itens enquanto define a opção que diz que ele pode usar cada item apenas uma vez.

Nesse cenário, cada parâmetro é válido individualmente :

  • A coleção de origem não é nula e contém itens
  • O tamanho solicitado das combinações é um número inteiro diferente de zero positivo
  • O modo solicitado (use cada item apenas uma vez) é uma opção válida

No entanto, o estado dos parâmetros, quando reunidos, causa problemas.

Nesse cenário, você esperaria que o método gerasse uma exceção (por exemplo InvalidOperationException) ou retornasse uma coleção vazia? Qualquer um parece válido para mim:

  • Você não pode produzir combinações de n itens a partir de um conjunto de m itens em que n> m se você puder usar cada item apenas uma vez, portanto, essa operação pode ser considerada impossível InvalidOperationException.
  • O conjunto de combinações de tamanho n que pode ser produzido a partir de m itens quando n> m é um conjunto vazio; nenhuma combinação pode ser produzida.

O argumento para um conjunto vazio

Minha primeira preocupação é que uma exceção evite o encadeamento idiomático de métodos no estilo LINQ quando você estiver lidando com conjuntos de dados que podem ter tamanho desconhecido. Em outras palavras, você pode fazer algo assim:

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

Se o seu conjunto de entradas tiver tamanho variável, o comportamento desse código é imprevisível. Se .CombinationsOf()lança uma exceção quando someInputSetpossui menos de 4 elementos, esse código às vezes falha no tempo de execução sem pré-verificação. No exemplo acima, essa verificação é trivial, mas se você está chamando pela metade de uma cadeia mais longa de LINQ, isso pode ser entediante. Se retornar um conjunto vazio, ele resultestará vazio, com o qual você poderá estar perfeitamente feliz.

O argumento para uma exceção

Minha segunda preocupação é que o retorno de um conjunto vazio possa ocultar problemas - se você estiver chamando esse método na metade de uma cadeia de LINQ e retornar silenciosamente um conjunto vazio, poderá encontrar alguns problemas mais tarde ou encontrar um vazio. conjunto de resultados e pode não ser óbvio como isso aconteceu, pois você definitivamente tinha algo no conjunto de entradas.

O que você esperaria e qual é o seu argumento para isso?

anaximander
fonte
66
Como o conjunto vazio é matematicamente correto, é provável que, quando você o obtenha, seja realmente o que deseja. As definições e convenções matemáticas são geralmente escolhidas por consistência e conveniência, para que as coisas funcionem com elas.
asmeurer
5
@asmeurer Eles são escolhidos para que os teoremas sejam consistentes e convenientes. Eles não são escolhidos para facilitar a programação. (Que às vezes é um benefício lateral, mas às vezes eles fazem a programação mais difícil, também.)
jpmc26
7
@ jpmc26 "Eles são escolhidos para que os teoremas sejam consistentes e convenientes" - garantir que seu programa sempre funcione conforme o esperado é essencialmente equivalente a provar um teorema.
Artem
2
@ jpmc26 Não entendo por que você mencionou programação funcional. Provar a correção de programas imperativos é bem possível, sempre vantajoso, e também pode ser feito informalmente - pense um pouco e use o bom senso matemático ao escrever seu programa, e você gastará menos tempo testando. Estatisticamente comprovada na amostra de um ;-)
Artem
5
@DmitryGrigoryev 1/0 como indefinido é muito mais matematicamente correto que 1/0 como infinito.
asmeurer

Respostas:

145

Retornar um conjunto vazio

Eu esperaria um conjunto vazio porque:

Existem 0 combinações de 4 números do conjunto de 3 quando só posso usar cada número uma vez

Ewan
fonte
5
Matematicamente, sim, mas também é provavelmente uma fonte de erro. Se esse tipo de entrada for esperado, talvez seja melhor exigir que o usuário capture a exceção em uma biblioteca de uso geral.
Casey Kuball
56
Não concordo que seja uma causa "muito provável" de erro. Suponha, por exemplo, que você esteja implementando um algoritmo ingênuo "matchmaknig" a partir de um conjunto de entradas amplo. Você pode solicitar todas as combinações de dois itens, encontrar a "melhor" correspondência entre eles e remover os dois elementos e começar de novo com o novo conjunto menor. Eventualmente, seu set ficará vazio e não haverá pares a serem considerados: uma exceção neste momento é obstrutiva. Eu acho que existem muitas maneiras de acabar em uma situação semelhante.
amalloy
11
Na verdade, você não sabe se isso é um erro aos olhos do usuário. O conjunto vazio é algo que o usuário deve procurar, se necessário. if (result.Any ()) DoSomething (result.First ()); else DoSomethingElse (); é muito melhor do que tentar, catch {DoSomethingElse ();} {result.first () dosomething ().}
Guran
4
@TripeHound É o que levou a decisão no final: exigir que o desenvolvedor que usa esse método verifique e depois jogue, tem um impacto muito menor do que lançar uma exceção que eles não desejam, em termos de esforço de desenvolvimento, desempenho do programa e simplicidade do fluxo do programa.
Anaximander
6
@Darthfett Tente comparar isso com um método de extensão LINQ existente: Where (). Se eu tiver uma cláusula: Onde (x => 1 == 2) Eu não recebo uma exceção, recebo um conjunto vazio.
Necoras 01/12
79

Em caso de dúvida, pergunte a outra pessoa.

Sua função exemplo tem uma forma muito semelhante em Python: itertools.combinations. Vamos ver como isso funciona:

>>> 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))
[]

E parece perfeitamente bem para mim. Eu esperava um resultado que pudesse repetir e consegui um.

Mas, obviamente, se você perguntasse algo estúpido:

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

Então, eu diria que, se todos os seus parâmetros forem validados, mas o resultado for um conjunto vazio, retornar um conjunto vazio, você não será o único a fazê-lo.


Como dito por @Bakuriu nos comentários, também é o mesmo para uma SQLconsulta como SELECT <columns> FROM <table> WHERE <conditions>. Enquanto <columns>, <table>, <conditions>são bem formados formados e referem-se a nomes existentes, você pode construir um conjunto de condições que excluem mutuamente. A consulta resultante não produziria nenhuma linha em vez de gerar um InvalidConditionsError.

Mathias Ettinger
fonte
4
Voto negativo porque não é idiomático no espaço de linguagem C # / Linq (consulte a resposta de sbecker para saber como os problemas fora dos limites são tratados no idioma). Se essa fosse uma pergunta em Python, seria um +1 de mim.
James Snell
32
@ JamesSnell Eu mal vejo como isso se relaciona com o problema fora dos limites. Não estamos escolhendo elementos por índices para reordená-los, estamos escolhendo nelementos em uma coleção para contar o número de maneiras que podemos selecioná-los. Se, em algum momento, não houver elementos ao selecioná-los, não haverá (0) maneira de selecionar nelementos da coleção.
Mathias Ettinger
1
Ainda assim, o Python não é um bom oráculo para questões de C #: por exemplo, o C # permitiria 1.0 / 0, o Python não.
Dmitry Grigoryev
2
@MathiasEttinger As diretrizes do Python sobre como e quando usar exceções são muito diferentes dos C #, portanto, usar o comportamento dos módulos Python como uma diretriz não é uma boa métrica para C #.
Ant P1
2
Em vez de escolher o Python, podemos simplesmente escolher o SQL. Uma consulta bem formada SELECT sobre uma tabela produz uma exceção quando o conjunto de resultados está vazio? (spoiler: não!). A "boa formação" de uma consulta depende apenas da própria consulta e de alguns metadados (por exemplo, a tabela existe e possui esse campo (com esse tipo)?) Depois que a consulta estiver bem formada e você a executar, espera-se um (possivelmente vazio) resultado válido ou uma exceção porque o "servidor" teve um problema (por exemplo, o servidor db caiu ou algo assim).
Bakuriu
72

Em termos leigos:

  • Se houver um erro, você deve gerar uma exceção. Isso pode envolver a execução de etapas em vez de em uma única chamada em cadeia para saber exatamente onde o erro ocorreu.
  • Se não houver erro, mas o conjunto resultante estiver vazio, não crie uma exceção, retorne o conjunto vazio. Um conjunto vazio é um conjunto válido.
Tulains Córdova
fonte
23
+1, 0 é um número perfeitamente válido e, de fato, extremamente importante. Existem muitos casos em que uma consulta pode legitimamente retornar zero ocorrências que gerar uma exceção seria altamente irritante.
precisa
19
bons conselhos, mas sua resposta é clara?
Ewan
54
Estou ainda nenhum o mais sábio a respeito de se esta resposta sugere que o OP deve throwou retornar um conjunto vazio ...
James Snell
7
Sua resposta diz que eu poderia fazer isso, dependendo da existência de um erro, mas você não mencionou se consideraria o cenário de exemplo um erro. No comentário acima, você diz que devo retornar um conjunto vazio "se não houver problemas", mas, novamente, condições insatisfatórias podem ser consideradas um problema, e você continua dizendo que não estou levantando exceções quando devo. Então, o que devo fazer?
Anaximander
3
Ah, entendo - você pode ter entendido mal; Não estou encadeando nada, estou escrevendo um método que espero que seja usado em uma cadeia. Eu estou querendo saber se o hipotético desenvolvedor futuro que usa esse método gostaria que ele jogasse no cenário acima.
Anaximander
53

Concordo com a resposta de Ewan, mas quero acrescentar um raciocínio específico.

Você está lidando com operações matemáticas, portanto, pode ser um bom conselho seguir as mesmas definições matemáticas. Do ponto de vista matemático, o número de conjuntos r de um conjunto n (ou seja, nCr ) está bem definido para todos os r> n> = 0. É zero. Portanto, retornar um conjunto vazio seria o caso esperado do ponto de vista matemático.

sigy
fonte
9
Este é um bom argumento. Se a biblioteca era de nível superior, como escolher combinações de cores para criar uma paleta - faz sentido gerar um erro. Porque você conhece uma paleta sem cores não é uma paleta. Mas um conjunto sem entradas ainda é um conjunto e a matemática o define como igual a um conjunto vazio.
Captain Man
1
Muito melhor do que a resposta de Ewans, porque cita a prática matemática. 1
user949300
24

Eu acho que uma boa maneira de determinar se deve usar uma exceção é imaginar pessoas envolvidas na transação.

Tomando como exemplo o conteúdo de um arquivo:

  1. Por favor, traga-me o conteúdo do arquivo, "não existe.txt"

    uma. "Aqui está o conteúdo: uma coleção vazia de caracteres"

    b. "Erm, há um problema, esse arquivo não existe. Eu não sei o que fazer!"

  2. Por favor, traga-me o conteúdo do arquivo "existe, mas está vazio.txt"

    uma. "Aqui está o conteúdo: uma coleção vazia de caracteres"

    b. "Erm, há um problema, não há nada neste arquivo. Eu não sei o que fazer!"

Sem dúvida, alguns discordarão, mas para a maioria das pessoas, "Erm, há um problema" faz sentido quando o arquivo não existe e retornando "uma coleção vazia de caracteres" quando o arquivo está vazio.

Então, aplicando a mesma abordagem ao seu exemplo:

  1. Por favor, me dê todas as combinações de 4 itens para {1, 2, 3}

    uma. Não há, aqui está um conjunto vazio.

    b. Há um problema, não sei o que fazer.

Novamente, "Há um problema" faria sentido se, por exemplo, nullfossem oferecidos como o conjunto de itens, mas "aqui está um conjunto vazio" parece uma resposta sensata à solicitação acima.

Se o retorno de um valor vazio mascarar um problema (por exemplo, um arquivo ausente, a null), uma exceção geralmente deverá ser usada (a menos que o idioma escolhido ofereça suporte a option/maybetipos, às vezes eles fazem mais sentido). Caso contrário, o retorno de um valor vazio provavelmente simplificará o custo e obedecerá melhor ao princípio de menor espanto.

David Arno
fonte
4
Este conselho é bom, mas não se aplica a este caso. Um exemplo melhor: Quantos dias após o dia 30 de janeiro ?: 1 Quantos dias após o dia 30 de fevereiro ?: 0 Quantos dias após o dia 30 de fevereiro ?: Exceção Quantas horas de trabalho no dia 30 : th de fevereiro: Exception
Guran
9

Como é para uma biblioteca de uso geral, meu instinto seria deixar o usuário final escolher.

Assim como temos Parse()e TryParse()disponível para nós, podemos ter a opção de que usamos, dependendo de qual saída precisamos da função. Você gastaria menos tempo escrevendo e mantendo um wrapper de função para lançar a exceção do que discutindo sobre a escolha de uma única versão da função.

James Snell
fonte
3
+1 porque eu sempre gosto da ideia de escolha e porque esse padrão tem a tendência de incentivar os programadores a validar suas próprias contribuições. A mera existência de uma função TryFoo torna óbvio que certas combinações de entradas podem causar problemas e, se a documentação explicar quais são esses problemas em potencial, o codificador poderá procurá-las e manipular entradas inválidas de maneira mais limpa do que poderia apenas capturando uma exceção. ou respondendo a um conjunto vazio. Ou eles podem ser preguiçosos. De qualquer maneira, a decisão é deles.
aleppke
19
Não, um conjunto vazio é a resposta matematicamente correta. Fazer outra coisa é redefinir a matemática firmemente acordada, o que não deve ser feito por um capricho. Enquanto estamos nisso, redefiniremos a função de exponenciação para gerar um erro se o expoente for igual a zero, porque decidimos que não gostamos da convenção de que qualquer número aumentado para o poder "zeroth" seja igual a 1?
Curinga
1
Eu estava prestes a responder algo semelhante. Os métodos de extensão Linq Single()e SingleOrDefault()imediatamente vieram à mente. Single lança uma exceção se houver zero ou> 1 resultado, enquanto SingleOrDefault não lançará e, em vez disso, retornará default(T). Talvez o OP possa usar CombinationsOf()e CombinationsOfOrThrow().
RubberDuck
1
@Wildcard - você está falando de dois resultados diferentes para a mesma entrada, o que não é a mesma coisa. O PO está interessado apenas em escolher a) o resultado ou b) lançar uma exceção que não é um resultado.
James Snell
4
Singlee SingleOrDefault(ou Firste FirstOrDefault) são uma história completamente diferente, @RubberDuck. Pegando meu exemplo do meu comentário anterior. É perfeitamente bom não responder à pergunta "O que existem primos maiores que dois?" , já que esta é uma resposta matematicamente sólida e sensata. Enfim, se você está perguntando "Qual é o primeiro primo par maior que dois?" não há resposta natural. Você simplesmente não pode responder à pergunta, porque você está pedindo um único número. Não é nenhum (não é um número único, mas um conjunto), não 0. Portanto, lançamos uma exceção.
Paul Kertscher
4

Você precisa validar os argumentos fornecidos quando sua função é chamada. De fato, você deseja saber como lidar com argumentos inválidos. O fato de vários argumentos dependerem um do outro não compensa o fato de você validar os argumentos.

Assim, eu votaria na ArgumentException, fornecendo as informações necessárias para o usuário entender o que deu errado.

Como exemplo, verifique a public static TSource ElementAt<TSource>(this IEnumerable<TSource>, Int32)função no Linq. Que lança uma ArgumentOutOfRangeException se o índice for menor que 0 ou maior que ou igual ao número de elementos na origem. Assim, o índice é validado em relação ao enumerável fornecido pelo chamador.

sbecker
fonte
3
+1 por citar um exemplo de situação semelhante no idioma.
James Snell
13
Estranhamente, seu exemplo me fez pensar e me levou a algo que acho que faz um forte contra-exemplo. Como observei, esse método foi projetado para ser semelhante ao LINQ, para que os usuários possam encadeá-lo com outros métodos LINQ. Se você new[] { 1, 2, 3 }.Skip(4).ToList();receber um conjunto vazio, o que me faz pensar que retornar um conjunto vazio talvez seja a melhor opção aqui.
Anaximander
14
Esse não é um problema de semântica de idioma; a semântica do domínio do problema já fornece a resposta correta, ou seja, para retornar o conjunto vazio. Fazer qualquer outra coisa viola o princípio de menos espanto para alguém que trabalha no domínio do problema combinatório.
Ukko
1
Estou começando a entender os argumentos para retornar um conjunto vazio. Por que faria sentido. Pelo menos para este exemplo em particular.
Sbecker 01/12/16
2
@sbecker, para trazer o repouso do ponto: Se a pergunta fosse "Como muitas combinações estão lá de ..." e depois "zero" seria uma resposta completamente válido. Da mesma forma, "Quais são as combinações que ..." tem o conjunto vazio como uma resposta completamente válida. Se a pergunta fosse: "Qual é a primeira combinação tal que ...", então e somente então uma exceção seria apropriada, uma vez que a pergunta não é respondida. Veja também o comentário de Paul K .
Curinga
3

Você deve seguir um destes procedimentos (embora continue apresentando problemas básicos de maneira consistente, como um número negativo de combinações):

  1. Forneça duas implementações, uma que retorne um conjunto vazio quando as entradas juntas não fizerem sentido e uma que seja lançada. Tente ligar para eles CombinationsOfe CombinationsOfWithInputCheck. Ou o que você quiser. Você pode reverter isso para que a verificação de entrada seja o nome mais curto e a lista seja CombinationsOfAllowInconsistentParameters.

  2. Para os métodos Linq, retorne o vazio IEnumerablena premissa exata que você descreveu. Em seguida, adicione estes métodos Linq à sua biblioteca:

    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;
          }
       }
    }

    Por fim, use o seguinte:

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

    ou assim:

    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();

    Observe que o método privado diferente dos públicos é necessário para que o comportamento de execução ou ação ocorra quando a cadeia linq é criada, em vez de algum tempo depois, quando é enumerada. Você quer jogar imediatamente.

    Observe, no entanto, que é claro que ele precisa enumerar pelo menos o primeiro item para determinar se há algum item. Essa é uma desvantagem potencial que, na minha opinião, é atenuada principalmente pelo fato de que futuros espectadores podem facilmente entender que um ThrowIfEmptymétodo precisa enumerar pelo menos um item, portanto, não deve ser surpreendido por isso. Mas você nunca sabe. Você poderia tornar isso mais explícito ThrowIfEmptyByEnumeratingAndReEmittingFirstItem. Mas isso parece um exagero gigantesco.

Eu acho que # 2 é bastante, bem, incrível! Agora, o poder está no código de chamada, e o próximo leitor do código entenderá exatamente o que está fazendo e não precisará lidar com exceções inesperadas.

ErikE
fonte
Por favor, explique o que você quer dizer. Como algo no meu post "não se comporta como um conjunto" e por que isso é ruim?
ErikE
Você está basicamente fazendo o mesmo da minha resposta, em vez de envolver a consulta que aconteceu para consultar uma condição IF, o resultado não muda uu
GameDeveloper
Não vejo como minha resposta é "basicamente fazendo o mesmo" da sua resposta. Eles parecem completamente diferentes para mim.
ErikE
Basicamente, você está apenas aplicando a mesma condição de "minha classe ruim". Mas você não está dizendo isso ^^. Você concorda que está impondo a existência de 1 elemento no resultado da consulta?
GameDeveloper 3/16/16
Você terá que falar mais claramente para os programadores evidentemente estúpidos que ainda não entendem o que você está falando. Que classe é ruim? Por que isso é ruim? Eu não estou aplicando nada. Estou permitindo que o USUÁRIO da classe decida o comportamento final em vez do CRIADOR da classe. Isso permite usar um paradigma de codificação consistente, no qual os métodos Linq não são lançados aleatoriamente devido a um aspecto computacional que não é realmente uma violação das regras normais, como se você solicitasse -1 itens por vez.
ErikE
2

Eu posso ver argumentos para ambos os casos de uso - uma exceção é ótima se o código downstream espera conjuntos que contêm dados. Por outro lado, simplesmente um conjunto vazio é ótimo se isso for esperado.

Eu acho que depende das expectativas do chamador se este for um erro ou um resultado aceitável - então eu transferiria a opção para o chamador. Talvez introduza uma opção?

.CombinationsOf(4, CombinationsGenerationMode.Distinct, Options.AllowEmptySets)

Christian Sauer
fonte
10
Enquanto chego aonde você está indo com isso, tento evitar métodos com muitas opções passadas que modificam seu comportamento. Ainda não estou 100% feliz com o enum de modo que já existe; Realmente não gosto da ideia de um parâmetro de opção que altera o comportamento de exceção de um método. Eu sinto que se um método precisa lançar uma exceção, ele precisa lançar; se você deseja ocultar essa exceção, é sua decisão chamar código, e não algo que você possa fazer com que meu código faça a opção de entrada correta.
Anaximander
Se o chamador espera que um conjunto não esteja vazio, cabe ao chamador lidar com um conjunto vazio ou lançar uma exceção. No que diz respeito ao receptor, um conjunto vazio é uma resposta perfeita. E você pode ter mais de um chamador, com opiniões diferentes sobre se conjuntos vazios são bons ou não.
precisa saber é o seguinte
2

Existem duas abordagens para decidir se não há resposta óbvia:

  • Escreva o código assumindo primeiro uma opção e depois a outra. Considere qual deles funcionaria melhor na prática.

  • Adicione um parâmetro booleano "strict" para indicar se você deseja que os parâmetros sejam estritamente verificados ou não. Por exemplo, o Java's SimpleDateFormattem um setLenientmétodo para tentar analisar entradas que não correspondem totalmente ao formato. Claro, você teria que decidir qual é o padrão.

JollyJoker
fonte
2

Com base em sua própria análise, retornar o conjunto vazio parece claramente correto - você até o identificou como algo que alguns usuários podem realmente querer e não caiu na armadilha de proibir algum uso, porque você não pode imaginar os usuários que desejam usá-lo dessa maneira.

Se você realmente acha que alguns usuários podem querer forçar retornos não vazios, ofereça a eles uma maneira de solicitar esse comportamento, em vez de forçá-lo a todos. Por exemplo, você pode:

  • Torne-a uma opção de configuração em qualquer objeto que esteja executando a ação para o usuário.
  • Torne um sinalizador que o usuário pode opcionalmente passar para a função.
  • Forneça um AssertNonemptycheque que eles possam colocar em suas correntes.
  • Faça duas funções, uma que afirme não vazia e outra que não.

fonte
este não parece oferecer nada substancial sobre os pontos feitos e explicado na prévia de 12 respostas
mosquito
1

Realmente depende do que seus usuários esperam obter. Para um exemplo (um pouco não relacionado), se o seu código executar uma divisão, você poderá lançar uma exceção ou retornar Infou NaNquando dividir por zero. Nem está certo ou errado, no entanto:

  • se você retornar Infem uma biblioteca Python, as pessoas o atacarão por ocultar erros
  • se você gerar um erro em uma biblioteca do Matlab, as pessoas o atacarão por não processar dados com valores ausentes

No seu caso, eu escolheria a solução que será menos surpreendente para os usuários finais. Como você está desenvolvendo uma biblioteca que lida com conjuntos, um conjunto vazio parece algo com o qual os usuários esperam lidar, portanto, retornar parece uma coisa sensata a ser feita. Mas eu posso estar enganado: você tem uma compreensão muito melhor do contexto do que qualquer outra pessoa aqui. Portanto, se você espera que seus usuários confiem no conjunto sempre não estando vazio, deve lançar uma exceção imediatamente.

As soluções que permitem ao usuário escolher (como adicionar um parâmetro "estrito") não são definitivas, pois substituem a pergunta original por uma nova equivalente: "Qual valor strictdeve ser o padrão?"

Dmitry Grigoryev
fonte
2
Pensei em usar o argumento NaN na minha resposta e compartilho sua opinião. Não podemos contar mais sem conhecer o domínio do OP
GameDeveloper 3/16/16
0

É concepção comum (em matemática) que, quando você seleciona elementos sobre um conjunto, não encontra nenhum elemento e, portanto, obtém um conjunto vazio . É claro que você deve ser consistente com a matemática se seguir este caminho:

Regras comuns definidas:

  • Set.Foreach (predicado); // sempre retorna true para conjuntos vazios
  • Set.Exists (predicado); // sempre retorna false para conjuntos vazios

Sua pergunta é muito sutil:

  • Pode ser que a entrada da sua função tenha que respeitar um contrato : nesse caso, qualquer entrada inválida deve gerar uma exceção, é isso, a função não está funcionando sob parâmetros regulares.

  • Pode ser que a entrada da sua função tenha que se comportar exatamente como um conjunto e, portanto, deve poder retornar um conjunto vazio.

Agora, se eu estivesse em você, seguiria o caminho "Set", mas com um grande "MAS".

Suponha que você tenha uma coleção que "por hipotese" deve ter apenas estudantes do sexo feminino:

class FemaleClass{

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

Agora sua coleção não é mais um "conjunto puro", porque você tem um contrato e, portanto, deve fazer cumprir seu contrato com uma exceção.

Quando você usa suas funções "set" de maneira pura, não deve lançar exceções no caso de set vazio, mas se você tiver coleções que não são mais "sets puros", deverá lançar exceções onde for apropriado .

Você deve sempre fazer o que parecer mais natural e consistente: para mim, um conjunto deve aderir às regras, enquanto coisas que não são conjuntos devem ter suas regras adequadamente pensadas.

No seu caso, parece uma boa ideia:

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;
}

Mas não é realmente uma boa ideia, temos agora um estado inválido que pode ocorrer em tempo de execução em momentos aleatórios, no entanto, isso está implícito no problema e, portanto, não podemos removê-lo, com certeza podemos mover a exceção dentro de algum método utilitário (é exatamente o mesmo código, movido em lugares diferentes), mas isso está errado e basicamente a melhor coisa que você pode fazer é seguir as regras estabelecidas regularmente .

De fato, adicionar uma nova complexidade apenas para mostrar que você pode escrever métodos de consultas linq não parece valer o seu problema . Tenho certeza de que, se o OP puder nos dizer mais sobre seu domínio, provavelmente poderemos encontrar o local onde a exceção é realmente necessária (se é possível que o problema não exija nenhuma exceção).

Desenvolvedor de jogos
fonte
O problema é que você está usando objetos matematicamente definidos para resolver um problema, mas o problema em si pode não estar exigindo um objeto matematicamente definido como resposta (na verdade, retorne aqui uma lista). Tudo depende do nível mais alto do problema, independentemente de como você o resolva, isso é chamado de encapsulamento / ocultação de informações.
GameDeveloper
O motivo pelo qual seu "SomeMethod" não deve mais se comportar como um conjunto é que você está solicitando uma informação "fora dos Conjuntos", veja especificamente a parte comentada "&& someInputSet.NotEmpty ()"
GameDeveloper
1
NÃO! Você não deve lançar uma exceção em sua classe feminina. Você deve usar o padrão Builder, que reforça que você não pode nem obter uma instância da instância, a FemaleClassmenos que ela tenha apenas fêmeas (não é publicamente publicável, apenas a classe Builder pode distribuir uma instância) e possivelmente possui pelo menos uma fêmea . Então, seu código em todo o lugar que leva FemaleClasscomo argumento não precisa lidar com exceções porque o objeto está no estado errado.
ErikE
Você está assumindo que a classe é tão simples que pode impor suas pré-condições simplesmente pelo construtor, na realidade. Você argumento é falho. Se você proíbe de não ter mais mulheres, então, ou você não pode ter um método para remover alunos da turma ou seu método deve lançar uma exceção quando restar 1 aluno. Você acabou de mudar meu problema para outro lugar. O ponto é que sempre haverá algum estado de pré-condição / função que não pode ser mantido "por design" e que o usuário irá quebrar: é por isso que existem exceções! Isso é uma coisa bastante teórica, espero que o voto negativo não seja seu.
GameDeveloper 3/16/16
O voto negativo não é meu, mas se fosse, ficaria orgulhoso disso. Voto com cuidado, mas com convicção. Se a regra da sua classe é que DEVE ter um item e todos os itens devem atender a uma condição específica, é uma má idéia permitir que a classe esteja em um estado inconsistente. Mover o problema para outro lugar é exatamente a minha intenção! Eu quero o problema a ocorrer em tempo de construção, não a tempo de uso. O objetivo de usar uma classe Builder em vez de um lançamento de construtor é que você pode construir um estado muito complexo em várias partes e partes através de inconsistências.
ErikE