domingo, 1 de marzo de 2009
Es 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:
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
Es importante, en este caso, incluir la excepción original en la excepción lanzada, para lo que debemos llenar la propiedad
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:
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
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:
En esta ocasión, si se produce la excepción
Publicado en: www.variablenotfound.com.
- 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
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.
Publicado por José M. Aguilar a las 11:51 p. m.
Etiquetas: buenas prácticas, c#, desarrollo, programación
5 Comentarios:
Muy interesamnte, no sabia que para enviar una excepcion tuviera tantas opciones distintas. Yo siempre he utilizado el segundo metodo pero no me habia parado a pensar lo que implicaba.
Un saludo.
JRL
¡Muy buen post!
Para mi tiene especial relevancia el último apartado que comentas, el lanzar la excepción con el throw directamente. Creo lo más lógico es que si queremos rebotar la excepción la intentemos enviar con la misma información posible, incluyendo el StackTrace
Soy seguidor de tu blog desde hace tiempo. Estoy probando de usar el tercer método para relanzar excepciones y parece q al eclipse no le gusta mucho. Uso java 5, no sé si esa podria ser la causa.
Hola!
Efectivamente, las técnicas descritas corresponden al lenguaje C#, pero no lo había dicho en ningún sitio...
He modificado el título para evitar confusiones.
Saludos y gracias por comentar.
Muy interesante la introducción a una gestión adecuada de las excepciones, una gestión que debería quedar clara para que se usase de forma homogénea dentro de los proyectos de desarrollo.
En particular, el detalle de "rellenar" la InnerException es algo que suele dejarse de lado cuando creamos una nueva excepción con información adicional, y que no debería de hacerse.
Otra forma de dar mayor información y control al usuario o desarrollador que desee indagar sobre los motivos por los que se produjo la excepción es utilizar la propiedad .Data que incluyen las clases Exception, y que permite incorporar a modo de diccionario una serie de información adicional sobre el error sufrido.
Un saludo.
Enviar un nuevo comentario