Pasar al contenido principal
Cargando...

Excepciones Java

Inspiring technology by Hunters

Nuestros Hunters, cazadores de tendencias tecnológicas, nunca paran. En esta nueva edición de Inspiring Technology nos plantean si estaremos manejando mal las excepciones Java. ¡Sigue leyendo!

En los desarrollos actuales es muy típico que ante una situación de error se lance una excepción. Ejemplos de esto puede ser una validación de negocio no satisfecha, registros no encontrados en la base de datos cuando se accede por su ID… incluso librerías de terceros.

Ahora bien, estas excepciones pueden o no ser manejadas en la misma clase donde se lanzan, y lo mas normal es que no sea así. Es posible incluso que, en una arquitectura de 3 capas, una excepción lanzada en la capa de acceso a datos acabe siendo manejada por la capa de presentación, lo cual, rompe el patrón en sí mismo y genera acoplamiento.

Queda claro que el manejo de excepciones así utilizado se convierte en un sofisticado GOTO y rompe el principio del menor asombro, que establece que el comportamiento de un programa debe ser, obvio, consistente y predecible. Aún pudiendo romper la dependencia entre capas a través del uso de Exceptions Translators, lo cierto es que el problema base seguiría existiendo, que es el uso de excepciones para algo que no es excepcional.

De acuerdo a la documentación original de Oracle:

Definición de excepción

 

Por tanto, las excepciones sirven para manejar eventos excepcionales. Al lanzar una excepción, ¿se está lanzando un evento excepcional o relevante? ¿Cuál es la diferencia? Un usuario introduce unos datos por formulario y los manda. Los datos son validados en el servidor y se determina que no son correctos. En este caso, no es tan raro que un usuario introduzca datos erróneos, por lo tanto, no se puede hablar de excepción. Sin embargo, si es relevante para el sistema, pues no puede permitir que la operación del usuario continúe.

Al margen de lo explicado, y debido a que las excepciones están funcionando como GOTO’s, hay un pequeño problema adicional de rendimiento.

Así pues…¿Cómo solventar estos problemas?

Vavr al rescate

Vavr es una librería que abraza el paradigma funcional que poco a poco se va abriendo camino en Java desde su versión 1.8 y que permite el tratamiento de excepciones como tipos de datos normales. Se sirve del concepto de monads que ya se introdujeron en Java 8 en tipos de datos como el Optional, Streams…

Vavr plantea el uso de cinco tipos de datos base:

Vavr

1. Either: Sintácticamente es una tupla de dos valores, Left y Right. Right es el valor obtenido de una operación y Left es el valor en caso de que la operación haya fallado:

1. Either

 

2. Option: Similar al Optional de java. Su valor añadido es la posibilidad de transformarse en un Either. Adicionalmente, si se hace un map sobre un valor nulo, en Optional de Java 8, no pasaría nada, pues no llegaría a ejecutarse. En Option de vavr habría un NullPointerException. Los desarrolladores de Vavr lo consideran una correcta adhesión al requerimiento de que una operación .map ejecutada sobre un Monad debe mantener el contexto computacional.

3. Try: Es un tipo monádico que representa la ejecución de una operación que puede acabar en éxito o en fallo.

El método toEither() devuelve un Either<Throwable,T> donde T es el tipo del dato devuelto por el método failPruneMehtod.

También permite implementar una versión funcional de un bloque try-catch.

El método toEither() devuelve un Either|{Throwable,T|} donde T es el tipo del dato devuelto por el método failPruneMehtod. El método toEither() devuelve un Either|{Throwable,T|} donde T es el tipo del dato devuelto por el método failPruneMehtod. bloque try-catch

4. Validation: es un Applicative Functor que permitirá la acumulación de errores de distintas funciones.

Un objeto Validation puede ser convertido fácilmente a Either mediante invocación de método .toEither();

Seq es un tipo de dato de Vavr que expresa una colección de valores. Puede ser convertido fácilmente a lista mediante .toJavaList();

4. Validation

5. Lazy: es un valor monódico que representa un valor calculado vagamente y solo una vez. Es decir, que una vez calculado, devuelve siempre el mismo valor.

5. Lazy

¿Cómo aplica esto a mi proyecto?

Se asume que los datos que llegan al negocio desde un servicio Rest deben ser siempre correctos, o de lo contrario, cortar la ejecución.

La creación del usuario sería como sigue:

Finalmente, el UserRepository implementaría el salvado de la siguiente manera:

De acuerdo, pero… ¿qué pasa con el tratamiento transaccional?

Actualmente hay una mejora que permitirá a Spring integrar en su comportamiento transaccional el control de rollback basado en los monads de Vavr.

No obstante, siempre se puede hacer un Aspect para ejecutar rollback de manera programática según interese.

Principales ventajas

Al programar de esta manera no se ha lanzado ni una sola excepción a pesar de todas las validaciones que se han hecho al crear los distintos objetos (CreateUserCommand, User…).

Se ha tenido en cuenta cualquier excepción que pudiera ocurrir durante el acceso a la base de datos mediante el uso de Try. Además, dichas excepciones son traducidas a objetos Error de negocio.

Si se decidiera mover las excepciones dentro de un objeto Error no habría problema en perder el Stack Trace, pues es un dato que va dentro de la propia excepción.

Adicionalmente se cuenta con la ventaja de que si hay varios errores en los datos introducidos del usuario, se obtendrán todos, no solo el fallo relativo a la primera validación que se ejecute. 

Finalmente, se adopta un enfoque completamente funcional, lo que permite una mayor velocidad de desarrollo.
 

Inconvenientes

La mayoría de los desarrolladores Java vienen del mundo imperativo, por lo que la correcta adopción de Vavr como librería funcional les suele costar, ya que, como se ha visto, supone una manera distinta de estructurar los programas. No obstante, dicha librería puede ser usada de una manera imperativa.

La adopción de Vavr por otro lado supone un acoplamiento a la propia librería, ya que no hay interfaces ni nada que sirva para “wrappear” esta funcionalidad. Por tanto, si se va a usar, debe ser desde el conocimiento lo que implica porque, como se ha visto, modifica la estructura de los programas ya sigan un enfoque imperativo o declarativo funcional.

¿Quieres saber más sobre Hunters?

Ser un hunter es aceptar el reto de probar nuevas soluciones que aporten resultados diferenciales. Únete al programa Hunters y forma parte de un grupo transversal con capacidad de generar y transferir conocimiento.

Anticípate a las soluciones digitales que nos harán crecer. Consulta más información sobre Hunters en la web.