Como o lançamento de um ArgumentNullException ajuda?


27

Digamos que eu tenho um método:

public void DoSomething(ISomeInterface someObject)
{
    if(someObject == null) throw new ArgumentNullException("someObject");
    someObject.DoThisOrThat();
}

Fui treinado para acreditar que jogar o item ArgumentNullExceptioné "correto", mas um erro "Referência de objeto não definida para uma instância de um objeto" significa que tenho um erro.

Por quê?

Sei que, se eu estava armazenando em cache a referência someObjecte usando-a posteriormente, é melhor verificar a nulidade quando transmitida e falhar mais cedo. No entanto, se estou desreferenciando na próxima linha, por que devemos fazer a verificação? Vai lançar uma exceção de uma maneira ou de outra.

Editar :

Apenas me ocorreu ... o medo do nulo não referenciado vem de uma linguagem como C ++ que não verifica você (isto é, apenas tenta executar algum método no local de memória zero + deslocamento do método)?


Tornar explícita a exceção ajuda a afirmar que há uma possibilidade de erro, que pode ser observada diretamente na documentação.
zzzzBov

Respostas:


34

Suponho que se você estiver desreferenciando imediatamente a variável, poderá debater de qualquer maneira, mas eu ainda preferiria a ArgumentNullException.
É muito mais explícito sobre o que está acontecendo. A exceção contém o nome da variável que era nula, enquanto uma NullReferenceException não. Particularmente no nível da API, um ArgumentNullException deixa claro que alguns chamadores não honraram o contrato de um método, e a falha não foi necessariamente um erro aleatório ou um problema mais profundo (embora esse ainda possa ser o caso). Você deve falhar cedo.

Além disso, o que os mantenedores posteriores têm mais probabilidade de fazer? Adicione ArgumentNullException se eles adicionarem código antes da primeira desreferência, ou simplesmente adicione o código e, posteriormente, permita que uma NullReferenceException seja lançada?


Por extensão, se esse fosse um método não público ou público em uma classe não pública, você não acha que há uma boa razão para a verificação nula?
Scott Whitlock

Normalmente, não adiciono essas verificações a métodos particulares, mas ainda posso adicioná-las a métodos públicos de classes privadas para serem consistentes. Para métodos privados, costumo assumir que os argumentos são validados anteriormente em algum nível de chamada pública.
Matt H

54

Fui treinado para acreditar que jogar ArgumentNullException está "correto", mas um erro "Referência de objeto não definida para uma instância de um objeto" significa que tenho um erro. Por quê?

Suponha que eu chame o método M(x)que você escreveu. Eu passo nulo. Eu recebo um ArgumentNullException com o nome definido como "x". Essa exceção significa sem ambiguidade que eu tenho um bug; Eu não deveria ter passado nulo para x.

Suponha que eu chame um método M que você escreveu. Eu passo nulo. Eu recebo uma exceção deref nula. Como o diabo é que eu vou saber se eu tenho um bug ou você tem um bug ou alguma biblioteca de terceiros que você ligou em meu nome tem um bug? Não sei se preciso corrigir meu código ou ligar para você para solicitar o seu.

Ambas as exceções são indicativas de erros; nenhum deles deve nunca ser lançado no código de produção. A questão é qual exceção comunica quem tem o bug melhor para a pessoa que está fazendo a depuração? A exceção nula de deref não diz quase nada; A exceção null do argumento diz muito. Seja gentil com seus usuários; lançar exceções que lhes permitam aprender com seus erros.


Não sei se entendi "neither should ever be thrown in production code"Que outros tipos de código / situações seriam usados? Eles devem ser compilados como Debug.Assert? Ou você quer dizer não jogá-los fora de uma API?
StuperUser

6
@ SuperUser: Eu acho que você não entendeu o que eu quis dizer, porque eu escrevi de uma maneira confusa. Você deve ter throw new ArgumentNullExceptionno seu código de produção e ele nunca deve ser executado fora de um caso de teste . A exceção nunca deve ser lançada, porque o chamador nunca deve ter esse bug.
precisa

1
Ele não apenas comunica quem tem o erro, mas também onde está o erro. Mesmo que as duas áreas sejam minhas, nem sempre é fácil entender exatamente qual variável era nula.
Ohad Schneider

5

O ponto é que seu código deve fazer algo significativo.

A NullReferenceExceptionnão é particularmente significativo, porque pode significar tudo. Sem olhar para onde a exceção foi lançada (e o código pode não estar disponível para mim), não consigo descobrir o que deu errado.

An ArgumentNullExceptioné significativo, porque me diz: a operação falhou porque o argumento someObjectera null. Não preciso olhar para o seu código para descobrir isso.

Ao criar essa exceção, você manipula explicitamente null, mesmo que sua única maneira de fazê-lo seja dizer que não pode manipulá-lo, enquanto deixar o código travar pode significar isso; você pode realmente manipular nulos. , mas esqueceu de implementar o caso.

O ponto das exceções é comunicar informações em caso de falha, que podem ser tratadas no tempo de execução para se recuperar da falha ou no momento da depuração para entender melhor o que deu errado.


2

Acredito que a única vantagem é que você pode fornecer melhores diagnósticos na exceção. Ou seja, você sabe que o chamador da função está passando nulo, enquanto se você deixar a desreferência jogá-lo, não terá certeza se o chamador passa dados incorretos ou se há um erro na própria função.

Eu faria isso se alguém chamar a função para facilitar o uso (depurar se algo der errado) e não fazê-lo no meu código interno, porque o tempo de execução a verificará de qualquer maneira.


1

Fui treinado para acreditar que jogar ArgumentNullException está "correto", mas um erro "Referência de objeto não definida para uma instância de um objeto" significa que tenho um erro.

Você tem um erro se o código não funcionar como projetado. Um NullReferenceExceptioné lançado pelo sistema e esse fato realmente torna mais um bug do que um recurso. Não apenas porque você não definiu a exceção específica a ser tratada. Também porque um desenvolvedor que lê seu código pode assumir que você não considerou a situação.

Por motivos semelhantes, o que ApplicationExceptionpertence ao seu aplicativo e um general Exceptionque pertence ao sistema operacional. O tipo de exceção que está sendo lançada indica onde a exceção se origina.

Se você considerou nulo, é melhor tratá-lo normalmente, lançando uma exceção específica ou se for uma entrada aceitável; então, não lance uma exceção porque são caras. Manipule-o executando operações com bons padrões ou nenhuma operação (por exemplo, nenhuma atualização necessária).

Por fim, não negligencie a capacidade do chamador de lidar com a situação. Ao fornecer a eles uma exceção mais específica, ArgumentExceptioneles podem construir sua tentativa / captura de maneira a lidar com esse erro antes do mais geral, NullReferenceExceptionque pode ser causado por vários outros motivos que ocorrem em outros lugares, como uma falha na inicialização de uma variável do construtor.


1

A verificação torna mais explícito o que está acontecendo, mas o problema real vem com código como este (artificial):

public void DoSomething(Foo someOtherObject, ISomeInterface someObject)
{
    someOtherObject.DoThisOrThat(this, someObject);
}

Aqui há uma cadeia de chamadas envolvida e, sem verificação nula, você vê apenas o erro em someOtherObject e não onde o problema se revelou pela primeira vez - o que significa instantaneamente a perda de tempo na depuração ou interpretação das pilhas de chamadas.

Em geral, é melhor ser consistente com esse tipo de coisa e sempre adicionar a verificação nula - o que torna mais fácil identificar se está faltando um do que escolher e escolher caso a caso (supondo que você saiba que nulo é sempre inválido).


1

Outro motivo a considerar é se algum processamento ocorrer antes que o objeto nulo seja usado?

Digamos que você tenha um arquivo de dados de IDs de empresas associadas (Cid) e de pessoas (Pid). Ele é salvo como um fluxo de pares de números:

Cid Pid Cid Pid Cid Pid Cid Pid Cid Pid

E essa função VB grava os registros no arquivo:

Sub WriteRecord(Company As CompanyInfo, Person As PersonInfo)
    Using File As New StringWriter(DataFileName)
        File.Write(Company.ID)
        File.Write(Person.ID)
    End Using
End Sub

Se Personfor uma referência nula, a linha File.Write(Person.ID)lançará uma exceção. O software pode capturar a exceção e se recuperar, mas isso deixa um problema. Um ID da empresa foi gravado sem um ID da pessoa associada. De fato, quando você chamar esta função pela próxima vez, você colocará um ID da empresa onde o ID da pessoa anterior era esperado. Além de o registro estar incorreto, todos os registros subsequentes terão um ID de pessoa seguido por um ID da empresa, que é o contrário.

Cid Pid Cid Pid Cid Cid Pid Cid Pid Cid

Ao validar os parâmetros no início da função, você evita que qualquer processamento ocorra quando o método é garantido com falha.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.