Existem problemas com o uso do Reflection?

49

Eu não sei porque, mas eu sempre sinto que estou "trapaceando" quando uso a reflexão - talvez seja por causa do desempenho que eu sei que estou tomando.

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

Quais são os possíveis problemas que eu preciso observar ao usar a reflexão e como eu deveria estar preocupado com eles? Quanto esforço vale a pena gastar para tentar encontrar uma solução mais convencional?

    
por P B 15.08.2011 / 16:11
fonte

14 respostas

47

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, simples e 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 do 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 a reflexão para corresponder ao elemento XML denominado fieldX ao campo apropriado na classe e para inicializar o último. Em alguns casos, ele pode criar uma caixa de diálogo simples de GUI a partir das propriedades identificadas em tempo real. Sem reflexão, isso levaria centenas de linhas de código em vários aplicativos. Assim, a reflexão nos ajudou a criar rapidamente uma ferramenta simples, sem muita dificuldade, e nos permitiu focar na parte importante (teste de regressão de nosso aplicativo da web, análise de logs do servidor, etc.) em vez de irrelevantes.
  • um módulo de nosso aplicativo da web legado era destinado a exportar / importar dados de tabelas de banco de dados para planilhas do Excel e vice-versa. Ele continha um monte de código duplicado, onde é claro que as duplicações não eram exatamente as mesmas, algumas 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 abaixo de 2.4K, enquanto torna o código mais robusto e 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 de fundo é, como qualquer ferramenta poderosa, o reflexo também pode ser usado para se atirar no pé. Se você aprender quando e como (não) usá-lo, ele poderá oferecer soluções elegantes e limpas para problemas difíceis. Se você abusar, você pode transformar um problema simples em uma bagunça complexa e feia.

    
por 15.08.2011 / 16:20
fonte
34

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, você receberá um erro em tempo de execução que pode afetar os usuários finais se você não testar bem o suficiente. Mesmo se você detectar o erro, será mais difícil depurá-lo.
  • 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, que pode ser muito difícil de rastrear.
  • O desempenho é mais lento - a reflexão em tempo de execução será mais lenta do que as chamadas de método / variáveis compiladas estaticamente. Se você só estiver refletindo ocasionalmente, isso não será importante, mas isso pode se tornar um gargalo de desempenho nos casos em que você está fazendo chamadas por meio de reflexões, milhares ou milhões de vezes por segundo. Uma vez eu consegui uma aceleração de 10x em algum código Clojure simplesmente eliminando toda a reflexão, então sim, este é um problema real.

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

  • Para o código de prototipagem rápida ou "descartável" quando é a solução mais simples
  • Para casos de uso de reflexão real , por exemplo Ferramentas IDE que permitem ao usuário examinar os campos / métodos de um objeto arbitrário durante a 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 as quais deseja chamar o (s) método (s) geralmente é suficiente para resolver casos mais simples.

    
por 15.08.2011 / 17:29
fonte
11

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

    
por 15.08.2011 / 16:21
fonte
8

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

Por exemplo, eu escrevi um aplicativo verificador de mídia que usa injeção de dependência para determinar que tipo de mídia (arquivos MP3 ou arquivos JPEG) deve ser verificado. O shell precisava exibir uma grade contendo as informações pertinentes para cada tipo, mas não tinha conhecimento do qual ele seria exibido. Isso é definido no assembly que lê esse tipo de mídia.

Portanto, eu tive que usar a reflexão para obter o número de colunas para exibir e seus tipos e nomes para que eu pudesse configurar a grade corretamente. Isso também significa 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 mudei o tipo de mídia que está sendo verificado. Isso teria introduzido outro ponto de falha para o aplicativo.

    
por 15.08.2011 / 16:17
fonte
7

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

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

    
por 15.08.2011 / 20:43
fonte
6

O Reflection é fantástico para criar ferramentas para desenvolvedores.

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

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

Um realmente desenvolvimento para reflexão (IMO) é que torna a escrita de uma biblioteca de streaming genérica muito simples (desde que a descrição da sua classe nunca mude (então ela se torna uma solução muito frágil)).

    
por 15.08.2011 / 16:45
fonte
5

O uso da reflexão é muitas vezes prejudicial em idiomas OO, se não for usado com grande consciência.

Perdi a conta de quantas perguntas ruins eu tenho visto nos sites do StackExchange, onde

  1. O idioma em uso é OO
  2. O autor quer descobrir qual 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 aponta isso como "não saber como me ajudar"

Aqui são exemplos típicos.

A maior parte do ponto de OO é que

  1. Você agrupa funções que sabem como manipular uma estrutura / conceito com a própria coisa.
  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 possui e solicitar que ele chame sua versão particular 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 algum ponto do seu código, o ponto 2 não for válido para um objeto que você recebeu, então um ou mais deles é verdadeiro

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

Desenvolvedores mal qualificados simplesmente não entendem isso e acreditam que podem ser passados em qualquer parte do código e fazer o que querem de um conjunto de possibilidades (codificadas). Esses idiotas usam a reflexão muito .

Para linguagens OO, a reflexão só deve ser necessária 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 a manipulação / configuração de código sobre o qual você não sabe nada por uma boa e legítima razão. Em quase todas as outras situações, se você está buscando reflexão, entã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.

    
por 29.01.2015 / 18:59
fonte
3

Uma alternativa, nos casos em que o domínio das classes refletidas é bem definido, é usar reflexão junto com outros metadados para gerar código , em vez de usar reflexão em tempo de execução. Eu faço isso usando o FreeMarker / FMPP; Existem muitas outras ferramentas para 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 criar um código muito mais rápido - ou apenas um monte de código inchado. Evita as desvantagens da reflexão:

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

mencionado anteriormente.

Se a reflexão parecer trapaça, pode ser porque você está baseando-se em muitas adivinhações das quais não tem certeza, e seu instinto está avisando que isso é arriscado. Certifique-se de fornecer uma maneira de melhorar 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.

    
por 16.08.2011 / 02:19
fonte
2

Não é trapaça, mas como qualquer ferramenta, deve ser usada para o que se pretende resolver. Reflexão, por definição, permite inspecionar e modificar o código por meio de código; Se isso é o que você precisa fazer, então a reflexão é a ferramenta para o trabalho. Reflexão é tudo sobre meta-código: Código que visa o código (em oposição ao código regular, que tem como alvo os dados).

Um exemplo de uso de boa reflexão são classes de interface de serviço da Web genéricas: Um design típico consiste em separar a implementação de protocolo da funcionalidade de carga útil. Então você tem uma classe (vamos chamá-la de T ) que implementa sua carga e outra que implementa o protocolo ( P ). T é bastante simples: para cada chamada que você deseja fazer, basta escrever um método que faça o que for necessário. O P , no entanto, precisa mapear as chamadas de serviço da web para chamadas de método. Tornar este mapeamento genérico é desejável, porque evita a redundância e torna P altamente reutilizável. O Reflection fornece os meios para inspecionar a classe T no tempo de execução e chamar seus métodos com base em cadeias passadas para P através do protocolo de serviço da web, sem nenhum conhecimento em tempo de compilação da classe T . Usando a regra 'código sobre código', pode-se argumentar que a classe P tem o código na classe T como parte de seus dados.

No entanto,

O Reflection também oferece ferramentas para contornar as restrições do sistema de tipos da linguagem - teoricamente, você poderia passar todos os parâmetros como tipo object e chamar seus métodos por meio de reflexões. Voilà, a linguagem que supostamente reforça a disciplina de tipagem estática, agora se comporta como uma linguagem tipada dinamicamente com ligação tardia, apenas que a sintaxe é muito mais elaborada. Cada instância de tal padrão que vi até agora tem sido um truque sujo e, invariavelmente, uma solução dentro do sistema de tipos da linguagem teria sido possível, e teria sido mais segura, elegante e eficiente em todos os aspectos. .

Existem algumas exceções, como controles GUI que podem ser ligados a dados a vários tipos de fontes de dados não relacionados; Mandar que seus dados implementem uma determinada interface apenas para que você possa vincular dados não é realista, e também não é necessário que o programador implemente 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.

    
por 15.08.2011 / 23:27
fonte
2

Um problema que tivemos com o Reflection foi quando adicionamos Obfuscation ao mix. Todas as classes obtêm novos nomes e, de repente, o carregamento de uma classe ou função pelo nome deixa de funcionar.

    
por 16.08.2011 / 09:49
fonte
1

Depende totalmente. Um exemplo de algo que seria difícil de fazer sem refletir seria replicar ObjectListView . Ele também gera código IL em tempo real.

    
por 15.08.2011 / 16:32
fonte
1

A reflexão é o principal método de criar sistemas baseados em convenções. Eu não ficaria surpreso ao descobrir que ele está em uso pesadamente dentro da maioria dos frameworks MVC. É um componente importante nos ORMs. É provável que você já esteja usando componentes criados com ele todos os dias.

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

    
por 29.01.2015 / 21:31
fonte
0

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

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 exige a reconfiguração do loop uma vez para cada operador. O Reflection nos permite obter desempenho equivalente à extração desse teste, sem repetir o loop uma dúzia de vezes (e, portanto, sacrificar a capacidade de manutenção). Apenas gere e compile o loop que você precisa na hora.

Eu tenho realmente feito essa otimização , embora tenha sido um Um pouco mais complicado de uma situação, e os resultados foram surpreendentes. Uma ordem de magnitude de melhoria no desempenho e menos linhas de código.

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

    
por 15.08.2011 / 18:22
fonte
-2

Não há como trapacear ... Em vez disso, ele permite que os servidores de aplicativos executem as classes feitas pelo usuário com o nome de sua escolha, fornecendo flexibilidade ao usuário e não trapaceando-o.

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

    
por 15.08.2011 / 18:05
fonte