Formas de relanzar excepciones en C#

PuzzleEs bastante habitual encontrar código que captura una excepción y la vuelve a relanzar tras realizar algún tipo de operación. Sin embargo, habréis observado que existen varias fórmulas para hacerlo, y no necesariamente equivalentes:

  • crear y lanzar una nueva excepción partiendo de la original
  • relanzar la excepción original
  • dejar que la excepción original siga su camino

El primer caso ocurre cuando capturamos una excepción de un tipo determinado, y desde dentro del mismo bloque catch instanciamos y lanzamos otra excepción distinta a la original. Esto permite elevar excepciones a niveles superiores del sistema con la abstracción que necesita, ocultando detalles innecesarios y con una semántica más apropiada para su dominio.

El siguiente ejemplo muestra una porción de código donde se están controlando distintos errores que pueden darse a la hora de realizar una transmisión de datos. Por cada uno de ellos se llevan a cabo tareas específicas, pero se elevan al nivel superior de forma más genérica, como TransmissionException, pues éste sólo necesita conocer que hubo un error en la transmisión, independientemente del problema concreto:

  ...
try
{
transmitir(datos);
}
catch (HostNotFoundException ex)
{
sendToAdmin(ex.Message); // Avisa al administrador
throw new TransmissionException("Host no encontrado", ex);
}
catch (TimeOutException ex)
{
increaseTimeOutValue(); // Ajusta el timeout para próximos envíos
throw new TransmissionException("Fuera de tiempo", ex);
}
...

 
Es importante, en este caso, incluir la excepción original en la excepción lanzada, para lo que debemos llenar la propiedad InnerException (en el ejemplo anterior es el segundo parámetro del constructor). De esta forma, si niveles superiores quieren indagar sobre el origen concreto del problema, podrán hacerlo a través de ella.

Otra posibilidad que tenemos es relanzar la misma excepción que nos ha llegado. Se trata de un mecanismo de uso muy frecuente, utilizado cuando deseamos realizar ciertas tareas locales, y elevar a niveles superiores la misma excepción. Siguiendo con el ejemplo anterior sería algo como:

  ...
try
{
transmitir(datos);
}
catch (HostNotFoundException ex)
{
sendToAdmin(ex.Message); // Avisa al administrador
throw ex;
}
...

 
Este modelo es conceptualmente correcto si la excepción que hemos capturado tiene sentido más allá del nivel actual, aunque estamos perdiendo una información que puede ser muy valiosa a la hora de depurar: la traza de ejecución, o lo que es lo mismo, información sobre el punto concreto donde se ha generado la excepción.

Si en otro nivel superior de la aplicación se capturara esta excepción, o se examina desde el depurador, la propiedad StackTrace de la misma indicaría el punto donde ésta ha sido lanzada (el código anterior), pero no donde se ha producido el problema original, en el interior del método transmitir(). Además, a diferencia del primer caso tratado, como la propiedad InnerException no está establecida no será posible conocer el punto exacto donde se lanzó la excepción inicial, lo que seguramente hará la depuración algo más ardua.

La forma de solucionar este inconveniente es utilizar el tercer método de los enumerados al principio del post: dejar que la excepción original siga su camino, conservando los valores originales de la traza:

  ...
try
{
transmitir(datos);
}
catch (HostNotFoundException ex)
{
sendToAdmin(ex.Message); // Avisa al administrador
throw;// Eleva la excepción al siguiente nivel
}
...

 
En esta ocasión, si se produce la excepción HostNotFoundException, los niveles superiores podrán examinar su propiedad StackTrace para conocer el origen concreto del problema.

Publicado en: www.variablenotfound.com.

4 comentarios sobre “Formas de relanzar excepciones en C#”

  1. Una pregunta José:

    Siguiendo cualquiera de estos 3 métodos…. Si yo tengo un método A, que llama a un método B, que llama a un método C…. así hasta Z….

    ¿Debería gestionar excepciones con Try/Catch en todos los métodos?. ¿Como me podía ahorrar esto?…. ¿Eventos de aplicaciones, Global.asax,…?. Esto me ahorraría poner try/catch en todos los métodos de mi aplicación (que pueden ser miles). Y solo codificar en un solo sitio (y ahí avisar al usuario con un mensaje modal, guardar un log de error y para la ejecución). ¿Esto es posible?. ¿Y para Compact Framework, donde no hay ni eventos de aplicación ni global.asax?.

    …. muchas preguntas… ;-). Un saludo!!!

  2. Hola José:

    Buen artículo. Sobre todo el incapíe en incluir la información de la excepción original. Muchas veces se olvida este importante detalle.

    Me gustaría comentar una cosa. No es una buena práctica logear o ‘avisar al administrador’ cuando las excepciones se relanzan.

    Supón que tu logeas y relanzas la excepción, una función más arriba se vuelve a logear y a relanzar y así… al final el log acaba lleno de ruido y el administrador recibe cientos varios mails para la misma excepción. Esta situación es desconcertante.

    Entiendo que tu código no es código de producción y que es un mero ejemplo en un blog, pero creí interesante comentar esto.

    En cierto modo esto también responde la consulta de Jorge. Si puedes hacer algo con una excepción, manejala, si solo puedes relanzarla, mejor dejar que simplemente se propague. La mejor manera de manejar las excepciones es protergerse de que estas ocurrand. Por ejemplo: En lugar de esperar a recibir una excepción si un archivo no existe es mejor comprobarlo de antemano.

    Para completar la información, decir que escribí sobre el tema de manejo de excepciones hace tiempo. Quizás a alguien le interese:

    Antipatrones en el trabajo con excepciones (I)
    http://geeks.ms/blogs/rcorral/archive/2006/05/09/233.aspx

    Antipatrones en el trabajo con excepciones (II)
    http://geeks.ms/blogs/rcorral/archive/2006/05/12/262.aspx

    Antipatrones en el trabajo con excepciones (y III)
    http://geeks.ms/blogs/rcorral/archive/2006/06/01/409.aspx

    ¡Un saludo!

  3. Hola, gracias por comentar.

    @Jorge, Rodrigo ha expuesto perfectamente lo que, también en mi opinión, es la forma correcta de hacer las cosas. Sólo debes capturar una excepción si realmente puedes hacer algo con ella; capturarla para relanzarla no tiene mucho sentido.

    @Rodrigo: efectivamente era sólo un ejemplo, pero está muy bien traído el tema del ruido en logs o avisos al relanzar excepciones, pues pueden producirse situaciones de «bombardeo» nada beneficiosas. Hay que ser precavido con esto, y asegurar que los errores dejan rastro, pero justo al nivel donde pueden aportar información útil, y, por supuesto, de forma única.

    Muy interesantes, además, los links que aportas tratando del tema.

    Un saludo.

  4. Muy interesante el post y lo que comenta Rodrigo en su serie de antipatrones.

    Es bastante frecuente encontrarse proyectos en los que no se ha diseñado una buena gestión de excepciones, algo importante en una buena arquitectura, sino simplemente las cosas se improvisan un poco.

    Yo añadiría a lo ya comentado que una buena razón para capturar una excepción, aparte de poder hacer con ella (reintentar conexiones fallidas por ejemplo), es añadir semántica a la excepción.

    Es decir, puede que haya una información muy útil que a la hora de depurar ayudaría tenerla presente. En este caso capturar y crear una nueva excepción (personalizada o no), añadirle este mensaje útil, envolver la excepción original y relanzarla es una buena práctica que hará más fácil la depuración.

    También comentar otro par de cosas que creo que son importantes, un principio básico como es el hecho de siempre empezar a capturar las excepciones de la más específica a la más general en bloques en los que se hayan puesto varios catch consecutivos (porque se ejecuten diferentes acciones según el tipo de excepción).

    Y finalmente que puede ser de ayuda (el arquitecto debe decidir si es relevante en cada proyecto) el hecho de crear excepciones personalizadas que incluyan información que puede ser de una utilidad crítica y que la clase Exception no provee por defecto, por ejemplo: saber con qué credenciales se ejecutó el thread de .NET que satisface una request de ASP.NET para averiguar posibles problemas de permisos o delegación de credenciales, etc. o incluir la versión del ensamblado concreto en que se produjo la excepción para controlar, en un escenario donde se han instalado muchos clientes, si concretamente uno tiene un ensamblado antiguo y la excepción ya estaba solucionada, con lo que deberá actualizar y no habrá que buscar errores que no existen.

    Son sólo dos ejemplos de cómo tener una excepción base personalizada con información relevante en la depuración de cada proyecto puede ser muy interesante.

    Saludos.

Responder a rcorral Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *