Existem problemas com o uso do Reflection?

49

Não sei por que, mas sempre sinto que estou "trapaceando" quando uso a reflexão - talvez seja por causa do desempenho que sei que estou sofrendo.

Parte de mim diz que, se faz parte do idioma que você está usando e pode realizar o que está tentando fazer, por que não usá-lo? A outra parte de mim diz que deve haver uma maneira de fazer isso sem usar a reflexão. Eu acho que talvez dependa da situação.

Quais são os possíveis problemas que preciso observar ao usar a reflexão e qual a minha preocupação com eles? Quanto esforço vale a pena tentar encontrar uma solução mais convencional?

PB
fonte
2
Eu acho que depende da linguagem e do ambiente. Alguns apóiam e até incentivam. Às vezes, ao trabalhar em Java, desejava um melhor suporte à reflexão.
FrustratedWithFormsDesigner
18
Bem, acho que não é tão difícil discernir entre "trapacear a reflexão" e um uso válido dela: quando você inspeciona membros privados ou usa-o para chamar métodos privados (para contornar interfaces), é muito provável que trapaceie. Quando você o usa para interagir com tipos dos quais você não tem conhecimento, provavelmente tudo bem (ligação de dados, proxies etc.).
Falcon
11
Que linguagem você está usando? Brainfuck não tem reflexão e Python é apenas diferente.
Job
11
Eu também nunca entendi por que é permitido acessar métodos privados através da reflexão. De acordo com minha intuição, isso deve ser proibido.
Giorgio
3
Difícil de acreditar que uma pergunta tão mal formulada tenha 27 votos positivos sem nenhuma edição.
Aaronaught

Respostas:

48

Não, não é trapaça - é uma maneira de resolver problemas em algumas linguagens de programação.

Agora, muitas vezes não é a melhor solução (mais limpa, mais simples, mais fácil de manter). Se houver uma maneira melhor, use essa de fato. No entanto, às vezes não há. Ou, se houver, é muito mais complexo, envolvendo muita duplicação de código etc., o que torna inviável (difícil de manter a longo prazo).

Dois exemplos de nosso projeto atual (Java):

  • algumas de nossas ferramentas de teste usam reflexão para carregar a configuração de arquivos XML. A classe a ser inicializada possui campos específicos, e o carregador de configuração usa reflexão para corresponder ao elemento XML nomeado fieldXpara o campo apropriado na classe e para inicializar o último. Em alguns casos, ele pode criar uma caixa de diálogo GUI simples a partir das propriedades identificadas em tempo real. Sem reflexão, isso levaria centenas de linhas de código em vários aplicativos. Portanto, a reflexão nos ajudou a montar uma ferramenta simples rapidamente, sem muito barulho, e nos permitiu focar na parte importante (regressão testando nosso aplicativo da web, analisando os logs do servidor etc.) e não no irrelevante.
  • um módulo de nosso aplicativo da web herdado foi destinado a exportar / importar dados de tabelas de banco de dados para planilhas do Excel e vice-versa. Continha muitos códigos duplicados, onde é claro que as duplicações não eram exatamente as mesmas, algumas delas continham bugs etc. Usando reflexão, introspecção e anotações, consegui eliminar a maior parte da duplicação, reduzindo a quantidade de código de mais de 5K a abaixo de 2.4K, tornando o código mais robusto e muito mais fácil de manter ou estender. Agora esse módulo deixou de ser um problema para nós - graças ao uso criterioso da reflexão.

A linha inferior é, como qualquer ferramenta poderosa, a reflexão também pode ser usada para dar um tiro no próprio pé. Se você aprender quando e como (não) usá-lo, poderá oferecer soluções elegantes e limpas para problemas difíceis. Se você abusar, pode transformar um problema simples em uma bagunça complexa e feia.

Péter Török
fonte
8
A reflexão +1 é incrivelmente útil para importação / exportação de dados quando você possui objetos intermediários por motivos de conversão.
Ed James
2
Também é incrivelmente útil em aplicativos orientados a dados - em vez de usar uma enorme pilha de if (propName = n) setN(propValue);, você pode nomear suas tags XML (ou seja) iguais às suas propriedades de código e executar um loop sobre elas. Esse método também facilita muito a adição de propriedades posteriormente.
Michael K
A reflexão é muito usada em interfaces fluentes, como o FluentNHibernate.
Scott Whitlock
4
@ Michael: Até você decidir renomear uma dessas propriedades na classe e descobrir que seu código explode. Se você não precisa manter a comparabilidade com as coisas que seu programa gera, tudo bem, mas suspeito que não seja a maioria de nós.
Billy ONeal
11
@ Billy, eu não quis contradizer você, eu concordo com o que você escreveu. Embora, o exemplo que você traz é IMHO, mais uma subcasa da regra geral "evite alterar as interfaces públicas".
Péter Török
37

Não é trapaça. Mas geralmente é uma má ideia no código de produção pelo menos pelos seguintes motivos:

  • Você perde a segurança do tipo em tempo de compilação - é útil que o compilador verifique se um método está disponível em tempo de compilação. Se você estiver usando reflexão, receberá um erro em tempo de execução que poderá afetar os usuários finais se você não testar o suficiente. Mesmo se você detectar o erro, será mais difícil depurar.
  • Isso causa bugs ao refatorar - se você estiver acessando um membro com base em seu nome (por exemplo, usando uma string codificada), isso não será alterado pela maioria das ferramentas de refatoração de código e você terá instantaneamente um bug, o que pode ser bastante difícil de rastrear.
  • O desempenho é mais lento - a reflexão no tempo de execução será mais lenta do que as chamadas / pesquisas de método compiladas estaticamente. Se você está refletindo apenas ocasionalmente, isso não importa, mas isso pode se tornar um gargalo de desempenho nos casos em que você faz chamadas via reflexão milhares ou milhões de vezes por segundo. Uma vez, obtive uma aceleração de 10x em algum código Clojure simplesmente eliminando toda a reflexão, então sim, esse é um problema real.

Sugiro limitar o uso da reflexão aos seguintes casos:

  • Para prototipagem rápida ou código "descartável", quando é a solução mais simples
  • Para casos de uso de reflexão genuínos , por exemplo, ferramentas IDE que permitem ao usuário examinar os campos / métodos de um objeto arbitrário em tempo de execução.

Em todos os outros casos, sugiro descobrir uma abordagem que evite a reflexão. Definir uma interface com o (s) método (s) apropriado (s) e implementá-lo no conjunto de classes para o qual você deseja chamar o (s) método (s) geralmente é suficiente para resolver os casos mais simples.

Mikera
fonte
3
+1 - existem vários casos de uso para reflexão; mas na maioria das vezes vejo programadores usando-os, eles estão encobrindo projetos com falhas sérias. Defina interfaces e tente remover a dependência da reflexão. Assim como o GOTO, ele tem seus usos, mas a maioria deles não é boa. (Alguns deles são bons, é claro)
Billy ONeal
3
Lembre-se de que os pontos acima podem ou não se aplicar ao seu idioma favorito. Por exemplo, a reflexão não tem penalidade de velocidade no Smalltalk, nem você perde a segurança no tempo de compilação, porque o cálculo é totalmente atrasado.
precisa saber é o seguinte
O reflexo em D não tem desvantagem que você mencionou. Então, eu diria que você está culpando a implementação em alguns idiomas mais que a própria reeleição. Eu não sei muito sobre smalltalk, mas de acordo com @Frank Shearar, ele também não tem essas desvantagens.
deadalnix
2
@deadalinx: A que desvantagem você se refere? Existem vários nesta resposta. O problema de desempenho é a única coisa que depende do idioma.
Billy ONeal
11
Devo acrescentar que o segundo problema - erros causados ​​durante a refatoração - é provavelmente o mais sério. Em particular, confiar em algo com um nome específico é interrompido instantaneamente quando o nome da coisa muda. Os erros assim causados ​​podem ser menos informativos, a menos que você tenha muito cuidado.
Frank Shearar
11

A reflexão é apenas outra forma de metaprogramação e tão válida quanto os parâmetros baseados em tipo que você vê na maioria dos idiomas atualmente. A reflexão é poderosa e genérica, e os programas reflexivos são de alta ordem para manutenção (quando usados ​​corretamente, é claro) e muito mais do que programas puramente orientados a objetos ou procedimentais. Sim, você paga um preço de desempenho - mas eu ficaria feliz em optar por um programa mais lento que seja mais sustentável em muitos, ou mesmo na maioria dos casos.

DeadMG
fonte
2
+1, isso me ajuda a entender o que é reflexão . Eu sou um programador de C ++ e, portanto, nunca pensei muito. Isso ajuda. (Embora eu confesso que na minha perspectiva, Reflexão parece ser uma tentativa de conciliar uma deficiência na língua Nós caras C ++ usar modelos para isso Então, eu suponho que você poderia dizer o mesmo de que :)...
greyfade
4
Re-desempenho - em alguns casos, você pode usar a API de reflexão para gravar novamente o código de desempenho. Por exemplo, no .NET você pode escrever IL no aplicativo atual - portanto, seu código de "reflexão" pode ser tão rápido quanto qualquer outro código (exceto que você pode remover muitos if / else / qualquer coisa, como já fez descobri as regras exatas)
Marc Gravell
@greyfade: De certa forma, os modelos estão refletindo apenas em tempo de compilação. Ser capaz de fazê-lo em tempo de execução tem a vantagem de permitir que recursos poderosos sejam construídos no aplicativo sem precisar recompilar. Você troca desempenho por flexibilidade, uma troca aceitável na maior parte do tempo do que o esperado (considerando seu histórico de C ++).
Donal Fellows
Eu não entendo sua resposta. Você parece estar dizendo que os programas que usam reflexão são mais sustentáveis ​​do que aqueles que não usam , como se a reflexão fosse um modelo de programação superior que deveria suplantar programas orientados a objetos e de procedimentos, se o desempenho não fosse necessário.
24915 DavidS
8

Certamente tudo depende do que você está tentando alcançar.

Por exemplo, escrevi um aplicativo de verificação de mídia que usa injeção de dependência para determinar que tipo de mídia (arquivos MP3 ou JPEG) verificar. O shell precisava exibir uma grade contendo as informações pertinentes para cada tipo, mas não tinha conhecimento do que iria exibir. Isso é definido na montagem que lê esse tipo de mídia.

Portanto, tive que usar a reflexão para obter o número de colunas a serem exibidas e seus tipos e nomes para poder configurar a grade corretamente. Isso também significava que eu poderia atualizar a biblioteca injetada (ou criar uma nova) sem alterar nenhum outro código ou arquivo de configuração.

A única outra maneira seria ter um arquivo de configuração que precisaria ser atualizado quando eu alternasse o tipo de mídia que está sendo verificado. Isso teria introduzido outro ponto de falha para o aplicativo.

ChrisF
fonte
11
"Certamente tudo depende do que você está tentando alcançar." 1
DaveShaw 15/08
7

O Reflection é uma ferramenta incrível se você é um autor de biblioteca e, portanto, não tem influência sobre os dados recebidos. Uma combinação de reflexão e metaprogramação pode permitir que sua biblioteca funcione perfeitamente com chamadores arbitrários, sem que eles precisem passar por obstáculos de geração de código etc.

Eu tento desencorajar a reflexão no código do aplicativo ; na camada de aplicativo, você deve usar diferentes metáforas - interfaces, abstração, encapsulamento etc.

Marc Gravell
fonte
Tais bibliotecas geralmente são difíceis de usar e devem ser evitadas (por exemplo, Spring).
Sridhar Sarnobat
@ Sridhar, então você nunca usou nenhuma API de serialização? Ou qualquer ORM / micro-ORM?
Marc Gravell
Eu os usei sem escolha própria. Foi desmoralizante encontrar problemas que eu poderia evitar se não me dissessem o que fazer.
Sridhar Sarnobat
7

A reflexão é fantástica para criar ferramentas para desenvolvedores.

Como permite que seu ambiente de construção inspecione o código e gere potencialmente as ferramentas corretas para manipular / init inspeciona o código.

Como técnica de programação geral, pode ser útil, mas é mais frágil do que a maioria das pessoas imagina.

Um uso realmente de desenvolvimento para reflexão (IMO) é que a escrita de uma biblioteca de streaming genérica é muito simples (desde que a descrição da sua classe nunca seja alterada (ela se tornará uma solução muito quebradiça)).

Martin York
fonte
De fato, a primavera é "mais frágil do que a maioria das pessoas imagina".
Sridhar Sarnobat
5

O uso da reflexão é frequentemente prejudicial nas línguas OO, se não for usado com grande consciência.

Perdi a conta de quantas perguntas ruins já vi nos sites StackExchange onde

  1. O idioma em uso é OO
  2. O autor deseja descobrir em que tipo de objeto foi passado e, em seguida, chamar um de seus métodos
  3. O autor se recusa a aceitar que a reflexão é a técnica errada e acusa qualquer um que apontar isso como "sem saber como me ajudar".

Aqui estão exemplos típicos.

A maior parte do objetivo do OO é que

  1. Você agrupa funções que sabem como manipular uma estrutura / conceito com a coisa em si.
  2. Em qualquer ponto do seu código, você deve saber o suficiente sobre o tipo de um objeto para saber quais funções relevantes ele tem e solicitar que chame sua versão específica dessas funções.
  3. Você garante a verdade do ponto anterior especificando o tipo de entrada correto para cada função e fazendo com que cada função não saiba mais (e não faça mais) do que é relevante.

Se, em qualquer ponto do seu código, o ponto 2 não for válido para um objeto ao qual você passou, um ou mais deles será verdadeiro

  1. A entrada incorreta está sendo alimentada
  2. Seu código está mal estruturado
  3. Você não tem ideia do que diabos está fazendo.

Desenvolvedores pouco qualificados simplesmente não entendem isso e acreditam que podem receber qualquer coisa em qualquer parte de seu código e fazem o que desejam de um conjunto de possibilidades (codificado). Esses idiotas usam muito a reflexão .

Para linguagens OO, a reflexão deve ser necessária apenas na meta atividade (carregadores de classes, injeção de dependência, etc.). Nesses contextos, a reflexão é necessária porque você está fornecendo um serviço genérico para auxiliar na manipulação / configuração do código sobre o qual você não sabe nada por um motivo bom e legítimo. Em quase qualquer outra situação, se você está buscando reflexão, está fazendo algo errado e precisa se perguntar por que esse pedaço de código não sabe o suficiente sobre o objeto que foi passado para ele.

itsbruce
fonte
3

Uma alternativa, nos casos em que o domínio das classes refletidas é bem definido, é usar a reflexão junto com outros metadados para gerar código , em vez de usar a reflexão no tempo de execução. Eu faço isso usando o FreeMarker / FMPP; existem muitas outras ferramentas para você escolher. A vantagem disso é que você acaba com um código "real" que pode ser facilmente depurado etc.

Dependendo da situação, isso pode ajudá-lo a tornar um código muito mais rápido - ou apenas um monte de código inchar. Evita as desvantagens da reflexão:

  • perda de segurança do tipo tempo de compilação
  • erros devido à refatoração
  • desempenho mais lento

mencionado anteriormente.

Se a reflexão parecer trapaça, pode ser porque você a baseia em muitas suposições, das quais não tem certeza, e seu intestino está avisando que isso é arriscado. Certifique-se de fornecer uma maneira de aprimorar os metadados inerentes à reflexão com seus próprios metadados, onde você pode descrever todas as peculiaridades e casos especiais das classes do mundo real que você pode encontrar.

Ed Staub
fonte
2

Não é trapaça, mas, como qualquer ferramenta, deve ser usada para o que se destina a resolver. Reflexão, por definição, permite que você inspecione e modifique o código através do código; se é isso que você precisa fazer, a reflexão é a ferramenta para o trabalho. Reflexão é tudo sobre meta-código: código que segmenta código (em oposição ao código regular, que segmenta dados).

Um exemplo de bom uso de reflexão são as classes genéricas da interface de serviço da web: Um design típico é separar a implementação do protocolo da funcionalidade da carga útil. Então você tem uma classe (vamos chamá-la T) que implementa sua carga útil e outra que implementa o protocolo ( P). Té bastante direto: para cada chamada que você deseja fazer, basta escrever um método que faça o que deve ser feito. P, no entanto, precisa mapear chamadas de serviço da web para chamadas de método. Tornar esse mapeamento genérico é desejável, pois evita redundância e torna Paltamente reutilizável. O Reflection fornece os meios para inspecionar a classe Tem tempo de execução e chamar seus métodos com base nas seqüências de caracteres transmitidas Ppelo protocolo de serviço da web, sem nenhum conhecimento da classe em tempo de compilaçãoT. Usando a regra 'código sobre código', pode-se argumentar que a classe Ppossui o código na classe Tcomo parte de seus dados.

Contudo.

O Reflection também oferece ferramentas para contornar as restrições do sistema de tipos da linguagem - teoricamente, você pode passar todos os parâmetros como tipo objecte chamar seus métodos através de reflexões. Voilà, a linguagem que deveria impor uma forte disciplina estática de digitação agora se comporta como uma linguagem dinamicamente digitada com ligação tardia, apenas que a sintaxe é muito mais elaborada. Todas as instâncias desse padrão que eu vi até agora foram um hack sujo e, invariavelmente, uma solução dentro do sistema de tipos da linguagem seria possível e seria mais segura, elegante e eficiente em todos os aspectos .

Existem algumas exceções, como controles da GUI que podem ser vinculados a dados para vários tipos não relacionados de fontes de dados; determinar que seus dados implementem uma determinada interface apenas para que você possa vinculá-los a dados não é realista e o programador não deve implementar um adaptador para cada tipo de fonte de dados. Nesse caso, usar a reflexão para detectar o tipo de fonte de dados e ajustar a ligação de dados é uma opção mais útil.

tdammers
fonte
2

Um problema que tivemos com o Reflection foi quando adicionamos Ofuscação à mistura. Todas as classes recebem novos nomes e o carregamento repentino de uma classe ou função por seu nome para de funcionar.

Carra
fonte
1

Depende totalmente. Um exemplo de algo que seria difícil de fazer sem reflexão seria replicar ObjectListView . Também gera código IL em tempo real.

Tangurena
fonte
1

A reflexão é o principal método de criação de sistemas baseados em convenções. Eu não ficaria surpreso ao descobrir que ele está sendo muito utilizado na maioria das estruturas MVC. É um componente importante nos ORMs. Provavelmente, você já está usando componentes criados com ele todos os dias.

A alternativa para esse uso é a configuração, que possui seu próprio conjunto de desvantagens.

Chris McCall
fonte
0

A reflexão pode alcançar coisas que simplesmente não podem ser feitas praticamente de outra maneira.

Por exemplo, considere como otimizar este código:

int PoorHash(char Operator, int seed, IEnumerable<int> values) {
    foreach (var v in values) {
        seed += 1;
        switch (char) {
            case '+': seed += v; break;
            case '^': seed ^= v; break;
            case '-': seed -= v; break;
            ...
        }
        seed *= 3;
    }
    return seed;
}

Há um teste caro no meio do loop interno, mas extraí-lo requer que você reescreva o loop uma vez para cada operador. A reflexão nos permite obter desempenho igual ao extrair esse teste, sem repetir o loop uma dúzia de vezes (e, assim, sacrificar a manutenção). Basta gerar e compilar o loop que você precisa em tempo real.

Na verdade, eu fiz essa otimização , embora a situação fosse um pouco mais complicada e os resultados foram surpreendentes. Uma melhoria de ordem de magnitude no desempenho e menos linhas de código.

(Nota: inicialmente, tentei o equivalente a passar um Func em vez de um caractere, e foi um pouco melhor, mas não quase o reflexo de 10x obtido.)

Craig Gidney
fonte
11
A melhor otimização é passar um functor. Provavelmente será incorporado no tempo de execução pelo compilador JIT e é mais sustentável do que reflexão.
Billy ONeal
Isso seria mais sustentável, e eu realmente tentei primeiro, mas não foi rápido o suficiente. O JIT definitivamente não estava incluído, provavelmente porque, no meu caso, havia um segundo loop interno sobre as operações a serem executadas.
Craig Gidney
Além disso, aponto que o que você tem lá é realmente um código auto-modificável, que não é realmente um reflexo. Um acessa-lo através de APIs de reflexão na maioria das línguas que fazem suporte reflexão, mas isso pode ser feito apenas como simplesmente em idiomas que não suportem reflexão (por exemplo, seria possível em C ++)
Billy ONeal
Eu queria evitar o exemplo padrão de 'igualdade estrutural'. A reflexão não é necessária para o código modificado automaticamente, mas certamente ajuda.
Craig Gidney
Na verdade não. Você pode fazer isso em linguagens de não reflexão muito bem.
Billy ONeal
-2

De jeito nenhum ele está trapaceando ... Em vez disso, ele permite que os servidores de aplicativos executem as classes criadas pelo usuário com o nome de sua escolha, fornecendo flexibilidade ao usuário e não trapaceando.

E se você quiser ver o código de qualquer arquivo .class (em Java), existem vários descompiladores disponíveis gratuitamente!

ANSHUL JAIN
fonte
A descompilação não é um recurso de reflexão.
Billy ONeal
sim, não é ... Mas eu quis dizer que se você sentir vontade de trapacear enquanto usa reflexão (o que fornece detalhes de qualquer classe em java), então você está errado bcoz reflexão é um conceito muito útil e se você obtém detalhes de qualquer classe é o que é que lhe dizem respeito do que pode ser feito facilmente com qualquer decompiler ...
Anshu Jain
2
Reflexão é um conceito útil em alguns casos, sim. Mas 99,9% das vezes eu vejo isso sendo usado, eu vejo isso ser usado para solucionar um erro de design.
Billy ONeal