Hablamos de desarrollo de software, y de cualquier cosa que venga a cuento de eso.
Un poco en joda, un poco en serio, depende el humor del día.

miércoles, 1 de octubre de 2008

Errores y excepciones (I).

Errores y excepciones (I)

Errores y excepciones (II)

Errores y excepciones (III)

Soy un obsesivo de una correcta respuesta de un sistema ante errores y excepciones. Realmente creo que un sistema demuestra más su buena calidad cuando falla correctamente que cuando funciona bien. Esto es porque todos los sistemas eventualmente fallan (a causa de sus propios errores) y porque son utilizados en entornos en los que, por más controlados que sean, se producen situaciones excepcionales. Así que no es determinante el hecho de que un sistema falle o no, porque de seguro lo hará (todos lo hacen), sino cómo lo hace.

Por otro lado, desde el punto de vista de la arquitectura, y más desde la codificación, es un tema extremadamente difícil de tratar.

En principio, lo más difícil es establecer una distinción entre error y excepción, que es lo que propongo en este post. Como conozco a mis lectores, me atrevo a pedir que vayan a los conceptos (de los que estoy bastante seguro) y que me corrijan en las etiquetas (las palabras, de las que no estoy tan seguro, incluso de la utilización de error y excepción para cada caso).

Comienzo con excepción. Entiendo que una excepción es una situación inusual en la ejecución del sistema, pero contemplada al momento de diseñar el sistema o de escribir el código.

Por ejemplo: el programador da de alta un pedido, y contempla la posibilidad de que se produzcan excepciones de negocio (él no sabe cuáles pueden ser, pero sí conoce la posibilidad de que sucedan):

public void RecibirPedido()
{
   Pedido pedido = new Pedido();
   pedido = FrontEnd.SolicitarDatosPedido();
   pedido.Descuento = Descuentos.PorCliente(pedido.Cliente);
   try
   {
      Negocio.IngresarPedido(pedido);
      FrontEnd.MostrarMensaje("Pedido ingresado");
   }
   catch(NegocioException ex)
   {
      FrontEnd.MostrarMensaje(ex);
   }
}

Usamos el bloque try-catch para separar claramente la línea normal de ejecución de las excepciones y porque para este tipo de casos es de fácil lectura. No es nada que no se pueda hacer con un par de if. El uso de una excepción nos permite manejar más fácilmente la infinidad de mensajes que podríamos recibir, hacer una pantalla especial para mostrarlos, ofrecer diferentes alternativas en cada caso, y resolver fuera de este método una infinidad de cuestiones relacionadas con el particular manejo de excepciones de un sistema que sería muy largo de codificar en cada llamada.

Pero nunca nos salimos de lo que el programador pensó que sería un curso de ejecución posible. Una excepción de negocio con el mensaje "El producto ingresado no está disponible en stock" es mostrada al usuario en su caso y todo se mantiene dentro de lo esperado por el programador.

Vayamos al error. Un error, a diferencia de la excepción, es una respuesta o estado no contemplado en el sistema. Siguiendo nuestro ejemplo de código, imaginemos que el método de negocio IngresarPedido invocado arriba está codificado de la siguiente manera:

public static void IngresarPedido(Pedido pedido)
{
   Producto producto = BaseDeDatos.ObtenerProducto(pedido.Producto);
   if (producto.Stock > 0)
   {
      BaseDeDatos.InsertarPedido(pedido);
   }
   else
   {
      throw new NegocioException("El producto ingresado
         no está disponible en stock");
   }
}

¿Y dónde está el error? Por desgracia los errores son más sutiles, y detectarlos requiere algún conocimiento adicional del sistema. Imagínese el lector que el programador que escribió IngresarPedido lo compiló, lo probó un poco y siguió tranquilo con su vida, que pasó las pruebas y se puso en producción.

Ahora agrego un par de datos: vemos que IngresarPedido utiliza la función de base de datos ObtenerProducto para consultar el stock y determinar el curso de acción a seguir. Esta función devuelve null si el producto no existe.

De vez en cuando se actualiza la lista de productos que tiene disponible el usuario para elegir. Y muy de vez en cuando se da la extraña casualidad de que un usuario abre la pantalla de pedido antes de que se dé de baja un producto y presiona Aceptar después, cuando el producto ya está dado de baja (¡qué mala suerte!). Conclusión: IngresarPedido recibe un valor null al solicitar el producto, situación claramente no contemplada por nadie.

Esto es, a todas luces, un error.

¿Y cómo se maneja un error específico como este en el código? No se maneja, si contemplásemos esta posibilidad de alguna manera, ya no lo llamaríamos error.

Pero alguien me dirá que probablemente haya algo en el front-end que sí contemple esta posibilidad. Cierto en parte, pero no si somos estrictos con las palabras. Supongamos ahora que la función del front-end btnNuevoPedido_Click está codificada así:

private void btnNuevoPedido_Click(object sender, EventArgs e)
{
   try
   {
      RecibirPedido();
   }
   catch (Exception ex)
   {
      Logs.Registrar(ex);
      FrontEnd.MostrarMensaje("Error inesperado.");
   }
}

En realidad, lo que está manejando el front-end es la posibilidad de un error de programación en las capas inferiores del sistema, como una situación poco usual (espero) pero posible (si no fuese así, no debería haberse codificado, no codificamos para lo imposible). Así, lo que para el resto del sistema es un error, para el front-end es una excepción (justamente porque está contemplada en el código). Sabe qué hacer en ese caso: registrarlo y presentar un mensaje al usuario (lacónico y un tanto divertido, ya que vemos que dice que no esperaba justamente aquello que estaba esperando).

Los métodos bien codificados dentro de una arquitectura sólida se comportan como pequeños sub-sistemas independientes, por ello es que lo que para un método es la ejecución normal (IngresarPedido cuando hace un throw) es una excepción para la capa superior (RecibirPedido).

Y si lo pensamos un poco, nuestro error (no contemplar la posibilidad de null) es parte de las comprobaciones normales que ejecuta el framework: notemos que recibimos una excepción específica (NullReferenceException). En algún lado del framework hay un if y un throw muy parecido al nuestro en el método de negocio. Es un error a nivel de negocio y es una excepción a nivel de front-end.

Luego seguimos... hay para relamerse:

Errores y excepciones (II)

Errores y excepciones (III)

Bookmark on Delicious Twit this votar Compartir en Facebook Suscríbete al feed Menéalo