Es usual para un programador novato y con prisa, resolver el tema de las excepciones simplemente suprimiéndolas o atrapándolas para únicamente registrarlas en bitácoras (logs) y lanzarla hacia arriba para repetir el proceso en las clases subsecuentes. Puedo afirmar esto porque yo mismo solía manejar las excepciones de esta manera. Uno puede pensar al principio que esto es lo más considerado, "loguiemos por si acaso".
public class A {
public void callB() throws Exception{
try {
new B().doSomething();
} catch (Exception exception) {
exception.printStackTrace();
throw Exception;
}
}
}
public class B{
public void doSomething() throws Exception {
try {
// Code potentially exception throwing.
} catch (Exception exception) {
exception.printStackTrace();
throw Exception;
}
}
}
Esta es una práctica común que parece inofensiva al principio en las etapas de desarrollo, pero no es hasta que nos encontramos analizando un problema de la aplicación corriendo en producción que nos damos cuenta de la mala idea que es tirar excepciones a lo loco. Cuando tenemos que investigar un problema de la aplicación que require ir a buzear en los archivos de bitácora, las múltiples apariciones de una sola excepción en varias clases produce un colocho mental para aquel que tiene que interpretar los registros de excepciones.
Además esto agrega ruido para cualquier actividad de monitoreo de las bitácoras. La excepción es multiplicada por el número de clases que invocan el método originador de esta, y cada clase no agrega ninguna información adicional de relevancia. Este confeti de excepciones crea la percepción de que el problema mostrado por la excepción es más grave de lo que parece. Esto dificulta la tarea de dirigir los esfuerzos al problema real.
Sugiero dos opciones para mejorar esto dependiendo de las necesidades de la aplicación:
1. Registrar en bitácoras solamente en la capa donde la excepción es producida y arrojar una excepción de tipo RuntimeException. Una excepción de tipo RuntimeException no necesita ser atrapada.
public class B{
public void doSomething() throws Exception {
try {
// Code potentially exception throwing.
} catch (Exception exception) {
exception.printStackTrace();
throw new RuntimeException(exception);
}
}
}
2. Lo otro que podemos hacer, sobre todo si las capas de arriba necesitan enterarse de que una excepción ocurrió, es crear nuevas excepciones (conocidas también como "wrappers") acuerdo con un grupo de categorias que facilitan la lectura de las bitácoras. En el siguiente código muestro un ejemplo con dos excepciones personalizadas: una cuando la culpa recae en el usuario debido a una mala especificación de los parámetros, y otra para encapsular las excepciones que se deben a una invocación remota.
public class ConfigurationException extends Exception {
}
public class RemoteServiceException extends Exception {
}
public class B{
public void doSomething(String mathFormula) {
try {
// Code to execute formula.
} catch (Exception exception) {
throw new ConfigurationException(exception);
}
}
public void doSomethingRemotely() {
try {
// Code to read Web Service.
} catch (Exception exception) {
throw new RemoteServiceException(exception);
}
}
}
public class A {
public void callB() {
try {
B b = new B();
b.doSomething("filePath");
b.doSomethingRemotely();
} catch (ConfigurationException configurationException) {
System.out.println("There was a configuration error!");
exception.printStackTrace();
} catch (RemoteServiceExceptionremote) {
System.out.println("There is no connectivity with remote services!");
exception.printStackTrace();
}
}
}
Al agregar otro nivel de abstracción ayudamos en las actividades de CSI (escena del crimen), especialmente cuando la gente que le toca esto no es la misma que la gente del equipo de desarrollo.