Eu tenho um método em que toda a lógica é realizada dentro de um loop foreach que itera sobre o parâmetro do método:
public IEnumerable<TransformedNode> TransformNodes(IEnumerable<Node> nodes)
{
foreach(var node in nodes)
{
// yadda yadda yadda
yield return transformedNode;
}
}
Nesse caso, o envio de uma coleção vazia resulta em uma coleção vazia, mas estou me perguntando se isso é imprudente.
Minha lógica aqui é que, se alguém está chamando esse método, eles pretendem passar dados e só passariam uma coleção vazia para o meu método em circunstâncias errôneas.
Devo capturar esse comportamento e lançar uma exceção para ele, ou é uma prática recomendada retornar a coleção vazia?
c#
exceptions
collections
parameters
Nick Udell
fonte
fonte
null
mas não se estiver vazia.Respostas:
Os métodos utilitários não devem ser lançados em coleções vazias. Seus clientes da API o odiariam por isso.
Uma coleção pode estar vazia; uma "coleção que não deve ser vazia" é conceitualmente uma coisa muito mais difícil de se trabalhar.
Transformar uma coleção vazia tem um resultado óbvio: a coleção vazia. (Você pode até economizar algum lixo retornando o próprio parâmetro.)
Existem muitas circunstâncias em que um módulo mantém listas de itens que podem ou não estar preenchidos com algo. Ter que verificar o vazio antes de cada chamada
transform
é irritante e tem o potencial de transformar um algoritmo simples e elegante em uma bagunça feia.Os métodos de utilidade devem sempre se esforçar para serem liberais em suas entradas e conservadores em suas saídas.
Por todas essas razões, pelo amor de Deus, lide com a coleção vazia corretamente. Nada é mais irritante do que um módulo auxiliar que pensa que sabe o que você quer melhor do que você.
fonte
null
- se na coleção vazia. Funções com limitações implícitas são uma dor.Populate1(input); output1 = TransformNodes(input); Populate2(input); output2 = TransformNodes(input);
se Populate1 deixasse a coleção vazia e você a retornasse para a primeira chamada TransformNodes, output1 e input seriam a mesma coleção, e quando Populaten2 fosse chamado, se colocasse nós na coleção, você terminaria com sua segunda conjunto de entrada na saída2.Vejo duas perguntas importantes que determinam a resposta para isso:
1. Retorno significativo
Essencialmente, se você puder retornar algo significativo, não lance uma exceção. Deixe o chamador lidar com o resultado. Então, se sua função ...
Em geral, meu viés de FP me diz "retornar algo significativo" e nulo pode ter um significado válido nesse caso.
2. Estilo
Seu código geral (ou o código do projeto ou da equipe) favorece um estilo funcional? Se não, serão esperadas e tratadas exceções. Se sim, considere retornar um tipo de opção . Com um tipo de opção, você retorna uma resposta significativa ou Nada / Nada . No meu terceiro exemplo acima, Nothing seria uma boa resposta no estilo FP. O fato de a função retornar um tipo de opção sinalizar claramente para o chamador do que uma resposta significativa pode não ser possível e que o chamador deve estar preparado para lidar com isso. Eu sinto que isso dá ao chamador mais opções (se você perdoar o trocadilho).
F # é onde todas as frias .Net crianças fazem este tipo de coisa, mas C # faz apoiar este modelo.
tl; dr
Mantenha exceções para erros inesperados no seu próprio caminho de código, não totalmente previsível (e legal) de entrada de outra pessoa.
fonte
Como sempre, depende.
Será que importa que a coleção está vazia?
A maioria dos códigos de manipulação de coleções provavelmente diria "não"; uma coleção pode ter qualquer número de itens, incluindo zero.
Agora, se você tem algum tipo de coleção em que é "inválido" não ter itens, é um novo requisito e você precisa decidir o que fazer.
Peça emprestada alguma lógica de teste do mundo do banco de dados: teste para zero itens, um e dois itens. Isso atende aos casos mais importantes (eliminando quaisquer condições de junção internas ou cartesianas mal formadas).
fonte
java.util.Collection
mas umacom.foo.util.NonEmptyCollection
classe personalizada , que possa preservar consistentemente essa invariância e impedir que você entre em um estado inválido para começar.Por uma questão de bom design, aceite o máximo possível de variações em suas entradas. Exceções só devem ser lançadas quando (entrada inaceitável é apresentada OU erros inesperados ocorrem durante o processamento) E o programa não pode continuar de maneira previsível como resultado.
Nesse caso, deve-se esperar que uma coleção vazia seja apresentada e seu código precise manipulá-la (o que já faz). Seria uma violação de tudo o que é bom se o seu código lançar uma exceção aqui. Isso seria semelhante à multiplicação de 0 por 0 em matemática. É redundante, mas absolutamente necessário para funcionar da maneira que funciona.
Agora, vamos ao argumento de coleção nula. Nesse caso, uma coleção nula é um erro de programação: o programador esqueceu de atribuir uma variável. Este é um caso em que uma exceção pode ser lançada, porque você não pode processá-la significativamente em uma saída, e tentar fazer isso introduziria um comportamento inesperado. Isso seria semelhante a dividir por zero em matemática - é completamente sem sentido.
fonte
A solução certa é muito mais difícil de ver quando você está apenas olhando para sua função isoladamente. Considere sua função como parte de um problema maior . Uma solução possível para esse exemplo é semelhante a esta (em Scala):
Primeiro, você divide a string por não dígitos, filtra as strings vazias, converte as strings em números inteiros e depois filtra para manter apenas os números de quatro dígitos. Sua função pode estar
map (_.toInt)
no pipeline.Esse código é bastante simples, porque cada estágio do pipeline manipula apenas uma sequência vazia ou uma coleção vazia. Se você inserir uma string vazia no início, obterá uma lista vazia no final. Você não precisa parar e procurar
null
uma exceção após cada chamada.Obviamente, isso pressupõe que uma lista de saída vazia não tenha mais de um significado. Se você precisar diferenciar entre uma saída vazia causada pela entrada vazia e uma causada pela própria transformação, isso muda completamente as coisas.
fonte
Esta questão é realmente sobre exceções. Se você olhar dessa maneira e ignorar a coleção vazia como um detalhe de implementação, a resposta é direta:
1) Um método deve lançar uma exceção quando não puder prosseguir: incapaz de executar a tarefa designada ou retornar o valor apropriado.
2) Um método deve capturar uma exceção quando é capaz de prosseguir, apesar da falha.
Portanto, seu método auxiliar não deve ser "útil" e gerar uma exceção, a menos que seja incapaz de fazer seu trabalho com uma coleção vazia. Deixe o chamador determinar se os resultados podem ser tratados.
Se ele retorna uma coleção vazia ou nula, é um pouco mais difícil, mas não muito: coleções anuláveis devem ser evitadas, se possível. O objetivo de uma coleção anulável seria indicar (como no SQL) que você não tem as informações - uma coleção de filhos, por exemplo, pode ser nula se você não souber se alguém tem alguma, mas você não sabe que eles não. Mas se isso é importante por algum motivo, provavelmente vale uma variável extra para acompanhá-lo.
fonte
O método é nomeado
TransformNodes
. No caso de uma coleção vazia como entrada, recuperar uma coleção vazia é natural e intuitiva e faz sentido matemático.Se o método foi nomeado
Max
e projetado para retornar o elemento máximo, seria natural lançarNoSuchElementException
uma coleção vazia, pois o máximo de nada não faz sentido matemático.Se o método foi nomeado
JoinSqlColumnNames
e projetado para retornar uma sequência em que os elementos são unidos por uma vírgula para uso em consultas SQL, faria sentido lançarIllegalArgumentException
uma coleção vazia, pois o chamador acabaria recebendo um erro SQL de qualquer maneira, se usasse a string em uma consulta SQL diretamente, sem mais verificações e, em vez de procurar uma string vazia retornada, ele realmente deveria ter verificado a coleção vazia.fonte
Max
do nada é frequentemente infinito negativo.Vamos voltar e usar um exemplo diferente que calcula a média aritmética de uma matriz de valores.
Se a matriz de entrada estiver vazia (ou nula), você pode atender razoavelmente à solicitação do chamador? Não. Quais são suas opções? Bem, você poderia:
Eu digo que dê a eles o erro se eles fornecerem uma entrada inválida e a solicitação não puder ser concluída. Quero dizer, um erro grave desde o primeiro dia, para que eles entendam os requisitos do seu programa. Afinal, sua função não está em posição de responder. Se a operação pode falhar (por exemplo, copiar um arquivo), então a sua API deve dar-lhes um erro eles podem lidar.
Portanto, isso pode definir como sua biblioteca lida com solicitações malformadas e solicitações que podem falhar.
É muito importante que o seu código seja consistente na maneira como lida com essas classes de erros.
A próxima categoria é decidir como sua biblioteca lida com solicitações sem sentido. Voltando a um exemplo semelhante à sua - vamos usar uma função que determina se um arquivo existe em um caminho:
bool FileExistsAtPath(String)
. Se o cliente passa uma cadeia vazia, como você lida com esse cenário? Que tal uma matriz vazia ou nula passada paravoid SaveDocuments(Array<Document>)
? Decida sua biblioteca / base de código e seja consistente. Por acaso, considero esses erros de casos e proíbo os clientes de fazer solicitações sem sentido, sinalizando-os como erros (por meio de uma asserção). Algumas pessoas resistem fortemente a essa idéia / ação. Acho essa detecção de erro muito útil. É muito bom para localizar problemas em programas - com boa localidade para o programa ofensivo. Os programas são muito mais claros e corretos (considere a evolução da sua base de código) e não queimam ciclos em funções que não fazem nada. O código é menor / mais limpo dessa maneira, e as verificações geralmente são enviadas para os locais onde o problema pode ser introduzido.fonte
if (!FileExists(path)) { ...create it!!!... }
bugs - muitos dos quais seriam capturados antes do commit, se as linhas de correção não estivessem borradas.Como regra geral, uma função padrão deve ser capaz de aceitar a lista mais ampla de entradas e fornecer feedback sobre ela; existem muitos exemplos em que os programadores usam funções de maneiras que o designer não havia planejado, com isso em mente, acredito que a função deve ser capaz de aceitar não apenas coleções vazias, mas uma ampla variedade de tipos de entrada e retornar um retorno gracioso, mesmo que seja um objeto de erro de qualquer operação realizada na entrada ...
fonte
writePaycheckToEmployee
não deve aceitar um número negativo como entrada ... mas se "feedback" significa "faz qualquer coisa que possa acontecer a seguir", sim, toda função fará algo que fará a seguir.