Freigeben über


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.

Comments

  • Anonymous
    July 01, 2010
    excelente articulo!!!!

  • Anonymous
    July 01, 2010
    Muy buen post Walter... la verdad siempre he intentado seguir estas reglas y la verdad uno se aconstumbra con el paso del tiempo.. y como dices, es mejor validar antes de usar try - catch...

  • Anonymous
    July 09, 2010
    Hola walter, Tengo una duda con el Exception Handling Application Block Hands-On Labs for Enterprise Library, para ser mas preciso si intenta ejecutar el Laboratorio Final del ejercicio 1 y 2 arroja errores, como si el laboratorio no funcionara, para el caso del ejercicio 1 cuando se intenta lanzar la excepción por medio de la clase Throw produce un error ya que no encuentra el manejador del evento, y en el ejercicio 2 pasa igual solo que esta vez con las directivas de seguridad, quisiera saber en donde esta el error ya que se supone que el proyecto en la version final deberia funcionar sin problemas. Estoy ejecutando el proyecto con VST2010 Framework 3.5 y 4.0, De antemano muchas gracias por su tiempo.

  • Anonymous
    July 11, 2010
    Don walter, muy bueno el articulo y muy inclusive para mi, solo una cosa, he visto que es muy comun usar codigos de error, ¿por que no se recomienda? ¿Que estrategia seria la adecuada? Muchas gracias y muy buen articulo

  • Anonymous
    July 12, 2010
    Muchas Gracias Oscardo.

  • Anonymous
    July 12, 2010
    Así es Ricardo, Jorge-...  Digo, Julio! La práctica hace al maestro ;)

  • Anonymous
    July 12, 2010
    Hola Joseph. Estuve probando los HOL en una máquina y me funcionaron correctamente. Me puedes por favor mostrar el error que te sale?

  • Anonymous
    July 12, 2010
    @Odahir! Viejo Odahir.... que bueno tenerle por aquí... Cuando uno bota códigos de error, automáticamente se embarca en la tarea de generar lo mecanismos para administrar esos códigos. Creación de nuevos códigos para nuevas excepciones, enseñar las tablas a los clientes de la aplicación, sincronizar dichos códigos cuando cambian... y todos los problemas que ello puede generar. Por ejemplo que un cliente tenga la v1.0 de la tabla de códigos de error, y al acceder a ala v2.0 hagan falta códigos, generándose entonces una excepción sobre excepciones... Los códigos de error fueron una gran alternativa en ambientes donde las excepciones no existían (VB, C, T-SQL) Pero en un lenguaje administrado como C# o VBNet, el mismo framework ofrece un conjunto de de excepciones que representan exactamente lo que sucedió (aquí se pueden ver algunas: www.developerfusion.com/.../3) y a las que además podemos enriquecer agregándoles datos inherentes a la excepción ocurrida; sin mencionar que por ejemplo también tenemos información del stacktrace, de manera que los clientes siempre se pueden enterar exactamente de qué fue lo que pasó sin necesidad de recurrir a tablas de equivalencia que a veces no tienen o están desactualizadas.

  • Anonymous
    July 23, 2010
    Algunos de los lineamientos que uso a la hora de pensar en excepciones son:

  • En cuanto a lanzar excepciones, hay que pensar que las excepciones son excepcionales, por lo tanto, solo deberían lanzarse cuando no se puede cumplir el propósito del método (que debería estar expresado en el mismo nombre del método) por alguna situación.
  • Respecto a capturar y manejar una excepción, normalmente debería dejarse esto a "la capa de presentación", preferiblemente mediante un manejador centralizado. Solo tendría sentido atrapar y manejar la excepción en niveles inferiores si realmente se puede hacer algo allí para solucionar la situación o al menos agregar más detalles para que sea más entendible o algo así.
  • Anonymous
    July 24, 2010
    The comment has been removed

  • Anonymous
    August 04, 2010
    @Warnov Tal vez debí ser más detallado, mencioné "la capa de presentación" entre comillas porque precísamente quería hacer referencia al escenario típico de una aplicación en .NET, pero digamos que quize decir la capa de más alto nivel bajo nuestro control, lo importante es tener un manejo centralizado de las excepciones sobre las que no se puede hacer nada por "realmente solucionar" el problema (no simplemente mandarlo al log) o al menos mejorar la información que provee (ej. agregando más detalles o cambiando la excepción por una más específica). Cuando digo que deben subir estas excepciones a un nivel más alto, incluso a la presentación, no me refiero a que sean mostradas al usuario tal cual, porque estas son realmente errores que no son responsabilidad del usuario de la aplicación final, sino del desarrollador que no previó y controló alguna situación o problemas de infraestructura física, por ejemplo. Nuevamente, me refiero es a que se les dé un manejo centralizado y no disperso, en cada lugar en donde se pueden producir. Por otro lado, idealmente uno no debería manejar excepciones en el negocio, si no aplican las excepciones que he comentado antes (que uno pueda solucionar o mejorar la excepción) porque precísamente se trata del "negocio" y deberíamos hacer todo lo posible por no contaminarlo, por ejemplo, la excepción que pones como ejemplo ("Object Reference not Set to an instance of and Object") no tiene nada que ver con una clase para realizar asientos contables, entonces aún si se produce en esta clase, no deberíamos ponerle la responsabilidad de enviar a un log o algo así.

  • Anonymous
    August 04, 2010
    Para continuar con el mismo ejemplo, ¿cómo manejarías la excepción "Object Reference not Set to an instance of and Object"? si ocurre digamos en un método 'AsentarRegistro' en una clase AsientosServicio o algo así, en una app sencillita y clásica de 3 capas en ASP.NET.

  • Anonymous
    September 08, 2010
    The comment has been removed

  • Anonymous
    September 08, 2010
    @Eduardeno Ballesteros Saludos Eduardo: Te agradezco, pues tu inquietud me ha dado para poner otro postg que explica las razones de mis afirmaciones acerca de los bloques try catch. Encuentralo aquí:  blogs.msdn.com/.../por-qu-233-los-bloques-try-catch-s-237-afectan-el-performance.aspx

  • Anonymous
    October 12, 2010
    Amigo muchas gracias por sacarme de la duda, pues siempre acostumbro a meter la mayor parte de mi codigo dentro de bloques try, catch. Gracias

  • Anonymous
    October 12, 2010
    @Eduardo Es con mucho gusto. Exitos!

  • Anonymous
    December 14, 2011
    The comment has been removed

  • Anonymous
    December 13, 2012
    Muchas gracias @Cesar Verano por tus apreciaciones!

  • Anonymous
    December 30, 2016
    Esto se convertirá en mis diez mandamientos :) gracias.

    • Anonymous
      January 19, 2017
      Es con todo gusto caveman!!