¿Es un mal hábito (sobre) usar la reflexión?

16

¿Es una buena práctica usar la reflexión si reduce en gran medida la cantidad de código repetitivo?

Básicamente, existe una compensación entre el rendimiento y tal vez la legibilidad por un lado y la abstracción / automatización / reducción del código repetitivo por el otro lado.

Editar: Aquí hay un ejemplo del uso recomendado de la reflexión .

Para dar un ejemplo, suponga que hay una clase abstracta Baseque tiene 10 campos y tiene 3 subclases SubclassA, SubclassBy SubclassCcada una con 10 campos diferentes; Todos son frijoles simples. El problema es que obtienes dos Basereferencias de tipo y quieres ver si sus objetos correspondientes son del mismo (sub) tipo y son iguales.

Como soluciones, existe la solución sin procesar en la que primero verifica si los tipos son iguales y luego verifica todos los campos o puede usar la reflexión y ver dinámicamente si son del mismo tipo e iterar sobre todos los métodos que comienzan con "get" (convención sobre configuración), llámelos a ambos objetos y llame a iguales en los resultados.

boolean compare(Base base1, Base, base2) {
    if (base1 instanceof SubclassA && base2 instanceof SubclassA) { 
         SubclassA subclassA1 = (SubclassA) base1;
         SubclassA subclassA2 = (SubclassA) base2;
         compare(subclassA1, subclassA2);
    } else if (base1 instanceof SubclassB && base2 instanceof SubclassB) {
         //the same
    }
    //boilerplate
}

boolean compare(SubclassA subA1, SubclassA subA2) {
    if (!subA1.getField1().equals(subA2.getField1)) {
         return false;
    }
    if (!subA1.getField2().equals(subA2.getField2)) {
         return false;
    }
    //boilerplate
}

boolean compare(SubclassB subB1, SubclassB subB2) {
    //boilerplate
}

//boilerplate

//alternative with reflection 
boolean compare(Base base1, Base base2) {
        if (!base1.getClass().isAssignableFrom(base2.getClass())) {
            System.out.println("not same");
            System.exit(1);
        }
        Method[] methods = base1.getClass().getMethods();
        boolean isOk = true;
        for (Method method : methods) {
            final String methodName = method.getName();
            if (methodName.startsWith("get")) {
                Object object1 = method.invoke(base1);
                Object object2 = method.invoke(base2);
                if(object1 == null || object2 == null)  {
                    continue;
                }
                if (!object1.equals(object2)) {
                    System.out.println("not equals because " + object1 + " not equal with " + object2);
                    isOk = false;
                }
            }
        }

        if (isOk) {
            System.out.println("is OK");
        }
}
m3th0dman
fuente
20
El uso excesivo de cualquier cosa es un mal hábito.
Tulains Córdova
1
@ user61852 Correcto, demasiada libertad conduce a la dictadura. Algún griego antiguo ya sabía sobre esto.
ott-- 01 de
66
“Demasiada agua sería mala para ti. Obviamente, demasiada es precisamente esa cantidad que es excesiva, ¡eso es lo que significa! ”- Stephen Fry
Jon Purdy
44
"Cada vez que se encuentre escribiendo el código de la forma" si el objeto es del tipo T1, haga algo, pero si es del tipo T2, haga otra cosa ", bofetada. Javapractices.com/topic/TopicAction.do?Id = 31
rm5248

Respuestas:

25

La reflexión fue creado para un propósito específico, para descubrir la funcionalidad de una clase que era desconocido en tiempo de compilación, similar a lo que el dlopeny dlsymfunciones hace en C. Cualquier uso fuera de que se debe en gran medida examinada.

¿Alguna vez se te ocurrió que los propios diseñadores de Java encontraron este problema? Es por eso que prácticamente cada clase tiene un equalsmétodo. Diferentes clases tienen diferentes definiciones de igualdad. En algunas circunstancias, un objeto derivado podría ser igual a un objeto base. En algunas circunstancias, la igualdad podría determinarse en función de campos privados sin captadores. Usted no sabe

Es por eso que cada objeto que quiere igualdad personalizada debe implementar un equalsmétodo. Eventualmente, querrás poner los objetos en un conjunto, o usarlos como un índice hash, luego tendrás que implementar de equalstodos modos. Otros lenguajes lo hacen de manera diferente, pero los usos de Java equals. Debes apegarte a las convenciones de tu idioma.

Además, el código "repetitivo", si se coloca en la clase correcta, es bastante difícil de arruinar. La reflexión agrega complejidad adicional, lo que significa posibilidades adicionales de errores. En su método, por ejemplo, dos objetos se consideran iguales si uno regresa nullpara un campo determinado y el otro no. ¿Qué pasa si uno de tus captadores devuelve uno de tus objetos, sin un apropiado equals? Tu if (!object1.equals(object2))fallará. También lo hace propenso a errores es el hecho de que la reflexión rara vez se usa, por lo que los programadores no están tan familiarizados con sus problemas.

Karl Bielefeldt
fuente
12

El uso excesivo de la reflexión probablemente depende del lenguaje utilizado. Aquí estás usando Java. En ese caso, la reflexión debe usarse con cuidado porque a menudo es solo una solución para un mal diseño.

Entonces, está comparando diferentes clases, este es un problema perfecto para la anulación de métodos . Tenga en cuenta que las instancias de dos clases diferentes nunca deben considerarse iguales. Puede comparar la igualdad solo si tiene instancias de la misma clase. Consulte /programming/27581/overriding-equals-and-hashcode-in-java para ver un ejemplo de cómo implementar la comparación de igualdad correctamente.

Sulthan
fuente
16
+1 - Algunos de nosotros creemos que CUALQUIER uso de la reflexión es una bandera roja que indica un mal diseño.
Ross Patterson el
1
Lo más probable es que en este caso sea deseable una solución basada en iguales, pero como idea general, ¿qué tiene de malo la solución de reflexión? En realidad, es muy genérico y no es necesario que los métodos de igualdad se escriban explícitamente en cada clase y subclase (aunque un buen IDE puede generarlos fácilmente).
m3th0dman
2
@RossPatterson ¿Por qué?
m3th0dman
2
@ m3th0dman Debido a que conduce a cosas como su compare()método, suponiendo que cualquier método que comience con "get" es un captador y es seguro y apropiado llamarlo como parte de una operación de comparación. Es una violación de la definición de interfaz prevista del objeto, y aunque eso puede ser conveniente, casi siempre es incorrecto.
Ross Patterson el
44
@ m3th0dman Viola la encapsulación: la clase base tiene que acceder a los atributos en sus subclases. ¿Qué pasa si son privados (o getter privados)? ¿Qué pasa con el polimorfismo? Si decido agregar otra subclase y quiero hacer la comparación de manera diferente en ella? Bueno, la clase base ya lo está haciendo por mí y no puedo cambiarlo. ¿Qué pasa si los captores perezosamente cargan algo? ¿Quiero un método de comparación para hacer eso? ¿Cómo sé si un método que comienza getes un captador y no un método personalizado que devuelve algo?
Sulthan
1

Creo que tienes dos problemas aquí.

  1. ¿Cuánto código dinámico vs estático debo tener?
  2. ¿Cómo expreso una versión personalizada de igualdad?

Código dinámico vs estático

Esta es una pregunta perenne y la respuesta es muy testaruda.

Por un lado, su compilador es muy bueno para detectar todo tipo de código incorrecto. Lo hace a través de varias formas de análisis, siendo el análisis de tipo común. Sabe que no puede usar un Bananaobjeto en el código que espera unCog . Te dice esto a través de un error de compilación.

Ahora solo puede hacer esto cuando puede inferir tanto el tipo aceptado como el tipo dado a partir del contexto. Cuánto puede inferirse y qué tan general es esa inferencia, depende en gran medida del lenguaje que se utilice. Java puede inferir información de tipo a través de mecanismos como Herencia, Interfaces y Genéricos. El millaje varía, algunos otros idiomas proporcionan menos mecanismos y otros proporcionan más. Todavía se reduce a lo que el compilador puede saber que es verdad.

Por otro lado, su compilador no puede predecir la forma del código externo, y a veces se puede expresar un algoritmo general sobre muchos tipos que no se pueden expresar fácilmente usando el sistema de tipos del lenguaje. En estos casos, el compilador no siempre puede conocer el resultado de antemano, y es posible que ni siquiera pueda saber qué pregunta hacer. La reflexión, las interfaces y la clase Object son la forma en que Java maneja estos problemas. Tendrá que proporcionar los controles y el manejo correctos, pero no es poco saludable tener este tipo de código.

Si hacer que su código sea muy específico o muy general se reduce al problema que está tratando de manejar. Si pudiera expresarlo fácilmente utilizando el sistema de tipos, hágalo. Deje que el compilador aproveche sus puntos fuertes y lo ayude. Si el sistema de tipos no puede saber de antemano (código extranjero), o el sistema de tipos no es adecuado para una implementación general de su algoritmo, entonces la reflexión (y otros medios dinámicos) son la herramienta adecuada para usar.

Solo tenga en cuenta que salir del sistema de tipos de su idioma es sorprendente. Imagina acercarte a tu amigo y comenzar una conversación en inglés. De repente, suelta algunas palabras del español, francés y cantonés para expresar tus pensamientos con precisión. El contexto le dirá mucho a su amigo, pero es posible que tampoco sepan cómo manejar esas palabras que conducen a todo tipo de malentendidos. Se trata de esos malos entendidos mejor que explicar esas ideas en Inglés utilizando más palabras?

Igualdad personalizada

Si bien entiendo que Java depende en gran medida del equalsmétodo para comparar generalmente dos objetos, no siempre es adecuado en un contexto dado.

Hay otra forma, y ​​también es un estándar Java. Se llama un comparador .

La forma en que implemente su comparador dependerá de lo que esté comparando y cómo.

  • Se puede aplicar a cualquiera de los dos objetos, independientemente de su equalsimplementación específica .
  • Podría implementar un método de comparación general (basado en la reflexión) para manejar cualquiera de los dos objetos.
  • Las funciones de comparación repetitivas podrían agregarse para los tipos de objetos comparados comúnmente, garantizando la seguridad y la optimización de los tipos.
Kain0_0
fuente
1

Prefiero evitar la programación reflexiva tanto como sea posible porque

  • hace que el código sea más difícil de verificar estáticamente por el compilador
  • hace que el código sea más difícil de razonar
  • hace que el código sea más difícil de refactorizar

También es mucho menos eficaz que las llamadas a métodos simples; solía ser más lento en un orden de magnitud o más.

Código de comprobación estática

Cualquier código reflexivo busca clases y métodos usando cadenas; en el ejemplo original busca cualquier método que comience con "get"; devolverá getters pero otros métodos además, como "gettysburgAddress ()". Las reglas podrían ajustarse en el código, pero el punto sigue siendo que es una verificación de tiempo de ejecución ; un IDE y el compilador no pueden ayudar. En general, no me gusta el código "mecanografiado en cadena" o "obsesivo primitivo".

Más difícil de razonar

El código reflexivo es más detallado que las llamadas a métodos simples. Más código = más errores, o al menos más potencial para errores, más código para leer, probar, etc. Menos es más.

Más difícil de refactorizar

Porque el código está basado en cadenas / dinámicamente; tan pronto como la reflexión esté en la escena, no puede refactorizar el código con un 100% de confianza utilizando las herramientas de refactorización de un IDE, ya que el IDE no puede detectar los usos reflexivos.

Básicamente, evite la reflexión en código general si es posible; Busque un diseño mejorado.

David Kerr
fuente
0

El uso excesivo de cualquier cosa por definición es malo, ¿verdad? Así que vamos a deshacernos del (más) por ahora.

¿Llamaría al uso interno increíblemente pesado de la reflexión de primavera un mal hábito?

Spring controla la reflexión mediante el uso de anotaciones, al igual que Hibernate (y probablemente docenas / cientos de otras herramientas).

Siga esos patrones si lo usa en su propio código. Use anotaciones para asegurarse de que el IDE de su usuario pueda ayudarlo (incluso si usted es el único "Usuario" de su código, el uso descuidado de la reflexión probablemente volverá a morderle el trasero).

Sin embargo, sin tener en cuenta cómo los desarrolladores usarán su código, incluso el uso más simple de la reflexión es probablemente un uso excesivo.

Bill K
fuente
0

Creo que la mayoría de estas respuestas pierden el punto.

  1. Sí, probablemente debería escribir equals()y hashcode(), como señaló @KarlBielefeldt.

  2. Pero, para una clase con muchos campos, esto puede ser tedioso repetitivo.

  3. Entonces, depende .

    • Si solo necesita igual y hashcode raramente , es pragmático, y probablemente esté bien, usar un cálculo de reflexión de propósito general. Al menos como un primer pase rápido y sucio.
    • pero si necesita igual, por ejemplo , estos objetos se colocan en HashTables, por lo que el rendimiento será un problema, definitivamente debe escribir el código. Será mucho más rápido.
  4. Otra posibilidad: si sus clases realmente tienen tantos campos que escribir un igual es tedioso, considere

    • poner los campos en un mapa.
    • aún puede escribir captadores y establecedores personalizados
    • Úselo Map.equals()para su comparación. (o Map.hashcode())

por ejemplo ( nota : ignorando las verificaciones nulas, probablemente debería usar Enums en lugar de las teclas de cadena, mucho no se muestra ...)

class TooManyFields {
  private HashMap<String, Object> map = new HashMap<String, Object>();

  public setFoo(int i) { map.put("Foo", Integer.valueOf(i)); }
  public int getFoo()  { return map.get("Foo").intValue(); }

  public setBar(Sttring s) { map.put("Bar", s); }
  public String getBar()  { return map.get("Bar").toString(); }

  ... more getters and setters ...

  public boolean equals(Object o) {
    return (o instanceof TooManyFields) &&
           this.map.equals( ((TooManyFields)o).map);
}
user949300
fuente