Dos tipos de cosas pueden lanzarse en una apliciación Java. Error y Exception.
Los Errores no se gestionan, cuando ocurren tiran abajo una aplicación. Cuando un Error es lanzado el estado del sistema se torna irreversible. No así las excepciones que sI permiten la recuperación del entorno donde se producen. Son gestionables y hay de dos tipos; Checked o Unckequed. Las primeras aparecen en la compilación. Necesitan de un "throws" en el método que las contiene o un "try/catch" que las recoja. Las segundas no aparecen en compilación ocurren en la ejecución del código. Son RuntimeException. La más conocida es NPE. En lo que sigue nos centraremos en la Chequed Exceptions.
Si pensamos en los diferentes escenarios en que tendremos que utilizarlas no hay muchos. En principio tendremos que tratarlas, lanzarlar o crearlas, con ó no exclusivo. Si llamamos a un método que lanza alguna excepción, checked se entiende, tenemos que tratarla poniendo un try/catch o un throws en nuestro método. Podemos tratarlas deteniendo la propagación de la excepción y decidir en el catch y quizá en el finaly el procedimiento a seguir. Podermos continuar la propagación creando una excepción que contenga la que nos vemos obligados a tratar y lanzarla hacia arriba. Otro escenario bien distinto es que dado cierto comportamiento en un método de un api que estamos desarrollando consideramos que es útil lanzar una excepción, creada por nosotros, para un informar a los posibles clientes. Visto lo mismo desde otro punto de vista podrían considerarse las excepciones en función de sí nos llegan y las gestionamos o somos nosotros su origen.
En lo que resta hablaremos del primer caso.
En lo que resta hablaremos del primer caso.
Qué hacer:
Expresividad- han de tener significado. Han de sugerir de donde viene el problema. Hay que percibirla dentro de un contexto, es decir, allí donde se relance es una capa de la aplicación y la excepción ha de hacer referencia a la misma. De manera genérica
java.io.BufferedInputStream.getBufIfOpen() throws IOException{byte[] buffer = buf;
if (buffer == null)
throw new IOException("Stream closed");
return buffer;
}
public FileOutputStream(File file, boolean append)
throws FileNotFoundException
{
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkWrite(name);
}
if (name == null) {
throw new NullPointerException();
}
fd = new FileDescriptor();
fd.incrementAndGetUseCount();
this.append = append;
if (append) {
openAppend(name);
} else {
open(name);
}
}
ya que :
private native void open(String name) throws FileNotFoundException;
private native void openAppend(String name) throws FileNotFoundException;
Por lo mismo cuando se utiliza una genérica suele añadirsele un mensaje aclaratorio que le de expresividad no así a la especifica que su mero nombre ya contiene. Generalmente cuando se persigue o preveé una funcionalidad especifica se crea una nueva excepción con su nombre especifico y cuando no simplemente se añade un mensaje a una más generica.
Encapsulado: Si se captura una excepción y se lanza otra esta segunda ha de correspoder al nuevo contexto, encapsulando la excepción original dentro del contexto explicito de la nueva excepción. Explicito por mostrarse en el nombre de una especifica o en el mensaje de una genérica.
Alcance: Los try no deben recoger solo la linea que produce la excepción. Deben recoger la funcionalidad que está incluida en la linea de donde sale la posible excepción.
Catch: es recomendable recoger errores especificos antes que genericos. Aunque si vemos largas lineas de código con infinitos catches probablemente nuestro error esté detrás. No hemos gestionado las excepciones donde debiamos, antes se entiende, y ahora pagamos las consecuencias.
Un salmo: relanza las excepciones lo más pronto posible y retenlas lo más tarde posible, cuando se tenga el máximo de información de que pasó y que ha de pasar. Si no sabes que hacer con una posible ckecked exception relanzala, encapsúlala y deja su catch definitivo para después.
Finaly: Para terminar que no falte. Si decidimos un curso de acción alternativo en caso de que se de la excepción esperada pondremos aquí el código a ejecutar. Evitar aquí , en la medida de lo posible, ejecutar código que pueda lanzar Excepciones.
Qué no hacer:
Log y Throw: Ha de ser Log o Throw, con o exclusivo. Sino se ensucian los logs y no se gana nada.
En la variedad no está el gusto: Lanzar varias Excepciones cuando la diferente información que proveen es irrelevante para el cliente o recogerlas si el curso de acción será el mismo. En este punto creo que la mejor opción es tener una Excepcion Genérica que abarque las diferentes.
Meter e.getMessage() en un log: Se pierde la traza y generalmente está a nulo el mismo mensaje.Tendría sentido si, habiendo mensaje, este nos indica una configuración indebida de la aplicación, una situación excepcional que no depende del contexto de ejecución sino del entorno de la aplicación. Es decir, no es necesario reproducir el error ya que este nos indica ipso facto su origen.
@Test
public void getMessageIntoLogAntiPatternTest(){
Object obj = null;
try {
obj.toString();
} catch (Exception e) {
assertTrue(e.getMessage() == null);
}
}
Meter el e.getCause() de la excepción recogida en la lanzada: Se pierde el stackTrace de la primera.
@SuppressWarnings("serial")
class MyException extends Exception{
public MyException(String s,Throwable t) {
super(s,t);
}
}
public void makeNullPointer() throws MyException{
Object obj = null;
try {
obj.toString();
} catch (Exception e) {
throw new MyException(e.getMessage(),e.getCause());
}
}
@Test
public void getMessageIntoNewExceptionAntiPatternTest(){
try {
makeNullPointer();
} catch (MyException e) {
String stackTraceMessage = null;
for(StackTraceElement staTra : e.getStackTrace()){
stackTraceMessage += staTra.toString();
}
assertTrue(!stackTraceMessage.contains("NullPointerException"));
}
}
Log y return null: Generalmente está mal pero no siempre. Se debería dejar al consumidor del método que lidie con las posibles excepciones.
Solo return null e ignorar el error. Se pierde la información de la excepción para siempre.
Referencias:
http://today.java.net/article/2006/04/04/exception-handling-antipatterns
http://www.wikijava.org/wiki/10_best_practices_with_Exceptions
http://marxsoftware.blogspot.com.es/2011/03/jdk-7-reflexion-exception-handling-with.html
http://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html