O serviço deve lançar exceção ou retornar quando nenhum item especificado para exclusão

12

Eu tenho um pedaço de código que pode ser representado como:

public class ItemService {

    public void DeleteItems(IEnumerable<Item> items)
    {
        // Save us from possible NullReferenceException below.
        if(items == null)
            return;

        foreach(var item in items)
        {
            // For the purpose of this example, lets say I have to iterate over them.
            // Go to database and delete them.
        }
    }
}

Agora, estou me perguntando se essa é a abordagem correta ou devo lançar uma exceção. Posso evitar a exceção, porque retornar seria o mesmo que iterar sobre uma coleção vazia, o que significa que nenhum código importante é executado de qualquer maneira, mas, por outro lado, possivelmente estou ocultando problemas em algum lugar do código, porque por que alguém iria querer chamar DeleteItemscom nullparâmetro? Isso pode indicar que há um problema em outro lugar no código.

Esse é um problema que geralmente tenho com métodos em serviços, porque a maioria deles faz alguma coisa e não retorna um resultado; portanto, se alguém passa informações inválidas, não há nada para o serviço fazer, então ele retorna.

FCin
fonte
2
Esta é apenas a minha opinião pessoal, mas sempre achei que fazia mais sentido que um método deveria lançar uma exceção quando faz algo não intencional (excepcional). No seu caso, eu lançaria uma InvalidOperationException se alguém tentasse excluir itens nulos / 0.
Falgantil
1
@gnat Minha pergunta é especificamente sobre serviços, por causa de como eles são usados ​​e sua finalidade. Esse "duplicado" fala apenas dos casos gerais e a resposta principal até se contradiz no contexto do meu método exato.
FCin
2
É possível que o enumerável esteja vazio em vez de nulo? Você lidaria com isso de maneira diferente?
JAD
13
@Falgantil Posso ver nulo sendo digno de uma exceção, mas acho absurdo lançar uma exceção em uma lista vazia.
24418 Kevin

Respostas:

58

Essas são duas perguntas diferentes.

Você deveria aceitar null? Isso depende da sua política geral sobre nulla base de código. Na minha opinião, banir nulltodos os lugares, exceto onde explicitamente documentado, é uma prática muito boa, mas é uma prática ainda melhor seguir a convenção que sua base de código já possui.

Você deve aceitar a coleção vazia? Na minha opinião: SIM, absolutamente. É muito mais esforço restringir todos os chamadores a coleções não vazias do que fazer a coisa matematicamente correta - mesmo que surpreenda alguns desenvolvedores que são duvidosos com o conceito de zero.

Kilian Foth
fonte
9
De qualquer forma, se você vai jogar, não há tanta utilidade em jogar AhaINoticedYouPassingInNullExceptionquando o tempo de execução já oferece a facilidade de jogar NullReferenceExceptionpara você, sem a necessidade de escrever nenhum código. Existe algum utilitário (por exemplo, sua ferramenta de cobertura de código informa se você tem ou não um teste de unidade para passar nulo no primeiro caso, mas não no último), mas nenhuma exceção deve ser tentada / capturada em todas as chamadas para o serviço, porque geralmente é um erro do programador.
Steve Jessop
2
... você só deve capturar imediatamente exceções geradas devido a parâmetros incompletos, se souber que o que está passando é nulo em alguns casos esperados e desejar ativamente a função que está chamando para validá-la. Como Kilian diz na resposta, sua política geral pode ser proibir nulos em todos os lugares em que não for explicitamente permitido. Então você não pegaria NullReferenceExceptionou em AhaINoticedYouPassingInNullExceptionqualquer outro lugar, exceto no código de recuperação de desastres de alto nível. E que manipulador de exceção provavelmente deve criar um relatório de bug :-)
Steve Jessop
2
@FCin: seu design de interface deve corresponder ao que os usuários realmente precisam fora do serviço. Portanto, se todos os chamadores tentarem resolver isso, então esse método ou algum método de conveniência deve verificar e ignorar nulo, porque é isso que o público exige. No entanto, como diz Kilian, geralmente funciona melhor tratar nulos de propagação como um erro de programação e não tentar continuar em todos os sites de chamada. Nesse caso, e se o serviço para o qual esse método é chamado for null- os controladores contêm clichê para capturar e continuar nesse caso também?
Steve Jessop
2
... se sim, parece que a base de código está tratando consistentemente os componentes ausentes como uma condição esperada que você deve manipular em nível baixo e continuar. Você pode razoavelmente perguntar: "de quem é a responsabilidade de fornecer um serviço e um número incontável para que essa operação possa prosseguir?". Se a resposta for "alguém", sua função está verificando as condições prévias , e o resultado de uma falha na pré-condição normalmente seria uma exceção capturada em alto nível, não em todas as chamadas. Se o material necessário para a operação for realmente opcional, sua função estará lidando com um caso de uso esperado .
Steve Jessop
2
Lançar explicitamente um ArgumentNullExceptioné superior a permitir que o código interno gere uma NullReferenceException- puramente olhando para a mensagem de exceção, é óbvio que se trata de um erro de entrada e não de um erro lógico no corpo do método no local do lance, e garantir a saída antecipada significa que é mais provável que você tenha deixado outros dados em um estado válido, em vez de ter sofrido uma mutação parcial de um nulo inesperado posteriormente.
Miral
9

Valor nulo

Como o @KilianFoth já disse, atenha-se à sua política geral. Se isso for tratado nullcomo um "atalho" para uma lista vazia, faça-o dessa maneira.

Se você não tem uma política consistente de nullvalores, recomendo a seguinte:

nulldeve ser reservado para representar situações que não podem ser expressas pelo tipo "normal", por exemplo, usar nullpara representar "não sei". E essa é uma boa escolha, pois todos que tentarem usar esse valor descuidadamente receberão uma exceção, que é a coisa certa.

Usar nullcomo atalho para uma lista vazia não se qualifica dessa maneira, pois já existe uma representação perfeita, sendo uma lista com zero elementos. E é uma escolha tecnicamente ruim, pois força todas as partes do seu código que lidam com listas a procurar a abreviação válida null.

Lista vazia

Para um DeleteItems()método, passar uma lista vazia efetivamente significa não fazer nada. Eu permitiria isso como argumento, não lançando uma exceção, apenas retornando rapidamente.

Obviamente, o chamador pode verificar se há zero elementos primeiro e pular a DeleteItems()chamada nesse caso. Se estamos falando de uma API da Web, por motivos de eficiência, o chamador deve realmente fazer isso para evitar tráfego desnecessário e latências de ida e volta. Mas não acho que sua API imponha isso.

Ralf Kleberhoff
fonte
1

Lance uma exceção e lide com nulos no código de chamada.

Como regra de design, tente evitar nulo como valores de parâmetro. Isso reduzirá NullPointerExceptions em geral, pois os nulos serão realmente uma exceção.

Além disso, observe o restante do seu código. Se esse é um padrão comum em seu projeto, mantenha-se consistente.

serprime
fonte
Estou no meio de "moldar" a forma como meus serviços são estruturados; portanto, algumas refatorações podem ser necessárias para gerar entradas inválidas, mas não muito. Mas concordo com o seu argumento de que os nulos se tornarão uma exceção quando eu começar a jogar, em vez de escondê-los.
FCin
0

De um modo geral, o lançamento de exceções deve ser reservado para situações excepcionais, ou seja, se o código não tiver um curso de ação razoável a ser adotado no contexto atual.

Você pode aplicar esse processo de pensamento a essa situação. Dado que esta é uma classe pública com um método público, você tem uma API pública e, em teoria, não tem controle sobre o que é passado para ela.

Seu código não tem contexto sobre o que está chamando (como não deveria) e não há nada que você possa razoavelmente fazer com um valor nulo. Isso certamente seria um candidato a um ArgumentNullException.

richzilla
fonte
"se o código não tiver um curso de ação razoável a ser adotado no contexto atual". Mas meu pensamento é que ele tem um curso de ação razoável, que é retornar vazio. Ele faz exatamente a mesma coisa que ao passar a coleção vazia, por isso, se eu permitir a coleção vazia, por que não permitiria null?
FCin
1
@FCin Devido a uma coleção vazia, você sabe que está vazia. Com nullvocê não tem coleção. Se o código de chamada deve / deve fornecer uma coleção para o método, há algo errado, então você deve lançar. O único motivo para tratar um nulo da mesma forma que uma coleção vazia é se você razoavelmente esperar que o código de chamada produza coleções nulas. Como outras respostas observadas, na maioria das vezes, algo nullé uma anomalia. Se você as tratar da mesma forma que coleções vazias, você estará potencialmente ignorando um problema em outra parte do código.
JAD
@FCin: também, se você nullentender que está vazio, isso é específico do contexto para esse método. Em algum outro lugar na mesma base de código, você pode ter um parâmetro IEnumerableou IContainerque faz algum tipo de filtragem, nullpode significar "sem filtro" (permitir tudo), enquanto um filtro vazio significa não permitir nada. E em outro lugar, nullpode significar explicitamente "eu não sei". Portanto, dado que nullnem sempre significa a mesma coisa em todos os lugares, é uma boa idéia não inventar nenhum significado para isso, a menos que esse significado seja necessário ou pelo menos útil para alguém.
Steve Jessop
0

Esta questão parece não ser sobre exceções, mas sobre nullser um argumento válido.

Portanto, primeiro e acima de tudo, você deve decidir se nullé um valor permitido para o argumento desse método. Se for, você não precisa de uma exceção. Caso contrário, você precisará de uma exceção.

Se você deseja permitir nullou não, é discutível, como mostra muitos e muitos hits do Google sobre isso. Ou seja, você não receberá uma resposta clara, e isso depende de opinião e tradição no local em que você está trabalhando.

Outra área de disputa é se uma função de biblioteca deve ser realmente rigorosa sobre tais coisas ou o mais branda possível, desde que não esteja tentando "corrigir" parâmetros errados. Compare isso com o mundo dos protocolos de rede, transferência de e-mail etc. (que são contratos de interface, assim como métodos de programação). Geralmente, a política é que o remetente esteja em conformidade o mais estritamente possível com o protocolo, enquanto o destinatário deve fazer o possível para poder trabalhar com o que estiver por vir.

Então você tem que decidir: é realmente o trabalho de um método de biblioteca impor políticas bastante grandes, como nullmanipulação? Especialmente se sua biblioteca também for usada por outras pessoas (que podem ter políticas diferentes).

Eu provavelmente nullerraria ao permitir valores, definindo e documentando suas semânticas (ou seja, null = empty arrayneste caso), e não lançaria uma exceção, a menos que 99% de seu outro código semelhante (código de estilo "biblioteca") faça isso de outra maneira 'volta.

AnoE
fonte
1
"É realmente o trabalho de um método de biblioteca impor políticas bastante grandes, como manipulação nula?" - nessa linha de pensamento, estão os modos de declaração e depuração, que permitem ajudar na imposição de uma política, em vez de realmente aplicá-la, se é isso que você deseja fazer. Portanto, se você vai nullaceitar o princípio de ser liberal no que aceita, fazer logon ou até jogar seria útil para pessoas cuja intenção é ser rigorosa no que elas passam, mas que confundiram tanto o código quanto seus testes de unidade na medida em que eles passam de nullqualquer maneira.
Steve Jessop
@SteveJessop, eu realmente não sei se você está argumentando contra o espírito da minha resposta, ou se está continuando na linha. Dito isso, não estou sugerindo simplesmente engolir null, mas declarar abertamente no contrato do método que é aceitável (ou não, dependendo de qualquer solução que o OP surja) e, então, a questão de lançar uma exceção (ou não) se resolve.
AnoE 24/05