[C#] Lanzamiento de excepciones y excepciones neutrales

Pubicado en3 comentariosCategoríasUncategorized

Escribir una aplicación puede ser más o menos sencillo, en función de las necesidades de la misma, pero sin duda lo más engorroso es el control de errores. Hay que prever todos los posibles «gambazos» que puedan suceder durante el uso de nuestro programa y no solo del usuario, si no también del sistema operativo o la red si hacemos uso de ella.

Para ello Visual Studio, como seguramente ya sepamos, nos provee de un mecanismo de control de errores: los bloques try … catch … finally y las excepciones.

El problema

Normalmente cuando escribimos un programa vamos plagando cada método y evento de sus propios bloques try…catch, tanto generales –aquellos bloques try que abarcan todo el método o evento–, como particulares –que abarcan una porción más o menos grandes de código dentro del método o evento. Este planteamiento es perfectamente válido y lograremos tratar las excepciones sin demasiado problema… pero ¿estamos seguros de haber capturado todas las posibilidades existentes? ¿no nos hemos olvidado ningún evento, ningún método, ningún constructor? Si el programa es pequeño, no hay problema, es fácil de asegurar o de dar un vistazo nuevamente para ver si nos hemos olvidado de algo. ¿Pero y si el proyecto es titánico, con cientos de formularios, librerías, varios programadores trabajando simultáneamente en ello? ¿Cómo podemos asegurarnos de que todo es controlado?

Esa es una cara de la moneda, la otra es que el código queda más sucio al estar constantemente interrumpido de try y catches por todos lados y es más difícil gestionar bien y eficientemente las excepciones

La solución

Bueno, más que «la solución» digámosle «mi solución» ya que es lo que hago yo en mis programas y probablemente existan alternativas mucho mejores… pero a mí me funcionan y me sirven.

Cuando un programa se encuentra un error en una línea determinada el programa salta al marco superior intentando localizar un bloque try…catch que lo gestione. Si no lo encuentra comienza a ascender por la pila de llamadas hasta encontrar alguno y, finalmente, se llega hasta el Main(). Si en éste no hay ningún control el programa finalizará mostrándonos la famosa pantalla de «el programa ha encontrado un problema y debe cerrarse,  blablablá».

Aprovechándonos de este «escalado» de problemas podemos escribir un control de excepciones directamente en el Main() de nuestra aplicación. Con él nos aseguramos que, aunque el programa hubiese roto por algún sitio, será controlado en algún lugar y no dejará al usuario viendo el escritorio de Windows.

Un ejemplo sería el siguiente:

static void Main(string[] args)
{
   try
   {
       iniciaPrograma();  // aquí arranca nuestra aplicación.
   }
   catch (Exception ex)
   {
        if(MessageBox.Show("Ha ocurrido un error no controlado en la aplicación. ¿Desea reiniciar la aplicación?",
                                "Atención", MessageBox.Buttons.YesNo, MessageBox.Icons.Question) == DialogResult.Yes)
        iniciaPrograma();  //volvemos a lanzar la aplicción
   }
}

Vale, sí. El error ha sucedido y la aplicación se iniciará desde el principio… pero se juega con un efecto psicológico importante de los usuarios, o al menos de la mayoría de ellos. Hay que evitar siempre que tengan que arrancar de nuevo el programa. En mi experiencia programando y dando soporte a los usuarios es muy diferente que te llamen diciendo que el programa se les ha cerrado de repente a que te digan que el programa dio un mensaje y se les reinició. Creedme: MUY diferente.

Esta excepción se denomina excepción neutral.


Lanzando inteligentemente excepciones

En función de cómo sea nuestro programa no siempre vamos a poder mostrar un mensaje de error al usuario. Imaginemos una aplicación desarrollada en capas. Desde una capa de negocio o la capa de datos no tendremos acceso a los objetos de usuario, es decir, no podremos (más bien no deberíamos, si hemos aislado bien las capas) lanzar un MessageBox con el error.

¿Qué haremos? Lanzaremos nuestra propia excepción.

Imaginemos el siguiente escenario:

Tenemos nuestra aplicación con el formulario de clientes abierto. Le damos al botón guardar.

El botón guardar, por ejemplo btGuardar, ejecuta el evento btGuardar_Click que llama al método Guardar del formulario.

Éste botón llama al método público de la capa de negocio GuardarClientes y éste al de la capa de datos GuardarCliente() que efectua el grabado real en la base de datos. Puede que os resulte un poco lioso si no habéis desarrollado en varias capas. En ese caso no os hace demasiada falta lo que os estoy contando aquí, de todos modos en otro momento os explicaré las ventajas e inconvenientes de un proyecto en capas.

Ahora imaginemos que el servidor de bases de datos no está disponible en ese momento y obtenemos un TimeOut.

El método GuardarCliente sería algo tal que así:

public Boolean GuardarCliente(clsDatosCliente cliente)
{
   try
   {
       // ... operaciones de guardado
   }
   catch(Exception ex)
   {
      throw new Excepcion("Error al guardar en base de datos al cliente.", ex);
   }
}

con el throw new Exception lo que hacemos es crear una excepción personalizada, en la que establecemos nuestro propio mensaje y le pasamos al constructor como segundo parámetro la excepción completa que ha sucedido de verdad.

Esa excepción buscará un bloque de control superior y, al no encontrarlo, saltará en la pila hacia atrás, retornando a la capa de negocio y al evento GuardarClientes(). Éste podría tener el siguiente aspecto:

public Boolean GuardarClientes(clsDatosCliente cliente)
{
   try
   {
     // llamada a la capa de datos para guardar y otros menesteres...
   }
   catch(Exception ex)
   {
     throw new Exception("Error en capa de negocio. Error guardando al cliente." ,ex);
   }
}

El programa «aterriza» de nuevo en el método en busca de un bloque try, y lo encuentra saltándo al bloque catch. En este bloque, que todavía no es capa de interfaz como para poder mostrar un mensaje al usuario, vuelve a relanzar la excepción llegando finalmente al método Guardar del formulario. Éste ya es de la capa de interfaz, así que ya podremos informar del mensaje.

private Boolean Guardar()
{
   try
   {
      //... validación de campos y llamada a la capa de negocio
   }
   catch(Exception ex)
   {
      String mensajesError = String.Empty;
      while(ex != null)
      {
          mensajeError += ex.Message + Environment.NewLine;
          ex = ex.InnerException;
      }
      MessageBox.Show("Ha ocurrido un error. El sistema responde: " + Environment.NewLine + mensajesError);
   }
}

El error vuelve a aterrizar en el bloque catch, pero esta vez, en lugar de relanzarlo lo tratamos.

Yo suelo hacer lo que veis en el ejemplo. Me hago un bucle while que va recorriendo las excepciones internas y de cada una de ellas voy tomando el mensaje de error así tengo una «pila de mensajes de error» que me permite identificar todos los desastres sucedidos por el camino y obtener información por pantalla de un error sucedido en un lugar en el que no podría mostrarla.

Para acabar de hacer la captura de errores con «clase», yo en lugar de usar un MessageBox normal y corriente me he hecho un formulario donde mostrar mensajes de error y la pila de errores la muestro en un treeView obteniendo un resultado muy elegante y profesional.

Espero que os resulte de utilidad.

Un saludo a todos.

3 comentarios en “[C#] Lanzamiento de excepciones y excepciones neutrales”

    1. De nada.

      Puede parecer una tontería, pero en su día a mí también me dio bastante la lata saber cómo tratar los errores en una aplicación de varias capas. Y como a veces da un poquillo de vergüenza preguntar algo tan «basico» (¿?) he decido publicar una entrada para aquellos que tengan la misma duda que tuve yo.

      Me alegra haberte sido de utilidad.
      Un abrazo
      🙂

Responder a [Z]eta Cancelar respuesta

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