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

49

Há algum tempo, escrevi esta resposta para uma pergunta sobre como evitar um getter e setter para cada variável mutável. Na época, eu tinha apenas um pressentimento difícil de verbalizar de que essa é uma má ideia, mas o OP estava perguntando explicitamente como fazê-lo. Procurei aqui por que isso pode ser um problema e encontrei essa pergunta , cujas respostas parecem ter como certo que o uso da reflexão, a menos que absolutamente necessário, seja uma prática ruim.

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

HAEM
fonte
12
Se você deseja apenas reduzir o padrão, o Lombok e o Groovy geram getters e setters silenciosamente, mas com segurança.
usar o seguinte comando
2
Como você consumiria esses getters e setters em uma linguagem estática como o java? Parece um começo para mim.
Esben Skov Pedersen 10/10
@EsbenSkovPedersen Você os consumiria como Mapos gete put, que é uma maneira bastante desajeitada de fazer as coisas.
HAEM 10/10
2
O IIRC, Lombok sintetiza getters / setters em tempo de compilação, conforme ditado pela anotação apropriada, para que eles: não sofram a penalidade de desempenho da reflexão, possam ser analisados ​​/ incorporados estaticamente, podem ser refatorados (o IntelliJ possui um plug-in para o Lombok)
Alexander

Respostas:

117

Desvantagens da reflexão em geral

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

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 reflexão ativamente (ou seja, consumir bibliotecas que usam reflexão não conta). Isso torna o código mais difícil de entender para esses programadores.

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

Suponha que eu tenha um getter getFoona minha classe e queira renomeá-lo para getBar. Se eu não usar reflexão, basta pesquisar na base de código getFooe encontrar todos os locais que usam o getter para que eu possa atualizá-la, e mesmo que eu perca uma, o compilador reclamará.

Mas se o local que usa o getter é algo como callGetter("Foo")e o callGetterfaz getClass().getMethod("get"+name).invoke(this), então o método acima não o encontrará e o compilador não irá reclamar. Somente quando o código for realmente executado, você obterá um NoSuchMethodException. E imagine a dor que você sente se essa exceção (que é rastreada) é engolida callGetterporque "é usada apenas com cadeias codificadas, não pode realmente acontecer". (Ninguém faria isso, alguém poderia argumentar? Exceto que o OP fez exatamente isso em sua resposta ao SO. Se o campo fosse renomeado, os usuários do setter genérico nunca perceberiam, exceto o bug extremamente obscuro do setter silenciosamente, sem fazer nada. Os 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 tipo pelo compilador

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

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

Talvez não na teoria, mas na prática, você não encontrará uma JVM que inclua ou crie um cache embutido Method.invoke. As chamadas de método normal estão disponíveis para essas otimizações. Isso os torna muito mais rápidos.

Código de reflexão é apenas 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 esse dispositivo barato de uma linha em um animal de reflexão, poderá (eu não medi isso) observar várias ordens de magnitude de desaceleração.

Desvantagem do getter / setter genérico especificamente

Essa é apenas uma péssima idéia, porque sua classe agora não tem mais encapsulamento. Todos os campos que ele possui são acessíveis. Você também pode torná-los públicos.

Sebastian Redl
fonte
8
Você atinge todos os pontos principais. Praticamente uma resposta perfeita. Eu poderia questionar que getters e setter são marginalmente melhores que os campos públicos, mas o ponto maior está correto.
21717 JimmyJames
2
Também vale a pena mencionar que getters / setters também podem ser facilmente gerados por um IDE.
stiemannkj1
26
+1 A lógica de reflexão de depuração em um projeto de tamanho de produção deve ser reconhecida como tortura pela ONU e ilegalizada.
errantlinguist
4
"mais difícil de entender para esses programadores." - para esses programadores, é significativamente difícil de entender, mas é mais difícil de entender para todos, por mais proficiente que seja.
precisa saber é o seguinte
4
"Código de reflexão inacessível à análise estática" - isto. Tão importante para ativar a refatoração. E à medida que as ferramentas de análise estática se tornam mais poderosas (por exemplo, pesquisa de código no Team Foundation Server mais recente), escrever código que as habilite se torna ainda mais valioso.
Dan J
11

Como os getter / setters de passagem são uma abominação que não fornece 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 muda a implementação do seu armazenamento em campo.

E porque isso tem muito menos desempenho. Em C #, pelo menos, um getter / setter irá alinhar diretamente para uma única instrução CIL. Na sua resposta, você está substituindo por três chamadas de função, que, por sua vez, precisam carregar metadados para refletir. Geralmente, usei 10x como regra geral para custos de reflexão, mas quando pesquisei dados, uma das primeiras respostas incluiu essa resposta, que aproximou 1000x.

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

Telastyn
fonte
2
Observe que a pergunta está marcada como Java, que tem uma porcaria deliciosa como o conceito Beans. Um bean não possui campos públicos, mas pode expor campos via getX()e setX()métodos que podem ser encontrados via reflexão. Os feijões não devem necessariamente encapsular seus valores, eles podem ser apenas DTOs. Portanto, em Java, os getters geralmente são uma dor necessária. As propriedades C # são muito, muito melhores em todos os aspectos.
amon
11
Porém, as propriedades C # são getters / setters vestidos. Mas sim, o conceito Beans é um desastre.
Sebastian Redl
6
@ amon Certo, o C # teve uma péssima idéia e tornou mais fácil de implementar.
21417 JimmyJames
5
@JimmyJames Não é uma ideia terrível, realmente; você pode manter a compatibilidade ABI apesar das alterações de código. Um problema que muitos não têm, suponho.
Casey #
3
@ Casey O escopo disso vai além de um comentário, mas a criação de uma interface a partir dos detalhes internos da implementação é invertida. Isso leva a um desenho processual. Há momentos em que ter um método getX é a resposta certa, mas é pouco frequente em códigos bem projetados. O problema com getters e setters em Java não é o código padrão, eles fazem parte de uma péssima abordagem de design. Tornar isso mais fácil é como tornar mais fácil dar um soco na cara.
21717 JimmyJames
8

Além dos argumentos já apresentados.

Não fornece a interface esperada.

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

Para fornecer a interface que os usuários esperam, você precisa escrever um getter / setter separado para cada propriedade. Depois de fazer isso, não há sentido em usar a reflexão.

Peter Green
fonte
Parece-me que esse motivo é mais importante que todos os outros em outras respostas.
Esben Skov Pedersen 10/10
-4

Claro que não é uma má ideia, é apenas uma má ideia (em um mundo java normal) (quando você só quer um getter ou setter). Por exemplo, algumas estruturas (spring mvc) ou librares já o usam (jstl) dessa maneira, alguns analisadores json ou xml estão usando, se você deseja usar um DSL, você o usará. Então isso depende do seu cenário de caso de uso.

Nada está certo ou errado como fato.

como um fato
fonte