Buenas prácticas: Tratamiento de Excepciones

Entrada rápida que resume algo que ya he escrito en otros sitios, y que me gustaría tener público (¡y recibir comentarios!).

Criterios a tener en cuenta al tratar excepciones en Java:

  • Es mejor que la aplicación falle a no saber que ha fallado (y qué ha fallado). Corolario: si no vas a tratar una excepción correctamente, mejor no hagas nada y que la aplicación falle.
  • Siempre que se produzca un error en la aplicación tiene que haber información sobre el error en pantalla, al menos indicando que se ha producido un error. Corolario: no te limites a trazar el error en el log, ¡no siempre habrá alguien mirando!
  • No traces mediante System.out o System.err, utiliza un sistema de logging.
  • Si sólo vas a relanzar la excepción, no la relances, haz que el método lance ese tipo de excepción.
  • No loguees y relances constantemente, no es necesario. Los únicos puntos que no deben lanzar excepciones son los métodos invocados directamente por la aplicación, para que no le llegue al navegador la traza del error. Y si utilizas un framework que gestione bien las excepciones (como Seam), incluso puedes saltarte esta norma.
  • Cuando escribas en el log, escribe información significativa, como los parámetros recibidos por el método. Lo trivial (el nombre del método, por ejemplo) ya se verá en la traza.
  • Al trazar, no concatenes el mensaje de la excepción, vuelca la excepción en sí (toda la traza).
  • Un método no debería declararse como throws Exception, ya que enmascara todas las excepciones, sin permitir gestionar el error concreto. No pasa nada, por ejemplo, por que lance varias: throws IOException, MiOtraException. De esta forma, el que la invoque sabrá a qué tipos de error se debe enfrentar.
Contraejemplos (ejemplos de cómo no se deben hacer las cosas):
  • } catch(Exception e) { }; : se ocultan los errores, es imposible saber qué ocurre.
  • } catch(Exception e) { System.err.println(e) }; : estamos trazando en err, ni se ve nada en pantalla ni en el log, así que en explotación no sabremos qué ocurre.
  • } catch(Exception e) { log.debug("Ha ocurrido algo malo: "+e); }; : el nivel de log es incorrecto (debería ser error), no se dan datos sobre el error, estamos concatenando la excepción, no estamos relanzando...
  • } catch(Exception e) { log.error(e) }; : no se dan datos sobre el error, y no estamos lanzando nada, así que en pantalla no se verán los errores.
Ejemplos válidos en otras circunstancias:
  • } catch(Exception e) { log.error("Identificador: "+id, e); throw new MiExcepcion(e) }; : trazamos el error (la excepción) e indicamos un parámetro que puede estar provocando el error. Además, lanzamos una excepción (que contiene la provocada, para no perder traza) para que el nivel superior pueda seguir tratando el error. Esto sería incorrecto, de todas formas, si en nuestro sistema esto vuelca la traza en el interfaz de usuario. Aparte, probablemente incluso sea innecesario hacer esto (ver último ejemplo correcto).
  • } catch(Exception e) { log.error("Identificador: "+id, e); anhadirMensajeDeErrorAlUsuario(e) }; : trazamos el error (la excepción) e indicamos un parámetro que puede estar provocando el error. Además, mostramos un mensaje de error en pantalla. Esto sería incorrecto, de todas formas, si este método es invocado por un nivel que necesita procesar el error específicamente.
  • ...) throws IOException : si el método no va a hacer nada interesante con la excepción, ¿para qué capturarla?
Actualización 0901131738: este es un tema del que se pueden encontrar infinidad de fuentes, como en java.net, con más detalle, en inglés.

Posted by Juan Ignacio Sánchez Lara 16:04  

5 Comments:

  1. javi santana said...
    Es que para mi no debería haber excepciones. Nunca las había necesitado hasta que empecé a programar en java y usándolas no he encontrado ninguna mejor en la productividad, muchas veces todo lo contrario.
    Mariano said...
    Estoy de acuerdo en la casi totalidad de la publicación, y como programdor novel que soy también me sirve para mejorar mi código, puesto que estamos en constante evolución.

    Sí que me gustaría añadir en que hay casos particulares en que tan sólo nos interesa constatar que tenemos un error, y la misma traza nos da la solución(eso sí, con una gestión como la mencionada, jerárquica, mandando los mensajes a un log y lanzando donde se debe lanzar).
    Quiero decir con esto que hay casos que la disgregación de ciertas clases de excepción en un método, en mi experiencia personal, es menos productivo que la generalización.

    Está claro que hay que saber diferenciar los casos donde es aplicable, y cada uno sabrá qué tipo de método está gestionando, y si le conviene o no distinguir el tipo de excepción...

    Un saludo y una enhorabuena por este blog... pasarse de vez en cuando y poder aprender cosas aplicables, es algo muy interesante.

    Mariano.
    Jose Ignacio said...
    Creo también hay que tener en cuenta que no todas las excepciones son 'fatales', en el sentido que en ocasiones el 'try' realmente significa inténtalo (estilo 'best effort').

    Por ejemplo, un conversor de divisas podría intentar conectarse al webservice de la Fed. Y si esto genera una excepción, entonces usar el del Banco Central Europeo.

    Internamente, se podría loguear si la Fed ha dado un time out, una RemoteException, ..., pero de cara al usuario no sería necesario mostrarle ningún mensaje de error.


    Y sobre el qué poner en los logs, creo que cambia dependiendo de si es posible solucionar ese error sin tocar el código o no.

    En el primer caso compensa describir completamente el contexto, por ejemplo al estilo de "Se esperaba una edad numérica para 'Jose Perez' en usuarios.csv, sin embargo edad=Valladolid".

    Así, al guardar en la traza un log con todo el contexto y en lenguaje no telegráfico, facilitaremos que un sysadmin pueda solucionalo. Y al usuario podría mostrarsele un simple "Error: El fichero usuarios.csv no tiene un formato que esta aplicación pueda entender. Contacte con el servicio técnico".

    Si, por el contrario, para solucionar el error hay que meterse en las tripas del código, creo que lo suyo sería loguear simplemente "edad: Valladolid" y ya veremos nosotros como ha llegado esa variable a tener ese valor no numérico y cómo solucionamos la excepción.
    Nacho said...
    @javi_santana : las situaciones de error suceden, y la alternativa a las excepciones, los códigos de error, no me convencen. Bien usadas, las excepciones son más claras.

    @Mariano : muchas veces, para símplemente logear y relanzar, mejor no hacer nada, y que sea el nivel que deba hacerlo el que capture y trace.

    @José Ignacio : cierto, esta entrada sólo tenía recomendaciones sobre los casos puramente de errores fatales, pero hay una máxima en Java que hay que respetar: no se deben usar excepciones en el flujo normal de la aplicación. Es decir, no hay que usar un try { } catch como si fuese un if { } else. Dicho esto, estoy de acuerdo con lo que comentas.
    javi santana said...
    """
    Bien usadas, las excepciones son más claras.
    """
    y yo te respondo:
    """
    Bien usadas, las $ponga_aquí_su_tecnología$ son más claras.
    """
    El problema es que java por poner un ejemplo es un lenguaje completamente orientado a excepciones. Por ejemplo, si falla la apertura de un fichero, lanza una excepción. Para mi no es una excepción, es el flujo normal del programa. Una excepción es un que un malloc te retorne 0 y alguna que otra cosa más (vamos, más o menos las RuntimeException de java)

    Una de las cosas por las que triunfan algunos frameworks (Ror por ejemplo) es porque precisamente evitan que hagas lo que quieras, te dan un camino. Las escepciones no son un camino, son piedras en él. Ese es el truco.

    Y el que no tenga un:
    try
    {
    //codigo
    } catch(Exception e)
    {/* nada */}

    que tire la primera piedra

Post a Comment