Buff... cuanto tiempo desde mi último post, entre estudiar y la medio-gripe que arrastro desde hace unas semanas no tengo ganas tiempo de nada :P
Un tema que siempre me ha parecido que se deja un poco olvidado a la hora de desarrollar una aplicación es el tema de las excepciones, su flujo, su registro y como afectan al diseño y modularidad de la aplicación.
Una excepción, es un error en el flujo lógico ideal de nuestra aplicación causado por una condición inusual. Ante un error de este tipo, debemos actuar para corregirlo y/ó recuperar un estado consistente ó registrarlo en algún lugar(archivo log, EventLog, ..etc..) donde el administrador pueda ver que ha sucedido y tomar medidas. Esto es algo bien sabido por todos, pero el problema viene cuando hay que decidir que hacer con las excepciones que no podemos solucionar y como registrar de forma descriptiva el problema para poder solventarlo.
El objetivo de capturar una excepción, es enfrentarse a una situación inesperada, por lo que debemos capturar las excepciones que podamos solucionar y las que no, dejarlas subir por la pila de llamadas hasta el punto donde se registren como un error no controlado. Por este motivo, esta ampliamente desaconsejado capturar de forma generalizada:
try
{
// lógica...
}
catch (Exception)
{
// Todas las excepciones son capturadas..
}
Sin embargo, hay situaciones donde es vital capturar la excepción aún cuando no se puede hacer nada para contrarrestarla, por ejemplo cuando usamos transacciones, es vital capturarla para deshacer las operaciones realizadas. En estos casos, una vez tomadas las acciones oportunas podemos re-lanzarla de nuevo:
MessageQueueTransaction tran = new MessageQueueTransaction();
try
{
tran.Begin();
// Trabajar en el contexto de la transacción
tran.Commit();
}
catch (Exception)
{
// Ha habido problemas, realizamos un rollback
tran.Abort();
// Re-lanzamos la excepción para que sea tratada
// en capas superiores.
throw;
}
finally
{
tran.Dispose();
}
Antaño (VB6, ASP,..etc...) era necesario capturar la excepción allí donde ocurriese para poder registrar el punto donde sucedió, pero las excepciones de .NET Framework (que derivan todas de la clase Exception) contienen la propiedad StackTrace que registra la consecución de llamadas que han sucedido en la pila hasta toparse con dicha excepción. Sabiendo esto, no necesitamos poner bloques try-catch en cada una de nuestras funciones para saber el lugar exacto donde sucedió.
Cuando se vaya a re-lanzar una excepción es importante hacerlo simplemente con "throw;" ya que preserva la información del StackTrace, NO como el siguiente ejemplo, ya que se perdería el StackTrace original:
catch (Exception ex)
{
// Ha habido problemas, realizamos un rollback
tran.Abort();
// MAL: Así perdemos el StackTrace original.
// Sería más difícil encontrar el problema.
throw ex;
}
Cuando se quiera usar tipos de excepciones propios ó simplemente otro para dar mensajes de error más descriptivos y/ó completos, podemos usar la propiedad InnerException para poder mantener la información original del error:
catch (ArgumentNullException ae)
{
throw new ArgumentException("El objeto no era válido", ae);
}
De esta forma, se mantiene la información del error original.
No se debe utilizar las excepciones como elementos de nuestra lógica y debemos evadirlas en la medida de lo posible ya que el uso de excepciones implica una penalización de rendimiento. Cuando digo evadirlas, me refiero a que si esperamos un error muy concreto... mejor procurar que el error no suceda, por ejemplo:
static Int32 Divide(Int32 a, Int32 b)
{
Int32 result = 0;
try
{
result = a / b;
}
catch (DivideByZeroException)
{
result = -1;
}
return result;
}
En este sencillo ejemplo, contemplamos la posibilidad de que la variable 'b' sea 0 y una excepción de tipo DivideByZeroException ocurra. Aunque se toman medidas para devolver un posible valor inocuo, se podría evitar fácilmente:
static Int32 Divide(Int32 a, Int32 b)
{
Int32 result = 0;
if (b == 0) return -1;
result = a / b;
return result;
}
El como registrar las excepciones no controladas es algo ya más dependiente de los requerimientos del proyecto ó conveniencia técnica, se puede usar desde un simple archivo de texto, el EventLog ó por ejemplo el Logging Application Block de Enterprise Library 3.1 (del que espero escribir en breve... si no las pincho antes xD).
Como esto se hace largo ya... en un próximo post escribiré algo sobre donde y porque capturar excepciones de forma que no afecte a la modularidad de nuestra aplicación.
Links: