[vc_row][vc_column][vc_column_text]El manejo de excepciones en Java no es un tópico sencillo. Es difícil de entender para los principiantes e incluso los desarrolladores expertos pueden tomar horas discutiendo como y que excepción Java debe ser lanzada o manejada.
Por tal motivo muchos equipos de desarrollo tienen su propio conjunto de reglas sobre cuando utilizarlas. Y si eres nuevo en el equipo, te puedes sorprender de cuan diferentes pueden ser estas reglas de algunas utilizadas antes.
Sin embargo, hay buenas prácticas que son utilizadas por la mayoría de los equipos. A continuación presentamos 9 de ellas que lo pueden ayudar a mejorar en el manejo de excepciones.[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
1. Limpia los recursos en el bloque Finally o utiliza la sentencia de Try con recursos
Sucede con recurso que utiliza un recurso en el bloque try, como un InputStream, que necesita cerrar después. Un error común en estas situaciones es cerrar el recurso al final del bloque try.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public void doNotCloseResourceInTry() { FileInputStream inputStream = null; try { File file = new File("./tmp.txt"); inputStream = new FileInputStream(file); // Utilice el inputStream para leer el archivo // NO haga esto inputStream.close(); } catch (FileNotFoundException e) { log.error(e); } catch (IOException e) { log.error(e); } } |
El problema es que este enfoque para funcionar perfectamente bien siempre que no se produzca ninguna excepción. Todas las declaraciones dentro del bloque try se ejecutarán y el recurso se cerrará.
Pero se agregó el bloque try por una razón. Dentro de él se utilizan uno o más métodos que pueden lanzar una excepción, o tal vez usted mismo lanza la excepción. Eso significa que es posible que no llegue al final del bloque try. Y como resultado, no cerrará los recursos.
Debería, por lo tanto, poner todo su código para cerrar recursos en el bloque finally o utilizar una instrucción try con recursos.
Uso del bloque Finally
A diferencia de las anteriores líneas de tu bloque try, el bloque finally siempre se ejecuta. Eso ocurre después de la ejecución exitosa del bloque try o después del manejo de una excepción en el bloque catch. Debido a esto, puede estar seguro que cierra todos los recursos abiertos.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public void closeResourceInFinally() { FileInputStream inputStream = null; try { File file = new File("./tmp.txt"); inputStream = new FileInputStream(file); // Utiliza el InputStream para leer un archivo } catch (FileNotFoundException e) { log.error(e); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { log.error(e); } } } } |
Java 7: sentencia try con recursos
Otra opción es utilizar try con recursos, el cual puede utilizar si el recurso implementa la interface AutoCloseable. Esto es lo que la mayoría de los recursos Java hace. Cuando abres un recursos en la sentencia try, este se cierra automáticamente cuando el bloque try se ejecuta o cuando se maneja una excepción.
1 2 3 4 5 6 7 8 9 10 11 | public void automaticallyCloseResource() { File file = new File("./tmp.txt"); try (FileInputStream inputStream = new FileInputStream(file);) { // Uso de InputStream para leer un archivo } catch (FileNotFoundException e) { log.error(e); } catch (IOException e) { log.error(e); } } |
[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
2. Utilizar Excepciones específicas
Cuanto más específica es la excepción que arrojas, es mejor. Siempre tenga en cuanta a los integrantes del equipo de desarrollo, o tal vez usted mismo en el futuro, necesita llamar a su método y manejar la excepción.
Por lo tanto, asegúrese de proporcionar la mayor cantidad de información posible. Eso hace que su API sea más fácil de entender. Y como resultado, la persona que llama a su método podrá manejar la excepción mejor o evitarla con un control adicional.
Por lo tanto, siempre intente encontrar la clase que mejor se adapte a su evento de excepción, por ejemplo, lanzar un NumberFormatException en lugar de un IllegalArgumentException. Y evite lanzar una excepción no especificada.
1 2 3 | public void doNotDoThis() throws Exception { ... } public void doThis() throws NumberFormatException { ... } |
[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
3. Documente las excepciones que especifique
Siempre que especifique una excepción en la firma de su método, también debe documentarla en su Javadoc. Tiene el mismo objetivo que la anterior recomendación: proporcionar a quién la utilice tanta información como sea posible para que pueda evitar o manejar la excepción.
Por lo tanto, asegúrese de agregar una declaración @throws a su Javadoc y describir las situaciones que puedan causar la excepción.
1 2 3 4 5 6 7 | /** * Este método realiza ... * * @param input * @throws MyBusinessException if ... happens */ public void doSomething(String input) throws MyBusinessException { ... } |
[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
4. Lance excepciones con mensajes descriptivos
La idea detrás de esta buena práctica es similar a las anteriores. Pero esta vez, no proporciona la información a la persona que llama al método. El mensaje de la excepción será leído por todos los que tienen que entender que paso cuando la excepción fue reportada en el archivo log o la herramienta de monitoreo.
Por lo tanto, debe describir el problema con la mayor precisión posible y proporcionar la información más relevante para comprender el evento que causo la excepción.
Si lanza una excepción específica, el nombre de clase probablemente ya describe el tipo de error. Por lo tanto, no es necesario proporcionar mucha información adicional. Un buen ejemplo de esto es NumberFormatException. Lo lanza el constructor de la clase java.lang.Long cuando proporcionas un String en un formato incorrecto.
1 2 3 4 5 | try { new Long("xyz"); } catch (NumberFormatException e) { log.error(e); } |
El nombre de la clase NumberFormatException le indica el tipo de problema. EL mensaje sólo necesita proporcionar la cadena de entrada que causó el problema. Si el nombre de la clase de excepción no es tan expresivo, debe proporcionar la información requerida en el mensaje.
1 | 17:17:26,386 ERROR TestExceptionHandling:52 - java.lang.NumberFormatException: For input string: "xyz" |
[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
5. Captura la excepción más específica primero
La mayoría de los IDEs lo ayudan con esta buena práctica. Informan un código de bloque inalcanzable cuando intenta capturar la excepción menos específica primero.
El problema es que solo se ejecuta el primer bloque catch que coincide con la excepción. Entonces, si primer se detecta una IllegalArgumentException, nunca llegará al bloque catch que debería manejar la NumberFormatException más específica porque es una sub clase de IllegalArgumentException.
Siempre capture primero la clase de excepción más específica y agregue los bloques de captura menos específicos al final de su lista.
1 2 3 4 5 6 7 8 9 | public void catchMostSpecificExceptionFirst() { try { doSomething("A message"); } catch (NumberFormatException e) { log.error(e); } catch (IllegalArgumentException e) { log.error(e) } } |
[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
6. No capture Throwable
Throwable es la super clase de todas las excepciones y errores. Se puede utilizar en una cláusula catch, pero nunca debes hacerlo.
Si usa Throwable en una cláusula cath, no solo capturará todas las excepciones; también detectará todos los errores. La JVM produce errores para indicar problemas graves que no están destinados a ser manejados por una aplicación. Ejemplos típicos para eso son OutOfMemoryError o StackOverFlowError. Ambos son causados por situaciones que están fuera de control de la aplicación y no se pueden manejar.
Por lo tanto, no atrape un Trowable.
1 2 3 4 5 6 7 | public void doNotCatchThrowable() { try { // realice algo } catch (Throwable t) { // No haga esto. } } |
[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
7. No ignore excepciones
Nunca ignore una excepción, no sabe como cambiara el código a futuro. Alguien podría eliminar la validación que impidió el evento excepcional sin reconocer que esto crea un problema. O el código que arroja la excepción se cambia y ahora arroja múltiples excepciones de la misma clase, y el código de llamada no los previene a todos.
Al menos debería escribir un mensaje de registro indicando que lo impensable acaba de suceder y que alguien debe verificarlo.
1 2 3 4 5 6 7 | public void logAnException() { try { // do something } catch (NumberFormatException e) { log.error("This should never happen: " + e); } } |
[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
8. No registre una excepción para luego lanzarla
Esta es probablemente la recomendación más ignorada en esta lista. Puede encontrar código e incluso librerías en donde una excepción es capturada, registrada y luego lanzada.
1 2 3 4 5 6 | try { new Long("xyz"); } catch (NumberFormatException e) { log.error(e); throw e; } |
Puede ser intuitivo registrar una excepción cuando ocurrió y luego volver a lanzarla para que la persona que la llame pueda manejarla adecuadamente. Pero escribirá múltiples mensajes de error para la misma excepción.
1 2 3 4 5 6 7 | 17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz" Exception in thread "main" java.lang.NumberFormatException: For input string: "xyz" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Long.parseLong(Long.java:589) at java.lang.Long.(Long.java:965) at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63) at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58) |
Los mensajes adicionales tampoco agregan ninguna información. Como se explica la práctica recomendada N° 4, el mensaje de excepción debe describir el evento excepcional. Y el seguimiento de la pila le dice en que clase, método y línea se lanzo la excepción.
Si necesita agregar información adicional, debe capturar la excepción y envolverla en una personalizada.
1 2 3 4 5 6 7 | public void wrapException(String input) throws MyBusinessException { try { // do something } catch (NumberFormatException e) { throw new MyBusinessException("A message that describes the error.", e); } } |
Entonces, solo capture la excepción si requiere manejarla. En otro caso, indíquela en la firma del método y deje que el que llamo al método maneje la excepción.[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
9. Envuelva la excepción sin consumirla
A veces es mejor capturar una excepción estándar y envolverla en una personalizada. Un ejemplo típico para una excepción de este tipo es una excepción comercial específica de la aplicación o del framework. Eso le permite agregar información adicional y también puede implementar un manejo especial para su clase de excepción.
Cuando lo hagas, asegúrate de establecer la excepción original como la causa. La clase Excepcion proporciona métodos de constructor específicos que aceptan un Throwable como parámetro. De lo contrario, perderá el rastro de la pila y el mensaje de la excepción original que dificultará el análisis del evento excepcional que causó la excepción.
1 2 3 4 5 6 7 | public void wrapException(String input) throws MyBusinessException { try { // do something } catch (NumberFormatException e) { throw new MyBusinessException("A message that describes the error.", e); } } |
[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
Resumen
Como has visto, hay muchas cosas diferentes que debes considerar cuando lanzas o atrapas una excepción. La mayoría de ellos tienen el objetivo de mejorar la legibilidad del código o la usabilidad de la API.
Las excepciones son a menudo un mecanismo de manejo de errores y un medio de comunicación al mismo tiempo. Por lo tanto, debe asegurarse de hablar sobre las mejores prácticas y reglas de manejo de excepciones de Java que desee aplicar al equipo de trabajo para que todos entiendan los conceptos generales y los utilicen de la misma manera.
Este artículo se encuentra basado en 9 Best Practices to Handle Exceptions in Java[/vc_column_text][/vc_column][/vc_row]
Pingback: Java: Errores comunes que se deben evitar al manejar Excepciones
Pingback: Java: Implementar Excepciones Personalizadas