Buenas Prácticas en Manejo de Excepciones .Net

Hace poco me preguntaron acerca de cómo se debería de abordar el manejo de excepciones para una aplicación grande.

Me sentí agradado de que esa persona estuviera concientizada de que no es un tema que se puede dejar al azar y que hay que tener ciertos temas en cuenta.

Básicamente, su duda era acerca de cómo capturar unificadamente todas las excepciones producidas en su aplicación; luego de alguna investigación, llegó a saber de Application.ThreadException; un evento al que se le puede poner un manejador y allí tomar las acciones necesarias. Sin embargo, personalmente no recomiendo esta solución. Por qué?

1. La notificación de la excepción ocurre muy tarde: cuando se recibe la notificación, la aplicación ya no podrá responder a la excepción.

2. La aplicación terminará abruptamente si la excepción ocurre en el hilo principal o en cualquier otro hilo que sea iniciado por código no administrado (el hilo principal de la aplicación obviamente es ejecutado por Windows – no administrado)

3. No tenemos acceso a ninguna información valiosa acerca del error, excepto la excepción misma. No se podrán cerrar conexiones a bases de datos, hacer rollback de transacciones ni nada útil.

Así que mi recomendación es nunca basar el manejo de excepciones en los eventos Application.ThreadException ni AppDomain.UnhandledException. Ellos deben ser usados y existen como las redes de seguridad de los acróbatas; su función es la de hacer un registro de la excepción en algún mecanismo de log, para alguna examinación futura. Así que hay que darse la pela y manejar cada excepción en su sitio y actuar en concordancia.

Esta última invitación conlleva a tener en cuenta otra serie de factores que generalmente se omiten cuando uno comienza a desarrollar “en forma”:

1. El código metido entre try, catch se ejecuta muy lento. Consume muchos recursos. Así que sólo manejemos excepciones cuando realmente se necesite. Cuándo es eso?
Las excepciones están construidas para manejar condiciones que no se pueden controlar con la lógica de la aplicación. Por ejemplo que se cae la conexión con el servidor, o que el disco está lleno, o que el hardware falló…
Antes de lanzar excepciones por todo, fijémonos si realmente es necesario. Evitemos también poner mucho código en el try; solo afectemos con el try el código que realmente puede fallar. Por ejemplo es preferible:

 if (conn.State != ConnectionState.Closed)
{      
   conn.Close(); 
} 

a:

 try
{
        conn.Close(); 
} 
catch (InvalidOperationException ex)
{
     Console.WriteLine(ex.GetType().FullName);
     Console.WriteLine(ex.Message);
} 

2. No emita Exception(). O es que eso le da mucha información?

El framework .net tiene un número de excepciones que nos permite controlar argumentos y operaciones inválidas, timeouts, conexiones, overflows, etc. Usemos esas excepciones que realmente nos indican qué fue lo que sucedió.

3. Use bloques try/finally. Recuerde que las instrucciones en el Finally siempre se ejecutan independientemente de si se produjo o no la excepción. Esto es muy útil para liberar recursos, cerrar conexiones, etc.

4. Siempre use un catch para cada tipo de excepción que se pueda generar y ordénelos desde el más específico hasta el menos específico. Esto ayuda a identificar plenamente el error que se generó y no terminar con excepciones como: “Object reference not set to a instance of an Object”.

5. Cuando cree excepciones propias del aplicativo, asegúrese de distribuir el Assembly a una ubicación compartida para todos los posibles dominios que tengan que ver con ellas.

6. De acuerdo a como el mecanismo de excepciones funciona, es muy importante siempre que creemos excepciones propias, proveerlas al menos con los tres constructores de excepciones básicos.

7. Siempre es recomendable usar las excepciones de .Net. Las personalizadas solo tendrán que ver con escenarios programáticos.

8. Siempre se ha pensado que para las excepciones personalizadas se debería heredar de ApplicationException; sin embargo en la práctica se ha demostrado que no se agrega valor significativo y además se pierde cierto performance. Así que es mejor heredar siempre de Exception.

9. Use mensajes gramáticamente correctos.

10. Las excepciones pueden incluir propiedades. Estas propiedades pueden ser accedidas programáticamente para tomar acciones. Incluya información extra relevante en estas propiedades cuando sea útil.

11. En vez de retornar null, lance excepciones en la mayoría de casos. Eso evita inconvenientes que a veces son difíciles de detectar. Evite también retornar códigos de error.

Estas son solo unas pocas recomendaciones a la hora de manejar excepciones. Hay muchas más que se aprenden con el pasar de las líneas de código.

Para finalizar, me parece importante mencionar también que igual existe un Application Block en Patterns And Practices para el manejo de Excepciones. Incluyéndolo en nuestras aplicaciones ya de por sí garantizaremos varias buenas prácticas; eso sí, usandolo tal y cual se explica en los lineamientos.