/ java

Java 8 - malas experiencias

Con el release de Java 8 (hace ya algo de tiempo), muchos hemos tenido que aprender sobre la marcha, otros han tomado cursos o leido la documentacion. Sin embargo, hay muchos desarrolladores que se han quedado atras y utilizan los features de manera muy distinta a la intencionada, dejando un codigo dificil de leer

lambdas

Muchos lenguajes han tenido ya expresiones lambda dentro de su arcenal por mucho tiempo ya, en el mundo de Java, sin embargo, esto es algo nuevo, y lamentablemente, como Java no solia tener conceptos del paradigma funcional dentro del lenguaje, muchos no entienden como deberia escribirse / leerse

Las expresiones lambda nos ayudan en varias cosas:

  • evadir clases anonimas - obj.method((p1, p2) -> p1.someMethod() && p2.someMethod()) en lugar de:
obj.method(new SomeClass() {
  @Override
  public boolean compareSomething(final SomeType p1, final SomeType p2) {
    return p1.someMethod() && p2.someMethod();
  }
})

Simplemente mucho mas resumido, facil de leer, entendible. Menos Java.

  • encapsular comportamientos en funciones - Este es el ejemplo canonico para la mayoria de los otros lenguajes que ya contaban con lambdas, donde una funcion puede ser abstraida / referenciada mediante una instancia: final Function<InputType, OutputType> validateSomething = param -> param.checkSomething() && param.getValue() > 20. Existen mas interfaces como Predicate -por ejemplo- que apoyan mas y permiten mas flexibilidad, pero este no es un tutorial. De esta manera, podemos utilizarlos flexiblemente tanto como referencia, por ejemplo:
public void enableAccount(final Customer customer) {
  final Function<CustomerModel, Boolean> isCustomerValid =
    c -> c.getAge() > VALID_AGE && cc.someOtherProperty() != null;
  
  if (isCustomerValid.test(customer)) {
    customerService.enableUser(customer);
  }
}

En el ejemplo anterior, se puede argumentar que podriamos simplemente haber definido un metodo dentro de la clase que llamemos de forma similar, pero es solo ilustrativo previo a los ejemplos con colecciones que son los que considero mas importantes

colecciones

Las colecciones cuentan ahora con una interface llamada Stream, esta provee de varios metodos muy utiles para utilizarlo con un enfoque funcional

Este post no es para explicar el paradigma funcional, asi que POR FAVOR amigo desarrollador Java, lee al respecto

Volviendo al tema, stream -en una version totalmente resumida- permite ejecutar ciertas operaciones como:

  • filter
  • map
  • collect
  • forEach

Una vez mas, no pongo definiciones especificas porque esto no es un tutorial. Ademas, los nombres de las funciones son nombres que considero bastante LITERALES a lo que realizan, solo hace falta saber.. Ya sabes, leer, para poder usarlas

Estas van a permitir utilizar predicados / funciones / expresiones lambda sobre la coleccion, por ejemplo, en lugar de hacer lo siguiente:

final List<User> validUsers = new ArrayList<>();

for (final User user : allUsers) {
  if (user.getAge() > 18) {
    validUsers.add(user);
  }
}

La simple lectura del codigo anterior no es directa, tenemos primero la creacion de una lista donde se almacenaran los usuarios validos, despues un loop sobre una coleccion con todos los usuarios, finalmente vemos una condicion para agregarlos a la lista -que seguramente para este momento ya habiamos olvidado que existia- de arriba

Utilizando los distintos metodos de stream

final List<User> validUsers = allUsers.stream()
  .filter(u -> u.getAge() > 18)
  .collect(Collectors.toList()

No solo es mas corto al escribirse, realmente es mas explicito sobre las operaciones que va a realizar sobre la coleccion

otras malas practicas

boxing / unboxing

final double total = 
  ofNullable(objetoDouble)
      .map(Double::doubleValue)
      .orElse(Double.valueOf(0d))
  + 10d;

No hagan eso :V, primero que nada.. Si no vas a utilizar absolutamente nada de la clase Double, utiliza la definicion primitiva, utiliza menos espacio. Y podrias dejar que el interprete se encargue del unboxing del objeto, por ejemplo:

final double total = ofNullable(objetoDouble).orElse(0d)

codigo completo en una sola linea

final Collection<User> validUsers = userService.getAllUsers().stream().filter(...).map(...).collect(....);

Simplemente no me imagino como escriben / leen codigo estas personas.. El formato / indentancion es tan importante que a veces quisiera que les marcara errores de indentacion como en Python solo para que valoren este punto, para un trabajo con stream, deberia ser sencillo leerlo, auto explicado:

// los usuarios validos son aquellos que
final Collection<User> validUsers =
  userService.getAllAccounts().stream()
    // cumplan con el filtro X
    .filter(...)
    // dichos usuarios validos se convierten a tipo User
    .map(...)
    // finalmente se colectan todos en una sola lista
    .collect(...);

Como se puede apreciar, la asignacion es muy sencilla de leer, tanto, que al quitar los comentarios podemos aun entender la intencion de cada linea:

final Collection<User> validUsers =
  userService.getAllAccounts().stream()
    .filter(...)
    .map(...)
    .collect(...);

La diferencia es muy simple... El codigo en multi linea esta acomodado de una forma en la que la lectura del codigo explica directamente la intencion de los statements, no tienes que estar pensando en lo que estabas haciendo previamente como si tuvieras todo en una sola linea

iterators

No hay razon por la cual aun deberiamos seguir utilizando iterators, el codigo se ve horrible, esto while (it.hasNext()) no se ve interesante, se ve terrible, todavia peor, he visto for (...; it.hasNext();...) <<== esto NO es inteligente

conclusion

Java 8 introdujo muchos artefactos bastante interesantes para hacer las cosas mas legibles y sencillas, sin embargo, la falta de interes de muchos desarrolladores pueden convertir estas en malas practicas directamente, he escuchado incluso tech leads argumentar sobre lo malo que son estos componentes por que se presetan para la malas implementaciones. Pero no, amigo, los features no se prestan a malas implementaciones, los programadores hacen esas malas implementaciones