Verificar parâmetros anotados com @Nonnull para null?

19

Começamos a usar o FindBugs e a anotar nossos parâmetros @Nonnulladequadamente, e é ótimo apontar erros no início do ciclo. Até o momento, continuamos verificando esses argumentos para nullusar Guava's checkNotNull, mas eu preferiria verificar nullapenas nas bordas - lugares onde o valor pode chegar sem ter sido verificado null, por exemplo, uma solicitação SOAP.

// service layer accessible from outside
public Person createPerson(@CheckForNull String name) {
    return new Person(Preconditions.checkNotNull(name));
}

...

// internal constructor accessed only by the service layer
public Person(@Nonnull String name) {
    this.name = Preconditions.checkNotNull(name);  // remove this check?
}

Eu entendo que @Nonnullnão bloqueia nullvalores em si.

No entanto, considerando que o FindBugs indicará em qualquer lugar que um valor seja transferido de um campo não marcado para um marcado @Nonnull, não podemos depender dele para capturar esses casos (o que ocorre) sem precisar verificar esses valores em nulltodos os lugares em que eles são passados o sistema? Sou ingênuo em querer confiar na ferramenta e evitar essas verificações detalhadas?

Conclusão: embora pareça seguro remover a segunda nullverificação abaixo, é uma má prática?

Essa pergunta talvez seja muito parecida com Deve-se procurar nulo se ele não espera nulo , mas estou perguntando especificamente em relação à @Nonnullanotação.

David Harkness
fonte

Respostas:

6

Se você não confia no FindBug, pode ser uma opção para usar uma asserção. Dessa forma, você evita os problemas mencionados pelo User MSalters, mas ainda tem uma verificação. Obviamente, essa abordagem pode não ser aplicável se tal abordagem à prova de falhas não for viável, ou seja, você precisará capturar qualquer exceção.

E para responder ainda mais à pergunta se é uma má prática. Bem, isso é discutível, mas eu não pensaria assim. Considero essa anotação do FindBug como uma especificação leve, seguindo o princípio do design por contrato.

No design por contrato, você estabelece um contrato entre um método e seu chamador. O contrato consiste em pré-condições que o chamador concorda em cumprir ao chamar o método e em uma pós-condição que, por sua vez, é cumprida pelo método após a execução, se sua pré-condição for atendida.

Um dos benefícios desse método de desenvolvimento é que você pode evitar o chamado estilo de programação defensiva (no qual você verifica explicitamente todos os valores de parâmetros inválidos etc.). Em vez disso, você pode confiar no contrato e evitar verificações redundantes para aumentar o desempenho e a legibilidade.

Os contratos podem ser usados ​​para a verificação da asserção de tempo de execução, que segue o princípio de falha-primeiro mencionado acima, no qual você deseja que seu programa falhe se um contrato for quebrado, fornecendo uma pista para identificar a origem do erro. (Uma pré-condição com falha significa que o chamador fez algo errado, uma pós-condição com falha indica um erro de implementação do método fornecido)

Além disso, os contratos podem ser usados ​​para análise estática, que tenta tirar conclusões sobre o código fonte sem realmente executá-lo.

A anotação @nonnull pode ser vista como uma pré-condição usada para análise estática pela ferramenta FindBugs. Se você preferir uma abordagem como a verificação de asserção de tempo de execução, ou seja, a estratégia de falha em primeiro lugar, considere o uso de instruções de asserção como Java interno. Ou talvez para se adaptar a uma estratégia de especificação mais sofisticada usando uma linguagem de especificação de interface comportamental, como a Java Modeling Language (JML).

JML é uma extensão do Java na qual a especificação é incorporada em Comentários Java especiais. Uma vantagem dessa abordagem é que você pode usar especificações sofisticadas, mesmo usando uma abordagem de desenvolvimento na qual confia apenas nas especificações, e não nos detalhes da implementação, e utiliza diferentes ferramentas que fazem uso da especificação, como as mencionadas ferramentas de verificação de asserção de tempo de execução, geração automatizada de testes de unidade ou documentação.

Se você compartilhar meu ponto de vista de @nonnull como um contrato (leve), seria uma prática comum não adicionar verificações adicionais, porque a idéia original por trás desse princípio é evitar a programação defensiva.

FabianB
fonte
2
É exatamente assim que estou usando @Nonnull: como uma especificação de contrato. Venho removendo os cheques defensivos por trás do contrato e até agora não tive problemas.
David Harkness
4

Bem, considere por que você não escreve

this.name = Preconditions.checkNotNull(name);  // Might be necessary
that.name = Preconditions.checkNotNull(this.name);  // Who knows?

Programar não é magia negra. Variáveis ​​de repente não se tornam nulas. Se você sabe que uma variável não é Nula e sabe que ela não foi alterada, não verifique.

Se você verificar, algum programador no futuro (possivelmente você) passará muito tempo descobrindo por que essa verificação está lá. A verificação deve estar lá por um motivo, afinal, então o que você está ignorando?

MSalters
fonte
3
O @Nonnullcontrato estipula que os chamadores não devem passar nullpara o construtor, e o FindBugs usa análise estática para localizar chamadas que possam permitir - nullespecificamente, chamadas que passam valores não marcados por @Nonnullsi mesmos. Mas um chamador é livre para passar nulle ignorar o aviso do FindBugs.
David Harkness
A programação não é idealmente magia negra. Infelizmente, existem muitas advertências e surpresas, especialmente quando se trabalha com alguns dos códigos de simultaneidade disponíveis por aí ♬ ~ ᕕ (ᐛ) ᕗ
Tim Harper