Por que é uma má idéia criar um setter genérico e getter com reflexão?

47

Há algum tempo eu escrevi esta resposta para uma pergunta sobre como evitar ter um getter e setter para cada variável mutável . Na época, eu tinha apenas uma intuição difícil de verbalizar de que essa é uma Idéia Ruim, mas o OP estava explicitamente perguntando como fazer isso. Eu procurei aqui por que isso pode ser um problema e encontrei pergunta , cujas respostas parecem tomar como dado que usar a reflexão, a menos que seja absolutamente necessário, é uma má prática.

Então, alguém pode verbalizar por que é um dado que a reflexão deva ser evitada, especificamente no caso do getter e setter genérico?

    
por HAEM 09.10.2017 / 16:39
fonte

4 respostas

114

Desvantagens da reflexão em geral

A reflexão é mais difícil de entender do que o código em linha reta.

Na minha experiência, a reflexão é um recurso de "nível de especialista" em Java. Eu diria que a maioria dos programadores nunca usa a reflexão ativamente (ou seja, o consumo de bibliotecas que usam reflexão não conta). Isso torna o código mais difícil de entender para esses programadores.

O código de reflexão está inacessível à análise estática

Suponha que eu tenha um getter getFoo em minha classe e quero renomeá-lo para getBar . Se eu não usar nenhum reflexo, posso apenas pesquisar a base de código para getFoo e encontrarei todos os locais que usam o getter para que eu possa atualizá-lo, e mesmo se eu perder um, o compilador irá reclamar.

Mas se o lugar que usa o getter for algo como callGetter("Foo") e callGetter faz getClass().getMethod("get"+name).invoke(this) , o método acima não o encontrará e o compilador não se queixará. Somente quando o código for realmente executado, você obterá um NoSuchMethodException . E imagine a dor em que você está se essa exceção (que é rastreada) for engolida por callGetter porque "ela é usada apenas com strings codificadas, não pode acontecer de fato". (Ninguém faria isso, alguém poderia argumentar? Exceto que o OP fez exatamente isso em sua resposta SO. Se o campo for renomeado, os usuários do genérico nunca perceberiam, exceto pelo bug extremamente obscuro do setter silenciosamente não fazendo nada. Usuários do getter podem, se tiverem sorte, perceber a saída do console da exceção ignorada.)

O código de reflexão não é verificado pelo compilador

Este é basicamente um grande sub-ponto do que foi dito acima. O código de reflexão é sobre Object . Os tipos são verificados no tempo de execução. Erros são descobertos por testes de unidade, mas somente se você tiver cobertura. ("É apenas um getter, eu não preciso testá-lo.") Basicamente, você perde a vantagem usando Java sobre Python ganhou em primeiro lugar.

O código de reflexão não está disponível para otimização

Talvez não em teoria, mas, na prática, você não encontrará uma JVM que insira ou crie um cache in-line para Method.invoke . Chamadas de método normais estão disponíveis para essas otimizações. Isso os torna muito mais rápidos.

O código de reflexão é lento em geral

A pesquisa de método dinâmico e a verificação de tipo necessárias para o código de reflexão são mais lentas que as chamadas de método normais. Se você transformar aquele getter barato de uma linha em um animal de reflexão, você pode (eu não medi isso) estar olhando para várias ordens de magnitude de desaceleração.

Desvantagem do getter / setter genérico especificamente

Isso é apenas uma má ideia, porque sua classe agora não tem mais encapsulamento. O campo Every que ele possui está acessível. Você também pode torná-los públicos.

    
por 09.10.2017 / 16:57
fonte
11

Porque os getters / setters de passagem são uma abominação que não fornece nenhum valor. Eles não fornecem nenhum encapsulamento, pois os usuários podem obter os valores reais por meio das propriedades. Eles são YAGNI porque você raramente, ou nunca, mudará a implementação do seu armazenamento de campo.

E porque isso é muito menos eficiente. Pelo menos em c #, um getter / setter entrará em linha reta em uma única instrução CIL. Em sua resposta, você está substituindo isso por três chamadas de função, que por sua vez precisam carregar metadados para refletir. Eu geralmente usei 10x como regra geral para os custos de reflexão, mas quando eu procurei por dados, uma das primeiras respostas incluiu esta resposta qual é o boletim mais próximo de 1000x.

(E isso torna o seu código mais difícil de depurar, e prejudica a usabilidade porque há mais maneiras de usá-lo incorretamente e prejudica a capacidade de manutenção porque agora você está usando sequências mágicas em vez de identificadores adequados ...)

    
por 09.10.2017 / 16:55
fonte
8

Além dos argumentos já apresentados.

Não fornece a interface esperada.

Os usuários esperam chamar foo.getBar () e foo.setBar (value), não foo.get ("bar") e foo.set ("bar", value)

Para fornecer a interface que os usuários esperam, é necessário escrever um getter / setter separado para cada propriedade. Depois de ter feito isso, não faz sentido usar a reflexão.

    
por 10.10.2017 / 14:18
fonte
-4

Claro que não é uma má idéia, é apenas que em um mundo java normal é uma má idéia (quando você quer apenas um getter ou setter). Por exemplo, alguns frameworks (spring mvc) ou librares já usam (jstl) dessa forma, alguns analisadores json o xml estão usando, se você quiser usar uma DSL você vai usá-lo. Então, depende do seu cenário de uso.

Nada está certo ou errado como um fato.

    
por 09.10.2017 / 20:36
fonte