¿Hay problemas con el uso de Reflection?

49

No sé por qué, pero siempre siento que estoy "haciendo trampa" cuando uso la reflexión, tal vez es por el éxito en el rendimiento que sé que estoy tomando.

Una parte de mí dice que si es parte del lenguaje que está usando y puede lograr lo que está tratando de hacer, entonces, ¿por qué no usarlo? La otra parte de mí dice que tiene que haber una manera de hacerlo sin usar la reflexión. Supongo que tal vez depende de la situación.

¿Cuáles son los posibles problemas que debo tener en cuenta al usar la reflexión y qué tan preocupado debería estar por ellos? ¿Cuánto esfuerzo vale la pena gastar para tratar de encontrar una solución más convencional?

PB
fuente
2
Supongo que depende del idioma y el entorno. Algunos lo apoyan, incluso lo alientan. A veces, cuando trabajo en Java, he deseado un mejor soporte de reflexión.
FrustratedWithFormsDesigner
18
Bueno, supongo que no es tan difícil disimular entre la "reflexión de trampa" y un uso válido de la misma: cuando inspeccionas a miembros privados o la usas para llamar a métodos privados (para evitar interfaces), es muy probable que sea una trampa. Cuando lo usas para interactuar con tipos de los que no tienes conocimiento, probablemente esté bien (enlace de datos, proxies, etc.).
Falcon
1
Qué idioma estás usando? Brainfuck no tiene reflejo y Python es simplemente diferente.
Trabajo
1
Tampoco entendí por qué está permitido acceder a métodos privados a través de la reflexión. Según mi intuición, debería estar prohibido.
Giorgio
3
Es difícil de creer que una pregunta tan mal redactada obtuvo 27 votos a favor sin ninguna edición.
Aaronaught

Respuestas:

48

No, no es trampa: es una forma de resolver problemas en algunos lenguajes de programación.

Ahora, a menudo no es la mejor solución (más limpia, más simple, más fácil de mantener). Si hay una mejor manera, use esa de hecho. Sin embargo, a veces no lo hay. O si lo hay, es mucho más complejo, implica una gran cantidad de duplicación de código, etc., lo que lo hace inviable (difícil de mantener a largo plazo).

Dos ejemplos de nuestro proyecto actual (Java):

  • Algunas de nuestras herramientas de prueba utilizan la reflexión para cargar la configuración desde archivos XML. La clase que se inicializará tiene campos específicos, y el cargador de configuración utiliza la reflexión para hacer coincidir el elemento XML nombrado fieldXcon el campo apropiado en la clase y para inicializar el último. En algunos casos, puede crear un cuadro de diálogo GUI simple a partir de las propiedades identificadas sobre la marcha. Sin reflexionar, esto tomaría cientos de líneas de código en varias aplicaciones. Así que la reflexión nos ayudó a armar una herramienta simple rápidamente, sin mucho alboroto, y nos permitió centrarnos en la parte importante (pruebas de regresión de nuestra aplicación web, análisis de registros del servidor, etc.) en lugar de lo irrelevante.
  • Un módulo de nuestra aplicación web heredada estaba destinado a exportar / importar datos de tablas DB a hojas Excel y viceversa. Contenía una gran cantidad de código duplicado, donde, por supuesto, las duplicaciones no eran exactamente las mismas, algunas contenían errores, etc. Utilizando la reflexión, la introspección y las anotaciones, logré eliminar la mayor parte de la duplicación, reduciendo la cantidad de código. 5K a menos de 2.4K, mientras hace que el código sea más robusto y mucho más fácil de mantener o extender. Ahora ese módulo dejó de ser un problema para nosotros, gracias al uso juicioso de la reflexión.

La conclusión es, como cualquier herramienta poderosa, la reflexión también se puede utilizar para dispararse en el pie. Si aprende cuándo y cómo (no) usarlo, puede brindarle soluciones elegantes y limpias para problemas difíciles. Si abusa de él, puede convertir un problema simple en un desastre complejo y feo.

Péter Török
fuente
8
+1 Reflection es increíblemente útil para importar / exportar datos cuando tienes objetos intermedios por razones de conversión.
Ed James
2
También es increíblemente útil en aplicaciones basadas en datos: en lugar de usar una pila masiva de if (propName = n) setN(propValue);, puede nombrar sus (es decir) etiquetas XML de la misma manera que sus propiedades de código y ejecutar un ciclo sobre ellas. Este método también hace que sea mucho más simple agregar propiedades más adelante.
Michael K
La reflexión se usa mucho en las interfaces de Fluent, como FluentNHibernate.
Scott Whitlock
44
@ Michael: Hasta que decidas cambiar el nombre de una de esas propiedades en la clase y descubras que tu código explota. Si no necesita mantener la comparabilidad con las cosas que genera su programa, está bien, pero sospecho que no somos la mayoría de nosotros.
Billy ONeal
1
@ Billy, no quise contradecirte, estoy de acuerdo con lo que escribiste. Aunque, el ejemplo que traes es, en mi humilde opinión, más una subcase de la regla general "evitar cambiar las interfaces públicas".
Péter Török
37

No es trampa. Pero generalmente es una mala idea en el código de producción al menos por las siguientes razones:

  • Pierde la seguridad del tipo de tiempo de compilación : es útil que el compilador verifique que haya un método disponible en el momento de la compilación. Si está utilizando la reflexión, obtendrá un error en tiempo de ejecución que podría afectar a los usuarios finales si no prueba lo suficientemente bien. Incluso si detecta el error, será más difícil de depurar.
  • Causa errores al refactorizar : si está accediendo a un miembro en función de su nombre (por ejemplo, utilizando una cadena codificada), la mayoría de las herramientas de refactorización de código no lo cambiarán e instantáneamente tendrá un error, que podría ser bastante Difícil de rastrear.
  • El rendimiento es más lento : la reflexión en tiempo de ejecución será más lenta que las llamadas a métodos compilados estáticamente / búsquedas variables. Si solo está haciendo reflexiones ocasionalmente, no importará, pero esto puede convertirse en un cuello de botella en el rendimiento en los casos en que realiza llamadas a través de la reflexión miles o millones de veces por segundo. Una vez obtuve una aceleración de 10x en algún código de Clojure simplemente eliminando toda reflexión, así que sí, este es un problema real.

Sugeriría limitar el uso de la reflexión a los siguientes casos:

  • Para prototipos rápidos o código "desechable" cuando es la solución más simple
  • Para casos de uso de reflexión genuinos , por ejemplo, herramientas IDE que permiten a un usuario examinar los campos / métodos de un objeto arbitrario en tiempo de ejecución.

En todos los demás casos, sugeriría encontrar un enfoque que evite la reflexión. Definir una interfaz con el (los) método (s) apropiado (s) e implementarlo en el conjunto de clases a las que desea llamar el (los) método (s) suele ser suficiente para resolver la mayoría de los casos simples.

mikera
fuente
3
+1: hay varios casos de uso para la reflexión; pero la mayoría de las veces veo que los programadores que lo usan están empapelando diseños con serias fallas. Defina interfaces e intente eliminar la dependencia de la reflexión. Al igual que GOTO, tiene sus usos, pero la mayoría de ellos no son buenos. (Algunos de ellos son buenos, por supuesto)
Billy ONeal
3
Tenga en cuenta que los puntos anteriores pueden aplicarse o no a su idioma favorito. Por ejemplo, la reflexión no tiene penalización de velocidad en Smalltalk, ni pierde la seguridad en tiempo de compilación, porque el cómputo está totalmente atrasado.
Frank Shearar
La reflexión en D no tiene el inconveniente que mencionas. Así que diría que está culpando a la implementación en algunos idiomas más que a la reflexión en sí misma. No sé mucho sobre smalltalk, pero según @Frank Shearar, tampoco tiene ese inconveniente.
deadalnix
2
@deadalinx: ¿A qué inconveniente te refieres? Hay varios en esta respuesta. El problema de rendimiento es lo único que depende del idioma.
Billy ONeal
1
Debo agregar que el segundo problema, los errores causados ​​durante la refactorización, es probablemente el más grave. En particular, confiar en algo que tiene un nombre particular se rompe instantáneamente cuando cambia el nombre de esa cosa. Los errores causados ​​pueden ser menos informativos, a menos que tenga mucho cuidado.
Frank Shearar
11

La reflexión es solo otra forma de metaprogramación, y tan válida como los parámetros basados ​​en tipos que se ven en la mayoría de los idiomas en estos días. La reflexión es poderosa y genérica, y los programas reflexivos son de alto orden de mantenibilidad (cuando se usan correctamente, por supuesto) y más que los programas puramente orientados a objetos o procedimientos. Sí, pagas un precio de rendimiento, pero con gusto elegiría un programa más lento que sea más fácil de mantener en muchos, o incluso en la mayoría de los casos.

DeadMG
fuente
2
+1, esto me ayuda a entender qué es la reflexión . Soy un programador de C ++, por lo que nunca he asimilado la reflexión. Esto ayuda. (Aunque confieso que desde mi perspectiva, Reflection parece un intento de conciliar una deficiencia en el lenguaje. Nosotros, los chicos de C ++, usamos plantillas para eso. Así que supongo que se podría decir lo mismo de eso. :)
greyfade
44
Rendimiento: en algunos casos, puede usar la API de reflexión para escribir completamente el código de rendimiento. Por ejemplo, en .NET puede escribir IL en la aplicación actual, por lo tanto, su código de "reflexión" puede ser tan rápido como cualquier otro código (excepto que puede eliminar una gran cantidad de if / else / whatever, como ya lo ha hecho) descubrió las reglas exactas)
Marc Gravell
@greyfade: En cierto sentido, las plantillas están haciendo una reflexión solo en tiempo de compilación. Poder hacerlo en tiempo de ejecución tiene la ventaja de permitir la creación de capacidades potentes en la aplicación sin tener que volver a compilar. Cambias el rendimiento por flexibilidad, una compensación aceptable más de lo que esperas (dada tu experiencia en C ++).
Donal Fellows
No entiendo tu respuesta. Parece estar diciendo que los programas que usan la reflexión son más fáciles de mantener que los que no lo hacen, como si la reflexión fuera un modelo de programación superior que debería suplantar a los programas orientados a objetos y a los procedimientos si no se necesita rendimiento.
DavidS
8

Seguramente todo depende de lo que intentes lograr.

Por ejemplo, escribí una aplicación de verificación de medios que utiliza la inyección de dependencia para determinar qué tipo de medios (archivos MP3 o JPEG) verificar. El shell necesitaba mostrar una cuadrícula que contenía la información pertinente para cada tipo, pero no tenía conocimiento de lo que iba a mostrar. Esto se define en el ensamblado que lee ese tipo de medios.

Por lo tanto, tuve que usar la reflexión para obtener el número de columnas para mostrar y sus tipos y nombres para poder configurar la cuadrícula correctamente. También significaba que podía actualizar la biblioteca inyectada (o crear una nueva) sin cambiar ningún otro código o archivo de configuración.

La única otra forma habría sido tener un archivo de configuración que debería actualizarse cuando cambié el tipo de medio que se verifica. Esto habría introducido otro punto de falla para la aplicación.

ChrisF
fuente
1
"Seguramente todo depende de lo que intentes lograr". +1
DaveShaw
7

Reflection es una herramienta increíble si eres autor de una biblioteca y, por lo tanto, no tienes influencia sobre los datos entrantes. Una combinación de reflexión y metaprogramación puede permitir que su biblioteca funcione a la perfección con llamadores arbitrarios, sin que tengan que saltar a través de aros de generación de código, etc.

Sin embargo, intento disuadir de la reflexión en el código de la aplicación ; en la capa de la aplicación deberías usar diferentes metáforas: interfaces, abstracción, encapsulación, etc.

Marc Gravell
fuente
Tales bibliotecas son a menudo difíciles de usar y es mejor evitarlas (por ejemplo, Spring).
Sridhar Sarnobat
@Sridhar, ¿entonces nunca ha usado ninguna API de serialización? O cualquier ORM / micro-ORM?
Marc Gravell
Los he usado sin elección propia. Ha sido desmoralizante encontrarse con problemas que podría haber evitado si no me hubieran dicho qué hacer.
Sridhar Sarnobat
7

La reflexión es fantástica para crear herramientas para desarrolladores.

Como permite que su entorno de compilación inspeccione el código y genere potencialmente las herramientas correctas para manipular / iniciar la inspección del código.

Como técnica de programación general, puede ser útil, pero es más frágil de lo que la mayoría de la gente imagina.

Un uso realmente desarrollado para la reflexión (IMO) es que hace que escribir una biblioteca genérica de transmisión sea muy simple (siempre y cuando la descripción de su clase nunca cambie (entonces se convierte en una solución muy frágil)).

Martin York
fuente
De hecho, la primavera es "más frágil de lo que la mayoría de la gente imagina".
Sridhar Sarnobat
5

El uso de la reflexión a menudo es perjudicial en los idiomas OO si no se usa con gran conocimiento.

He perdido la cuenta de cuántas preguntas malas he visto en los sitios de StackExchange donde

  1. El idioma en uso es OO
  2. El autor quiere averiguar qué tipo de objeto se ha pasado y luego llamar a uno de sus métodos
  3. El autor se niega a aceptar que la reflexión sea una técnica incorrecta y acusa a cualquiera que lo señale como "no saber cómo ayudarme".

Aquí hay ejemplos típicos.

La mayor parte del punto de OO es que

  1. Agrupa funciones que saben manipular una estructura / concepto con la cosa misma.
  2. En cualquier punto de su código, debe saber lo suficiente sobre el tipo de un objeto para saber qué funciones relevantes tiene y pedirle que llame a su versión particular de esas funciones.
  3. Usted asegura la verdad del punto anterior especificando el tipo de entrada correcto para cada función y haciendo que cada función no sepa más (y no haga más) de lo que es relevante.

Si en algún punto de su código, el punto 2 no es válido para un objeto que le ha pasado, entonces uno o más de estos es verdadero

  1. Se está introduciendo la entrada incorrecta
  2. Tu código está mal estructurado
  3. No tienes idea de qué demonios estás haciendo.

Los desarrolladores poco calificados simplemente no entienden esto y creen que se les puede pasar cualquier cosa en cualquier parte de su código y hacer lo que quieran de un conjunto de posibilidades (codificadas). Estos idiotas usan mucho la reflexión .

Para los lenguajes OO, la reflexión solo debería ser necesaria en la metaactividad (cargadores de clases, inyección de dependencias, etc.). En esos contextos, la reflexión es necesaria porque está proporcionando un servicio genérico para ayudar a la manipulación / configuración del código del cual no sabe nada por una razón buena y legítima. En casi cualquier otra situación, si está buscando la reflexión, entonces está haciendo algo mal y debe preguntarse por qué este código no sabe lo suficiente sobre el objeto que se le ha pasado.

itsbruce
fuente
3

Una alternativa, en los casos en que el dominio de las clases reflejadas está bien definido, es usar la reflexión junto con otros metadatos para generar código , en lugar de usar la reflexión en tiempo de ejecución. Hago esto usando FreeMarker / FMPP; Hay muchas otras herramientas para elegir. La ventaja de esto es que terminas con un código "real" que se puede depurar fácilmente, etc.

Dependiendo de la situación, esto puede ayudarlo a hacer un código mucho más rápido, o simplemente una gran cantidad de código. Evita las desventajas de la reflexión:

  • Pérdida de seguridad del tipo de tiempo de compilación
  • errores debidos a refactorización
  • rendimiento más lento

mencionado anteriormente.

Si la reflexión se siente como hacer trampa, puede ser porque la estás basando en muchas conjeturas de las que no estás seguro, y tu instinto te advierte que esto es arriesgado. Asegúrese de proporcionar una manera de mejorar los metadatos inherentes a la reflexión con sus propios metadatos, donde puede describir todas las peculiaridades y casos especiales de las clases del mundo real que pueda encontrar.

Ed Staub
fuente
2

No es trampa, pero como cualquier herramienta, debe usarse para lo que se pretende resolver. La reflexión, por definición, le permite inspeccionar y modificar el código a través del código; Si eso es lo que necesita hacer, entonces la reflexión es la herramienta para el trabajo. La reflexión tiene que ver con el metacódigo: el código que se dirige al código (a diferencia del código regular, que se dirige a los datos).

Un ejemplo de buen uso de la reflexión son las clases de interfaz de servicio web genéricas: un diseño típico es separar la implementación del protocolo de la funcionalidad de la carga útil. Entonces tienes una clase (llamémosla T) que implementa tu carga útil, y otra que implementa el protocolo ( P). Tes bastante sencillo: para cada llamada que desee hacer, simplemente escriba un método que haga lo que se supone que debe hacer. P, sin embargo, necesita asignar llamadas de servicio web a llamadas de método. Hacer que este mapeo sea genérico es deseable, ya que evita la redundancia y lo hace Paltamente reutilizable. Reflection proporciona los medios para inspeccionar la clase Ten tiempo de ejecución y llamar a sus métodos basados ​​en cadenas pasadas a Ptravés del protocolo de servicio web, sin ningún conocimiento de la clase en tiempo de compilaciónT. Usando la regla 'código sobre código', uno puede argumentar que la clase Ptiene el código en clase Tcomo parte de sus datos.

Sin embargo.

Reflection también le brinda herramientas para sortear las restricciones del sistema de tipos del lenguaje: en teoría, podría pasar todos los parámetros como tipo objecty llamar a sus métodos a través de reflexiones. Voilà, el lenguaje que se supone que impone una fuerte disciplina de tipeo estático ahora se comporta como un lenguaje tipado dinámicamente con enlace tardío, solo que la sintaxis es mucho más elaborada. Cada instancia de ese patrón que he visto hasta ahora ha sido un truco sucio, e invariablemente, una solución dentro del sistema de tipos de lenguaje habría sido posible, y habría sido más seguro, más elegante y más eficiente en todos los aspectos. .

Existen algunas excepciones, como los controles de la GUI que pueden vincularse a varios tipos de fuentes de datos no relacionadas; exigir que sus datos implementen una determinada interfaz para que pueda vincular los datos no es realista, y tampoco es que el programador implemente un adaptador para cada tipo de fuente de datos. En este caso, usar la reflexión para detectar el tipo de fuente de datos y ajustar el enlace de datos es una opción más útil.

tdammers
fuente
2

Un problema que hemos tenido con Reflection es cuando agregamos Ofuscación a la mezcla. Todas las clases obtienen nuevos nombres y, de repente, cargar una clase o función por su nombre deja de funcionar.

Carra
fuente
1

Depende totalmente. Un ejemplo de algo que sería difícil de hacer sin reflexión sería replicar ObjectListView . También genera código IL sobre la marcha.

Tangurena
fuente
1

La reflexión es el método principal para crear sistemas basados ​​en convenciones. No me sorprendería descubrir que se usa mucho en la mayoría de los marcos MVC. Es un componente importante en los ORM. Lo más probable es que ya estés usando componentes creados con él todos los días.

La alternativa para tal uso es la configuración, que tiene su propio conjunto de inconvenientes.

Chris McCall
fuente
0

La reflexión puede lograr cosas que simplemente no se pueden hacer prácticamente de otra manera.

Por ejemplo, considere cómo optimizar 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;
}

Hay un golpe de prueba costoso en el medio del bucle interno, pero extraerlo requiere reescribir el bucle una vez para cada operador. Reflection nos permite obtener un rendimiento a la par con la extracción de esa prueba, sin repetir el ciclo una docena de veces (y por lo tanto sacrificar la capacidad de mantenimiento). Simplemente genere y compile el ciclo que necesita sobre la marcha.

He hecho realmente esta optimización , aunque era un poco más complicado de la situación, y los resultados fueron sorprendentes. Una mejora en el orden de magnitud en el rendimiento y menos líneas de código.

(Nota: Inicialmente probé el equivalente de pasar un Func en lugar de un char, y fue un poco mejor, pero no casi la reflexión 10x lograda).

Craig Gidney
fuente
1
La mejor optimización es pasar un functor en su lugar. Es probable que el compilador JIT lo incorpore en tiempo de ejecución y es más fácil de mantener que la reflexión.
Billy ONeal
Eso sería más fácil de mantener, y en realidad lo intento primero, pero no fue lo suficientemente rápido. El JIT definitivamente no lo incluía, probablemente porque en mi caso había un segundo bucle interno sobre las operaciones a realizar.
Craig Gidney
Además, señalo que lo que tienes allí es realmente un código auto modificable, que no es realmente un reflejo. Se accede a través de API de reflexión en la mayoría de los idiomas que admiten reflexión, pero se puede hacer de la misma manera en idiomas que no admiten reflexión (por ejemplo, sería posible en C ++)
Billy ONeal
Quería evitar el ejemplo estándar de 'igualdad estructural'. La reflexión no es necesaria para el código auto modificable, pero ciertamente ayuda.
Craig Gidney
Realmente no. Puede hacerlo en lenguajes que no sean de reflexión.
Billy ONeal
-2

De ninguna manera es trampa ... Más bien, permite a los servidores de aplicaciones ejecutar las clases hechas por el usuario con el nombre de su elección, por lo tanto, proporciona flexibilidad al usuario, no los engaña.

Y si desea ver el código de cualquier archivo .class (en Java), ¡hay varios descompiladores disponibles sin cargo!

ANSHUL JAIN
fuente
La descompilación no es una característica de reflexión.
Billy ONeal
sí, no lo es ... Pero quería decir que si tiene ganas de hacer trampa mientras usa la reflexión (que le proporciona detalles de cualquier clase en Java) de lo que está equivocado porque la reflexión es un concepto muy útil y si obtener detalles de cualquier la clase es lo que te preocupa, eso se puede hacer fácilmente con cualquier descompilador ...
ANSHUL JAIN
2
La reflexión es un concepto útil en algunos casos, sí. Pero el 99.9% de las veces que veo que se usa, veo que solía piratear un error de diseño.
Billy ONeal