¿Por qué es una mala idea crear un setter y getter genérico con reflexión?

47

Hace un tiempo escribí esta respuesta a una pregunta sobre cómo evitar tener un captador y definidor para cada variable mutable . En ese momento, solo tenía la sensación de que era una mala idea, pero OP estaba preguntando explícitamente cómo hacerlo. Busqué aquí por qué esto podría ser un problema, y encontré esto pregunta , cuyas respuestas parecen tomarse como un hecho que el uso de la reflexión a menos que sea absolutamente necesario es una mala práctica.

Entonces, ¿puede alguien verbalizar por qué es un hecho que se debe evitar la reflexión, específicamente en el caso del genérico que obtiene y establece?

    
pregunta HAEM 09.10.2017 - 16:39

4 respuestas

114

Desventajas de la reflexión en general

La reflexión es más difícil de entender que el código de línea recta.

En mi experiencia, la reflexión es una función de "nivel experto" en Java. Yo diría que la mayoría de los programadores nunca usan la reflexión activamente (es decir, el consumo de bibliotecas que usan la reflexión no cuenta). Eso hace que el código sea más difícil de entender para estos programadores.

El código de reflexión es inaccesible para el análisis estático

Supongamos que tengo un getter getFoo en mi clase y quiero cambiarle el nombre a getBar . Si no uso ningún reflejo, solo puedo buscar en el código base para getFoo y encontraré cada lugar que use el getter para poder actualizarlo, e incluso si pierdo uno, el compilador se quejará.

Pero si el lugar que utiliza el captador es algo como callGetter("Foo") y callGetter hace getClass().getMethod("get"+name).invoke(this) , entonces el método anterior no lo encontrará y el compilador no se quejará. Solo cuando el código se ejecute realmente obtendrá un NoSuchMethodException . E imagine el dolor en el que se encuentra si esa excepción (que se rastrea) es tragada por callGetter porque "solo se usa con cadenas codificadas, no puede suceder". (¿Nadie haría eso? ¿Alguien podría discutir? Excepto que el OP hizo exactamente eso en su respuesta SO. Si se cambia el nombre del campo, los usuarios del genérico nunca lo notarán, excepto por el error extremadamente oscuro El setter no hace nada en silencio. Los usuarios del getter pueden, si tienen suerte, notar la salida de la consola de la excepción ignorada.)

El compilador no comprueba el código de reflexión

Esto es básicamente un gran subpunto de lo anterior. El código de reflexión tiene que ver con Object . Los tipos se verifican en tiempo de ejecución. Los errores se descubren mediante pruebas unitarias, pero solo si tiene cobertura. ("Es solo un captador, no necesito probarlo"). Básicamente, pierdes la ventaja de que Java sobre Python te ganó en primer lugar.

El código de reflexión no está disponible para optimización

Tal vez no en teoría, pero en la práctica, no encontrará una JVM que incluya o cree un caché en línea para Method.invoke . Las llamadas a métodos normales están disponibles para tales optimizaciones. Eso los hace mucho más rápidos.

El código de reflexión es lento en general

La búsqueda dinámica de métodos y la verificación de tipos necesaria para el código de reflexión es más lenta que las llamadas de métodos normales. Si convierte ese captador barato de una línea en una bestia de reflexión, podría (no he medido esto) estar observando varios órdenes de magnitud de desaceleración.

La desventaja de un genérico getter / setter específicamente

Eso es simplemente una mala idea, porque tu clase ya no tiene encapsulación. Cada campo que tiene es accesible. Podrías también hacerlos públicos.

    
respondido por el Sebastian Redl 09.10.2017 - 16:57
11

Porque los captadores / definidores de paso son una abominación que no proporcionan ningún valor. No proporcionan encapsulación ya que los usuarios pueden obtener los valores reales a través de las propiedades. Son YAGNI porque rara vez va a cambiar la implementación de su almacenamiento de campo.

Y porque esto es mucho menos eficaz. Al menos en C #, un captador / definidor se alineará directamente con una única instrucción CIL. En su respuesta, está reemplazando eso con 3 llamadas de función, que a su vez necesitan cargar metadatos para reflexionar. Generalmente he usado 10x como regla general para los costos de reflexión, pero cuando busqué datos, una de las primeras respuestas incluyó esta respuesta que lo parchó más de 1000x.

(Y hace que su código sea más difícil de depurar, y perjudica la facilidad de uso porque hay más formas de utilizarlo incorrectamente y perjudica la capacidad de mantenimiento porque ahora está utilizando cadenas mágicas en lugar de identificadores adecuados ...)

    
respondido por el Telastyn 09.10.2017 - 16:55
8

Además de los argumentos ya presentados.

No proporciona la interfaz esperada.

Los usuarios esperan llamar a foo.getBar () y foo.setBar (valor), no foo.get ("barra") y foo.set ("barra", valor)

Para proporcionar la interfaz que los usuarios esperan, debe escribir un getter / setter separado para cada propiedad. Una vez que haya hecho eso, no tiene sentido usar la reflexión.

    
respondido por el Peter Green 10.10.2017 - 14:18
-4

Por supuesto, no es una mala idea, es solo que en un mundo java normal es una mala idea (cuando solo quieres un captador o colocador). Por ejemplo, algunos frameworks (spring mvc) o librares ya lo usan (jstl) de esa manera, algunos analizadores json o xml lo están usando, si quieres usar un DSL lo vas a usar. Entonces depende de su caso de uso.

Nada es correcto o incorrecto como un hecho.

    
respondido por el as a fact 09.10.2017 - 20:36

Lea otras preguntas en las etiquetas