Estou me perguntando se devo me defender contra o valor de retorno de uma chamada de método, validando que eles atendam às minhas expectativas, mesmo que eu saiba que o método que estou chamando atenderá a essas expectativas.
DADO
User getUser(Int id)
{
User temp = new User(id);
temp.setName("John");
return temp;
}
EU DEVERIA FAZER
void myMethod()
{
User user = getUser(1234);
System.out.println(user.getName());
}
OU
void myMethod()
{
User user = getUser(1234);
// Validating
Preconditions.checkNotNull(user, "User can not be null.");
Preconditions.checkNotNull(user.getName(), "User's name can not be null.");
System.out.println(user.getName());
}
Estou perguntando isso no nível conceitual. Se eu conheço o funcionamento interno do método que estou chamando. Ou porque eu escrevi ou inspecionei. E a lógica dos possíveis valores que ela retorna atende às minhas pré-condições. É "melhor" ou "mais apropriado" pular a validação ou ainda devo me defender de valores errados antes de prosseguir com o método que estou implementando atualmente, mesmo que sempre seja aprovado.
Minha conclusão de todas as respostas (sinta-se à vontade para vir para o seu próprio país):
Afirme quando- O método demonstrou se comportar mal no passado
- O método é de uma fonte não confiável
- O método é usado em outros lugares e não indica explicitamente suas pós-condições
- O método está próximo ao seu (consulte a resposta escolhida para obter detalhes)
- O método define explicitamente seu contrato com algo como documento adequado, segurança de tipo, teste de unidade ou verificação pós-condição
- O desempenho é crítico (nesse caso, uma declaração do modo de depuração pode funcionar como uma abordagem híbrida)
java
validation
null
return-type
defensive-programming
Didier A.
fonte
fonte
Null
)? Provavelmente é igualmente problemático se o nome for""
(não nulo, mas a sequência vazia) ou"N/A"
. Ou você pode confiar no resultado ou precisa ser adequadamente paranóico.Respostas:
Isso depende da probabilidade de alteração do getUser e do myMethod e, mais importante, da probabilidade de alteração independente um do outro .
Se, de alguma forma, você tem certeza de que o getUser nunca mudará no futuro, então sim, é uma perda de tempo validando-a, tanto quanto é uma perda de tempo validando que
i
tem um valor de 3 imediatamente após umai = 3;
declaração. Na realidade, você não sabe disso. Mas existem algumas coisas que você sabe:Essas duas funções "vivem juntas"? Em outras palavras, eles mantêm as mesmas pessoas, fazem parte do mesmo arquivo e, portanto, tendem a permanecer "sincronizados" entre si por conta própria? Nesse caso, provavelmente é um exagero adicionar verificações de validação, pois isso significa apenas mais código que precisa ser alterado (e potencialmente gera bugs) toda vez que as duas funções mudam ou são refatoradas para um número diferente de funções.
A função getUser faz parte de uma API documentada com um contrato específico e o myMethod é apenas um cliente dessa API em outra base de código? Nesse caso, você pode ler essa documentação para descobrir se deve validar valores de retorno (ou pré-validar parâmetros de entrada!) Ou se é realmente seguro seguir cegamente o caminho feliz. Se a documentação não esclarecer isso, peça ao mantenedor para corrigir isso.
Finalmente, se essa função em particular mudou repentinamente e inesperadamente seu comportamento no passado, de uma maneira que quebrou seu código, você tem todo o direito de ficar paranóico com relação a ela. Os erros tendem a se agrupar.
Observe que todas as opções acima se aplicam mesmo que você seja o autor original das duas funções. Não sabemos se é esperado que essas duas funções "convivam" pelo resto de suas vidas, ou se elas se separem lentamente em módulos separados, ou se você, de alguma forma, se matou com um bug nos antigos versões do getUser. Mas você provavelmente pode adivinhar bastante.
fonte
getUser
mais tarde, para que pudesse retornar nulo, a verificação explícita forneceria informações que você não pôde obter de "NullPointerException" e o número da linha?A resposta do Ixrec é boa, mas adotarei uma abordagem diferente porque acredito que vale a pena considerar. Para os propósitos desta discussão, falarei sobre afirmações, já que esse é o nome pelo qual você
Preconditions.checkNotNull()
é tradicionalmente conhecido.O que eu gostaria de sugerir é que os programadores geralmente superestimam o grau em que têm certeza de que algo se comportará de uma certa maneira e subestimam as consequências de não se comportar dessa maneira. Além disso, os programadores geralmente não percebem o poder das asserções como documentação. Posteriormente, na minha experiência, os programadores afirmam as coisas com muito menos frequência do que deveriam.
Meu lema é:
Naturalmente, se você tiver certeza absoluta de que algo se comporta de uma certa maneira, você se abstém de afirmar que de fato se comportou dessa maneira, e isso é geralmente razoável. Não é realmente possível escrever software se você não pode confiar em nada.
Mas há exceções até para esta regra. Curiosamente, para tomar o exemplo de Ixrec, existe um tipo de situação em que é perfeitamente desejável seguir
int i = 3;
com uma afirmação quei
está de fato dentro de um certo intervalo. Essa é a situação em quei
é suscetível de ser alterada por alguém que está tentando vários cenários hipotéticos, e o código a seguir baseia-se emi
ter um valor dentro de um determinado intervalo, e não é imediatamente óbvio olhando rapidamente para o código a seguir. o intervalo aceitável é. Portanto,int i = 3; assert i >= 0 && i < 5;
a princípio pode parecer sem sentido, mas se você pensar sobre isso, ele diz que você pode brincari
e também indica o alcance dentro do qual você deve permanecer. Uma asserção é o melhor tipo de documentação, porqueé imposta pela máquina , por isso é uma garantia perfeita e é refatorada juntamente com o código , portanto permanece sempre pertinente.Seu
getUser(int id)
exemplo não é um parente conceitual muito distante doassign-constant-and-assert-in-range
exemplo. Você vê, por definição, erros que acontecem quando as coisas exibem um comportamento diferente do que esperávamos. É verdade que não podemos afirmar tudo, portanto, às vezes, haverá alguma depuração. Mas é um fato bem estabelecido na indústria que, quanto mais rápido detectamos um erro, menos isso custa. As perguntas que você deve fazer não são apenas a certeza de que vocêgetUser()
nunca retornará nulo ((e nunca retornará um usuário com um nome nulo)), mas também que tipos de coisas ruins acontecerão com o restante do código, se ocorrerem em um dia, retorne algo inesperado e como é fácil olhar rapidamente para o restante do código para saber exatamente o que era esperadogetUser()
.Se o comportamento inesperado fizer com que seu banco de dados fique corrompido, talvez você deva afirmar mesmo se tiver 101% de certeza de que isso não acontecerá. Se um
null
nome de usuário causasse algum erro estranho e não rastreável de milhares de linhas mais abaixo e bilhões de ciclos de clock depois, talvez seja melhor falhar o mais cedo possível.Portanto, mesmo que eu não discorde da resposta de Ixrec, sugiro que você considere seriamente o fato de que as afirmações não custam nada; portanto, não há realmente nada a perder, apenas a ser ganho, usando-as liberalmente.
fonte
... && sureness < 1)
.Um não definitivo.
Um chamador nunca deve verificar se a função que está chamando respeita seu contrato. O motivo é simples: existem potencialmente muitos chamadores e é impraticável e até mesmo errado do ponto de vista conceitual de cada chamador duplicar a lógica para verificar os resultados de outras funções.
Se alguma verificação for feita, cada função deve verificar suas próprias pós-condições. As pós-condições dão a você a confiança de que você implementou a função corretamente e os usuários poderão confiar no contrato de sua função.
Se uma determinada função nunca retornar nula, isso deve ser documentado e se tornar parte do contrato dessa função. Este é o objetivo desses contratos: oferecer aos usuários alguns invariantes em que possam confiar, para que enfrentem menos obstáculos ao escrever suas próprias funções.
fonte
Se null for um valor de retorno válido do método getUser, seu código deverá tratar disso com um If, não uma Exception - não é um caso excepcional.
Se null não for um valor de retorno válido do método getUser, ocorrerá um erro no getUser - o método getUser deve lançar uma exceção ou ser corrigido.
Se o método getUser fizer parte de uma biblioteca externa ou não puder ser alterado e você desejar que exceções sejam lançadas no caso de um retorno nulo, agrupe a classe e o método para executar as verificações e ative a exceção para que todas as instâncias de a chamada para getUser é consistente no seu código.
fonte
Eu diria que a premissa da sua pergunta está errada: por que usar uma referência nula para começar?
O padrão de objeto nulo é uma maneira de retornar objetos que essencialmente não fazem nada: em vez de retornar nulo para o usuário, retorne um objeto que represente "nenhum usuário".
Esse usuário ficaria inativo, teria um nome em branco e outros valores não nulos indicando "nada aqui". O principal benefício é que você não precisa desarrumar o seu programa, irá anular verificações, registros etc. Você apenas usa seus objetos.
Por que um algoritmo deve se importar com um valor nulo? Os passos devem ser os mesmos. É igualmente fácil e muito mais claro ter um objeto nulo que retorne zero, sequência em branco etc.
Aqui está um exemplo de verificação de código para null:
Aqui está um exemplo de código usando um objeto nulo:
Qual é mais claro? Eu acredito que o segundo é.
Enquanto a pergunta é marcada como java , é importante notar que o padrão de objeto nulo é realmente útil em C ++ ao usar referências. As referências Java são mais parecidas com ponteiros C ++ e permitem nulo. Referências C ++ não podem ser mantidas
nullptr
. Se alguém quiser ter algo análogo a uma referência nula em C ++, o padrão de objeto nulo é a única maneira que conheço para fazer isso.fonte
getCount()
documentar uma garantia de que agora não retornará 0, e não o fará no futuro, exceto em circunstâncias em que alguém decida fazer uma alteração significativa e procure atualizar tudo o que for necessário para lidar com isso. a nova interface. Ver o que o código atualmente faz não ajuda, mas se você for inspecionar o código, o código a ser inspecionado são os testes de unidadegetCount()
. Se eles testarem para não retornar 0, você saberá que qualquer alteração futura para retornar 0 será considerada uma alteração de ruptura porque os testes falharão.Geralmente, eu diria que depende principalmente dos três aspectos a seguir:
robustez: o método de chamada pode lidar com o
null
valor? Se umnull
valor resultar em umaRuntimeException
recomendação, eu recomendaria uma verificação - a menos que a complexidade seja baixa e os métodos de chamada / chamada sejam criados pelo mesmo autor enull
não sejam esperados (por exemplo, ambosprivate
no mesmo pacote).responsabilidade do código: se o desenvolvedor A é responsável pelo
getUser()
método e o desenvolvedor B o usa (por exemplo, como parte de uma biblioteca), recomendo fortemente que valide seu valor. Só porque o desenvolvedor B pode não saber sobre uma alteração que resulta em um potencialnull
valor de retorno.complexidade: quanto maior a complexidade geral do programa ou ambiente, mais eu recomendaria validar o valor de retorno. Mesmo se você tiver certeza de que não pode ser
null
hoje no contexto do método de chamada, pode ser que você precise mudargetUser()
para outro caso de uso. Passados alguns meses ou anos e adicionadas milhares de linhas de códigos, isso pode ser uma armadilha.Além disso, eu recomendo documentar possíveis
null
valores de retorno em um comentário JavaDoc. Devido ao destaque das descrições do JavaDoc na maioria dos IDEs, esse pode ser um aviso útil para quem usagetUser()
.fonte
Você definitivamente não precisa.
Por exemplo, se você já sabe que um retorno nunca pode ser nulo - Por que você deseja adicionar uma verificação nula? Adicionar uma verificação nula não quebrará nada, mas é apenas redundante. E melhor não codificar lógica redundante, desde que você possa evitá-la.
fonte