Buscar
Social
Ofertas laborales ES

Foro sobre Java SE > JavaSE 1.8 lambdas: refactorización

He refactorizado estos días el método siguiente, con el objeto de usar las nuevas lambdas de Java 8

public static Map<Object, Object> getProperties(JComponent component) {
final Method[] methods = component.getClass().getMethods();
final Map<Object, Object> retVal = new HashMap<>(Numbers.getMapCapacity(methods.length));
for (Method method : methods) {
if (method.getName().matches("^(is|get).*") && method.getParameterTypes().length == 0) {
try {
final Class<?> returnType = method.getReturnType();
if (returnType != void.class && !returnType.getName().startsWith("[") && !setExclude.contains(method.getName())) {
final Object value = method.invoke(component);
if (value != null && !(value instanceof Component)) {
retVal.put(method.getName(), value);
}
}
// ignore exceptions that arise if the property could not be accessed
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
}
}
}
return retVal;
}

Finalmente, el nuevo método ha quedado como sigue. Como aquí no se puede dar un formato adecuado al código, he separado las funciones con un espacio en blanco, para facilitar la lectura.

public static Map<String, Object> getProperties(final JComponent component) {
return Arrays

.stream(component.getClass().getMethods())

.filter((Method method) -> method.getName().matches("^(is|get).*") && method.getParameterTypes().length == 0)

.filter((Method method) -> {
final Class<?> returnType = method.getReturnType();
return returnType != void.class && !returnType.getName().startsWith("[") && !setExclude.contains(method.getName());
})

.map((Method method) -> {
Method methodMapped = null;
try {
Object value = method.invoke(component);
if (value != null && !(value instanceof Component)) {
methodMapped = method;
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
}
return methodMapped;
})

.filter((Method method) -> method != null)

.collect(Collectors.toMap(
Method::getName,
(Method method) -> {
Object value = null;
try {
value = method.invoke(component);
} catch (IllegalAccessException | IllegalArgumentException |
InvocationTargetException ex) {
}
return value;
}));
}

Es indudable el aumento significativo en las líneas de código. Aunque es comprensible, dados los tres if encadenados, con una variable de control en medio, y las excepciones.
Me parece que el código es más comprensible ahora, porque la secuencia lógica es lineal.
Aunque es un caso especial (normalmente el uso de lambdas no implica aumentos tan evidentes de la extensión del código), me gustaría conocer opiniones al respecto.

junio 29, 2014 | Registered Commenterchoces

Choces ¿Por qué no ponemos esto como una noticia en portada?. Creo que puede ser un debate bastante interesante.

junio 29, 2014 | Registered CommenterAbraham

No sé si como noticia será bien aceptado. Es un caso muy concreto de refactorización.
Quizás podría mencionarse en Noticias esta entrada en el foro, invitando a abrir un debate sobre lambdas en el mismo foro, y ver qué pasa.
Ya cansa un poco que el foro sea solo un "solucionario de problemas" :)

junio 29, 2014 | Registered Commenterchoces

Buenas,

Yo creo que los lambdas son geniales para trabajar con listas, ficheros, para eliminar las clases anonimas, para paralelizar y hacer map/reduce sin esfuerzo, etc etc....
Pero este ejemplo que pones para mi es un claro ejemplo de abuso de la funcionalidad. Del mismo modo que estoy seguro que refactorizar a la implementacion funcional te habra costado muchisimo mas esfuerzo de relizar que la implementacion original estructurada, lo mismo pasa a cualquiera que lea el codigo. Al menos a mi me pasa. La primera la leo sin problemas. Para la segunda necesito lapiz, papel y tiempo para saber que hace...
No podemos pretender eliminar todas las secuencias de control y hacer un programa totalmente lineal. Si lo hacemos asi, a mi entender habremos vuelto atras 50 anios. Como casi siempre, la medida exacta es aquella que dicta el sentido comun.
Como no tengo mucho tiempo, pongo un link que expresa un poco como yo lo veo. Por suerte es corto y basta con leer la primera mitad :-)
http://javax0.wordpress.com/2014/04/16/we-hatelove-lambda/

Un saludo

junio 30, 2014 | Unregistered CommenterUnoPorAhi

Es cuestión de acostumbrarse. No me ha llevado tanto tiempo ni esfuerzo, porque antes ya había realizado un centenar de refactorizaciones a lambdas, por lo que leo sin esfuerzo las funciones.
El caso es complejo, debido a un for y tres if con condiciones intermedias, aderezado con las excepciones.
Estando habituado a la programación imperativa, es normal que se lea mejor que la funcional; pero no porque la primera sea más clara, sino por simple hábito mental.
Sin embargo, una vez acostumbrado a lo que significa cada función, y lo que devuelve, el nuevo código se lee y se comprende de un tirón, leyéndolo tal y como está escrito: linealmente.
Creo que esa es una de las grandes ventajas de la programación funcional.

También es cierto que sin un formato adecuado, apenas se entiende nada :)
Lástima que el foro no lo permita.

Podría publicar el caso inverso, donde la refactorización a lambdas ha reducido el volumen de código a menos de una cuarta parte, y la comprensión ha aumentado varios órdenes de magnitud: de implementaciones directas de Fork/Join a Lambas.

junio 30, 2014 | Registered Commenterchoces

Podría publicar el caso inverso, donde la refactorización a lambdas ha reducido el volumen de código a menos de una cuarta parte, y la comprensión ha aumentado varios órdenes de magnitud: de implementaciones directas de Fork/Join a Lambas.

Exacto, estamos 100% de acuerdo en muchos casos los lambdas mejoran la legibilidad y reducen el código.

Sin embargo, en este caso su uso produce un efecto contrario. Entonces... en que mejora el segundo código al primero? Que aporta el refactorizarlo?

Un saludo,

junio 30, 2014 | Unregistered CommenterUnoPorAhi

El for se reemplaza por stream
Los tres if se reemplazan por dos filter y un map
Se añade un filter adicional para evitar posibles null en el retorno del map
El collect final crea y llena el Map resultante

En realidad es lo mismo (excepto ese filter adicional) resuelto de otra manera, porque el resto del código es casi idéntico.
En este caso, las excepciones no se pueden lanzar como con los métodos, porque los lambdas no lo permiten; de ahí esas excepciones capturadas que añaden líneas al código.
Si un componente tiene muchas propiedades, el stream se puede paralelizar, lo que añadiría un mejor rendimiento.
Cuando leo el segundo código, entiendo mejor lo que sucede, porque no tengo que realizar saltos mentales para seguir la ejecución, inevitables en la solución imperativa.
A fin de cuentas, el código fuente se escribe para que lo entiendan seres humanos :)

junio 30, 2014 | Registered Commenterchoces