Portanto, não sei se esse é um design de código bom ou ruim, por isso acho melhor perguntar.
Costumo criar métodos que processam dados envolvendo classes e, muitas vezes, faço muitas verificações nos métodos para garantir que não receba referências nulas ou outros erros antes.
Para um exemplo muito básico:
// fields and properties
private Entity _someEntity;
public Entity SomeEntity => _someEntity;
public void AssignEntity(Entity entity){
_someEntity = entity;
}
public void SetName(string name)
{
if (_someEntity == null) return; //check to avoid null ref
_someEntity.Name = name;
label.SetText(_someEntity.Name);
}
Então, como você pode ver, estou verificando se há nulo sempre. Mas o método não deve ter essa verificação?
Por exemplo, o código externo deve limpar os dados com antecedência, para que os métodos não precisem ser validados como abaixo:
if(entity != null) // this makes the null checks redundant in the methods
{
Manager.AssignEntity(entity);
Manager.SetName("Test");
}
Em resumo, os métodos devem ser "validadores de dados" e, em seguida, processam os dados, ou devem ser garantidos antes de chamá-lo, e se você não validar antes de chamar o método, deve ocorrer um erro (ou capturar o erro)?
fonte
Respostas:
O problema com o seu exemplo básico não é a verificação nula, é a falha silenciosa .
Erros nulos de ponteiro / referência, na maioria das vezes, são erros de programador. Os erros do programador geralmente são mais bem resolvidos ao falhar imediatamente e em voz alta. Você tem três maneiras gerais de lidar com o problema nessa situação:
Uma terceira solução é mais trabalhosa, mas muito mais robusta:
_someEntity
estar em um estado inválido. Uma maneira de fazer isso é se livrarAssignEntity
e exigir isso como um parâmetro para instanciação. Outras técnicas de injeção de dependência também podem ser úteis.Em algumas situações, faz sentido verificar a validade de todos os argumentos de função antes de qualquer trabalho que você esteja realizando; em outras, faz sentido informar ao responsável pela chamada que é responsável por garantir que suas entradas sejam válidas e não verificar. Qual extremidade do espectro em que você vai depender do domínio do seu problema. A terceira opção tem um benefício significativo, pois você pode, até certo ponto, fazer com que o compilador imponha que o chamador faça tudo corretamente.
Se a terceira opção não for uma opção, na minha opinião, isso realmente não importa, contanto que não falhe silenciosamente. Se a não verificação de nulo faz com que o programa exploda instantaneamente, tudo bem, mas se, em vez disso, corromper os dados silenciosamente para causar problemas no caminho, é melhor verificar e lidar com eles imediatamente.
fonte
null
está errado ou é inválido, porque na minha biblioteca hipotética defini os tipos de dados e o que é válido e o que não é. Você chama isso de "forçar suposições", mas adivinhe: é o que toda biblioteca de software existente faz. Há momentos em que nulo é um valor válido, mas isso nem sempre é "".null
corretamente" - Esse é um mal-entendido sobre o que significa manipulação adequada. Para a maioria dos programadores Java, isso significa lançar uma exceção para qualquer entrada inválida. Usando@ParametersAreNonnullByDefault
, significa também para todosnull
, a menos que o argumento esteja marcado como@Nullable
. Fazer qualquer outra coisa significa prolongar o caminho até que finalmente chegue a outro lugar e, assim, também prolongar o tempo perdido na depuração. Bem, ignorando os problemas, você pode conseguir que ele nunca sopra, mas o comportamento incorreto é bastante garantido.Exception
são debates completamente independentes. (Concordo que ambos são problemáticos.) Para este exemplo específico,null
obviamente não faz sentido aqui se o objetivo da classe é invocar métodos no objeto.Como
_someEntity
pode ser modificado em qualquer estágio, faz sentido testá-lo sempre queSetName
for chamado. Afinal, poderia ter mudado desde a última vez em que esse método foi chamado. Mas lembre-se de que o códigoSetName
não é seguro para threads; portanto, você pode executar essa verificação em um thread,_someEntity
definidonull
por outro e, em seguida, o código exibirá um sinal deNullReferenceException
qualquer maneira.Uma abordagem para esse problema, portanto, é ficar ainda mais defensivo e seguir um destes procedimentos:
_someEntity
e faça referência a essa cópia através do método,_someEntity
para garantir que ele não seja alterado à medida que o método for executado,try/catch
ae execute a mesma ação em qualquerNullReferenceException
uma que ocorra dentro do método que você executaria na verificação nula inicial (nesse caso, umanop
ação).Mas o que você realmente deve fazer é parar e se perguntar: você realmente precisa
_someEntity
ser substituído? Por que não configurá-lo uma vez, através do construtor. Dessa forma, você só precisa fazer essa verificação nula uma vez:Se possível, essa é a abordagem que eu recomendaria em tais situações. Se você não se preocupa com a segurança do encadeamento ao escolher como lidar com possíveis nulos.
Como um comentário adicional, sinto que seu código é estranho e pode ser melhorado. Tudo de:
pode ser substituído por:
Que possui a mesma funcionalidade, exceto por poder definir o campo por meio do configurador de propriedades, em vez de um método com um nome diferente.
fonte
Esta é a sua escolha.
Ao criar um
public
método, você está oferecendo ao público a oportunidade de chamá-lo. Isso sempre vem junto com um contrato implícito sobre como chamar esse método e o que esperar ao fazê-lo. Este contrato pode (ou não) incluir " sim, uhhm, se você passarnull
como um valor de parâmetro, ele explodirá na sua cara ". Outras partes desse contrato podem ser " oh, BTW: toda vez que dois threads executam esse método ao mesmo tempo, um gatinho morre ".Que tipo de contrato você deseja criar depende de você. O importante é
crie uma API que seja útil, consistente e
documentá-lo bem, especialmente para esses casos extremos.
Quais são os casos prováveis de como seu método está sendo chamado?
fonte
As outras respostas são boas; Eu gostaria de estendê-los fazendo alguns comentários sobre modificadores de acessibilidade. Primeiro, o que fazer se
null
nunca for válido:métodos públicos e protegidos são aqueles em que você não controla o chamador. Eles devem jogar quando passados nulos. Dessa forma, você treina seus chamadores para nunca passarem nulos, porque eles gostariam que o programa deles não falhasse.
internas e privadas métodos são aqueles onde você não controlar o chamador. Eles devem afirmar quando passaram nulos; eles provavelmente travarão mais tarde, mas pelo menos você obteve a afirmação primeiro e uma oportunidade de invadir o depurador. Mais uma vez, você deseja treinar seus chamadores para chamá-lo corretamente, causando danos quando eles não o fazem.
Agora, o que você faria se isso pode ser válido para um nulo para ser passado em? Eu evitaria essa situação com o padrão de objeto nulo . Ou seja: crie uma instância especial do tipo e exija que ela seja usada sempre que um objeto inválido for necessário . Agora você está de volta ao mundo agradável de jogar / afirmar cada uso de null.
Por exemplo, você não deseja estar nesta situação:
e no site da chamada:
Porque (1) você está treinando seus chamadores que nulo é um bom valor e (2) não está claro quais são as semânticas desse valor. Em vez disso, faça o seguinte:
E agora o site de chamada fica assim:
e agora o leitor do código sabe o que significa.
fonte
if
s para os objetos nulos, mas use o polimorfismo para fazê-los se comportar corretamente.EmptyQueue
tipo que lança quando desenfileirado e assim por diante.Person
classe era imutável, e não continha um construtor argumento zero, como você construir suasApproverNotRequired
ouApproverUnknown
casos? Em outras palavras, quais valores você passaria no construtor?As outras respostas apontam que seu código pode ser limpo para não precisar de uma verificação nula onde você a possui, no entanto, para obter uma resposta geral sobre o que pode ser uma verificação nula útil, considere o seguinte código de exemplo:
Isso oferece uma vantagem para a manutenção. Se ocorrer algum defeito no seu código em que algo passe um objeto nulo para o método, você obterá informações úteis do método sobre qual parâmetro foi nulo. Sem as verificações, você receberá uma NullReferenceException na linha:
E (considerando que a propriedade Something é um tipo de valor), a ou b pode ser nulo e você não terá como saber qual do rastreamento de pilha. Com as verificações, você saberá exatamente qual objeto foi nulo, o que pode ser extremamente útil ao tentar remover um defeito.
Gostaria de observar que o padrão no seu código original:
Pode ter seus usos em determinadas circunstâncias, também pode (possivelmente com mais freqüência) fornecer uma maneira muito boa de mascarar problemas subjacentes em sua base de código.
fonte
Não, não, mas depende.
Você só faz uma verificação de referência nula se quiser reagir a ela.
Se você fizer uma verificação de referência nula e lançar uma exceção , forçará o usuário do método a reagir e permitir que ele se recupere. O programa ainda funciona conforme o esperado.
Se você não fizer uma verificação de referência nula (ou lançar uma exceção desmarcada), ela poderá lançar uma desmarcada
NullReferenceException
, que geralmente não é tratada pelo usuário do método e pode até desligar o aplicativo. Isso significa que a função é obviamente completamente incompreendida e, portanto, o aplicativo está com defeito.fonte
Algo como uma extensão da resposta do @ null, mas, na minha experiência, faça verificações nulas, não importa o quê.
Uma boa programação defensiva é a atitude de codificar a rotina atual isoladamente, supondo que todo o resto do programa esteja tentando travá-la. Quando você está escrevendo um código de missão crítica, onde a falha não é uma opção, esse é o único caminho a percorrer.
Agora, como você realmente lida com um
null
valor, isso depende muito do seu caso de uso.Se
null
for um valor aceitável, por exemplo, qualquer um dos parâmetros do segundo ao quinto da rotina _splitpath () da MSVC, lidar com ele silenciosamente é o comportamento correto.Se
null
nunca deveria acontecer ao chamar a rotina em questão, ou seja, no caso do OP, o contrato da API precisaAssignEntity()
ser chamado com um válido e, emEntity
seguida, lance uma exceção ou falhe, garantindo que você faça muito barulho no processo.Nunca se preocupe com o custo de uma verificação nula, eles são muito baratos se acontecerem e, com os compiladores modernos, a análise de código estático pode e irá eliminá-los completamente se o compilador determinar que isso não acontecerá.
fonte