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

49

Hace un tiempo escribí esta respuesta a una pregunta sobre cómo evitar tener un getter y setter para cada variable mutable. En ese momento, solo tenía un presentimiento difícil de verbalizar de que esta es una mala idea, pero OP estaba preguntando explícitamente cómo hacerlo. Busqué aquí por qué esto podría ser un problema, y ​​encontré esta pregunta , cuyas respuestas parecen dar por sentado que usar 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 getter y setter genérico?

HAEM
fuente
12
Si solo desea reducir el repetitivo, tanto Lombok como Groovy generarán captadores y colocadores de forma silenciosa pero segura.
chrylis -on strike-
2
¿Cómo consumirías estos getters y setters en un lenguaje estático como java? A mí me parece un no titular.
Esben Skov Pedersen
@EsbenSkovPedersen Se podría consumirlos muy parecido a un Map's gety put, lo que es una manera bastante torpe de hacer las cosas.
HAEM
2
IIRC, Lombok sintetiza getters / setters en tiempo de compilación, según lo dictado por la anotación adecuada, por lo que: no incurren en la penalización de rendimiento de la reflexión, pueden analizarse / alinearse estáticamente, pueden refactorizarse (IntelliJ tiene un complemento para Lombok)
Alexander

Respuestas:

117

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 característica 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 uso del 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 captador getFooen mi clase y quiero cambiarle el nombre getBar. Si no utilizo ningún reflejo, simplemente puedo buscar la base del código getFooy encontraré todos los lugares que usan el captador para poder actualizarlo, e incluso si me pierdo uno, el compilador se quejará.

Pero si el lugar que usa el captador es algo así callGetter("Foo")y lo callGetterhace 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 está sufriendo si se traga esa excepción (que se rastrea) callGetterporque "solo se usa con cadenas codificadas, en realidad 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 setter genérico nunca lo notarían, excepto por el error extremadamente oscuro del setter que no hace nada en silencio. Los usuarios del getter podrían, si tienen suerte, notar la salida de la consola de la excepción ignorada).

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

Esto es básicamente un gran subpunto de lo anterior. El código de reflexión se trata 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 usar Java sobre Python que obtuviste en primer lugar.

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

Tal vez no en teoría, pero en la práctica, no encontrará una JVM que alinee o cree una caché en línea 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 necesarios para el código de reflexión es más lenta que las llamadas a métodos normales. Si convierte ese captador de una línea barato en una bestia reflectante, podría (no he medido esto) estar observando varios órdenes de magnitud de desaceleración.

Desventaja de getter / setter genérico específicamente

Esa es solo una mala idea, porque su clase ahora ya no tiene encapsulación. Cada campo que tiene es accesible. También podrías hacerlos públicos.

Sebastian Redl
fuente
8
Llegaste a todos los puntos principales. Más o menos una respuesta perfecta. Podría objetar que getters y setter son marginalmente mejores que los campos públicos, pero el punto más grande es correcto.
JimmyJames
2
También vale la pena mencionar que un IDE también puede generar fácilmente getters / setters.
stiemannkj1
26
+1 La depuración de la lógica de reflexión en un proyecto de tamaño de producción debe ser reconocida como tortura por la ONU e ilegalizada.
errantlinguist
44
"más difícil de entender para estos programadores". - para estos programadores es significativamente difícil de entender, pero es más difícil de entender para todos, sin importar cuán competente sea.
BartoszKP
44
"El código de reflexión es inaccesible para el análisis estático" - Esto. Tan importante para permitir la refactorización. Y a medida que las herramientas de análisis estático se vuelven más potentes (por ejemplo, la búsqueda de código en el último Team Foundation Server), escribir código que las habilite se vuelve aún más valioso.
Dan J
11

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

Y porque esto es mucho menos eficiente. Al menos en C #, un captador / definidor se alineará directamente a una sola instrucción CIL. En su respuesta, está reemplazando eso con 3 llamadas a funciones, que a su vez necesitan cargar metadatos para reflexionar. En general, 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 se acercó a 1000x.

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

Telastyn
fuente
2
Tenga en cuenta que la pregunta está etiquetada como Java, que tiene una basura tan deliciosa como el concepto Beans. Un bean no tiene campos públicos, pero puede exponer campos a través getX()y setX()métodos que se pueden encontrar a través de la reflexión. No necesariamente se supone que los frijoles encapsulan sus valores, podrían ser DTO. Entonces, en Java, los captadores a menudo son un dolor necesario. Las propiedades de C # son mucho, mucho mejores en todos los aspectos.
amon
11
Sin embargo, las propiedades de C # son getters / setters disfrazados. Pero sí, el concepto de Frijoles es un desastre.
Sebastian Redl
66
@amon Correcto, C # tomó una idea terrible y fue más fácil de implementar.
JimmyJames
55
@JimmyJames No es una idea terrible, de verdad; Puede mantener la compatibilidad ABI a pesar de los cambios de código. Un problema que muchos no tienen, supongo.
Casey
3
@Casey El alcance de esto va más allá de un comentario, pero construir una interfaz a partir de los detalles internos de la implementación es al revés. Conduce a un diseño de procedimiento. Hay momentos en que tener un método getX es la respuesta correcta, pero es poco frecuente en un código bien diseñado. El problema con getters y setters en Java no es el código repetitivo que los rodea, es que son parte de un enfoque de diseño terrible. Hacerlo más fácil es como hacerlo más fácil golpearse en la cara.
JimmyJames
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 a foo.get ("bar") y foo.set ("bar", valor)

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

Peter Green
fuente
Me parece que esta razón es más importante que todas las demás en otras respuestas.
Esben Skov Pedersen
-4

Por supuesto, no es una mala idea, es solo que en un mundo java normal es una mala idea (cuando solo quieres un getter o setter). 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 escenario de caso de uso.

Nada es correcto o incorrecto como un hecho.

como un hecho
fuente