lunes, 15 de febrero de 2010

.Net MVC + jQuery: manejo de excepciones. I: Requerimientos.

Me pasé las últimas semanas trabajando sobre la beta de Visual Studio 2010 (acaba de salir el Visual Studio 2010 RC) armando un esquema de trabajo (un mini-framework) para proyectos MVC, en un intento de materializar la experiencia acumulada en otros proyectos en la forma de herramientas encapsuladas, probadas y listas para reutilizar.

Uno de los puntos que más trabajo me ha llevado es el manejo de excepciones. Un tema traicionero, simple a primera vista pero complicado y retorcido a medida que se avanza. Siempre lo tuve cubierto (es decir, sin graves problemas) con un poco de alambre, pegamento y parches aquí y allá, y quise aprovechar este nuevo comienzo en limpio para pensar un esquema más consistente y por lo tanto más sólido y fácil de mantener.

Comencemos, como corresponde, por los requerimientos. ¿Qué pretendo de a la infraestructura de un proyecto en cuanto a manejo de errores y excepciones (teoricé sobre la diferencia hace un tiempo, en Errores y excepciones)?

En principio, un uso estándar y transparente. Por ejemplo, en el botón “Save” de una vista, el programador debería codificar, en javascript (+jQuery, por supuesto):

$("#Save").click(function () {
 try {
  $("#CommentForm").valid();
  Save({
   User: $("#User").val(),
   Date: $("#Date").val(),
   CommentText: $("#CommentText").val()
  });
 }
 catch (ex) {
  ShowException(ex);
 }
});

En el ejemplo anterior, el bloque try…catch captura los errores de validación disparados por la instrucción $("#CommentForm").valid(); (es el plugin jQuery Validation) y la función ShowException los muestra en una forma amigable al usuario. Este no es el comportamiento “normal” de la función valid() de jQuery Validation, hay que codificarlo.

Pero no sólo eso. La función Save del ejemplo debe enviar los datos al servidor y el servidor debe pasarlos al modelo de negocio y esperar excepciones. Excepciones del tipo “La fecha del comentario ingresado corresponde a un informe ya aprobado” y cosas por el estilo, tan molestas para nosotros como necesarias para nuestros clientes. Es decir que se comunicará con una acción en un controlador, supongamos:

namespace ShowCase.Web.Controllers
{
    public class CommentsController : Controller
    {
  public bool SaveComment(CommentsModel model)
  {
   model.Save();
   return true;
  }
    }
}

Vemos que la acción devuelve siempre true. Esto es porque espera que los problemas se comuniquen a través de las excepciones. Un ejemplo esquemático para el método Save del modelo sería:

public class CommentsModel
{
 public void Save()
 {
  FinancialReport financialReport = FinancialReports.GetByDate(this.Date);
  if(financialReport.Status == ReportStatus.Approved)
   throw new ApplicationException("The comment's date points to an approved report.");
  
  FinancialReports.SaveComment(financialReport, this);
 }
}

El manejo de este tipo de excepciones generadas del lado del servidor debería ser, para el programador de la funcionalidad, idéntico al de las generadas del lado del cliente. Así que tenemos que llegar, de alguna manera, desde el throw en el método Save de CommentsModel, al catch en nuestra función de javascript.

Para complicar las cosas, agreguemos los errores. Estamos hablando ahora de verdaderos errores del sistema: errores de codificación, de conexión con la base de datos, de conexión entre el cliente y el servidor web, de javascript, etc. Errores del tipo que solemos llamar catastróficos, pero que yo prefiero denominar inesperados, como para restarle dramatismo al asunto (un cliente que no llamaría a las 3 de la mañana por un error “inesperado” tal vez sí lo haga por uno “catastrófico”).

Es el tipo de errores, en definitiva, que deberían “romper” o “cerrar” la aplicación, puesto que se ha arribado a un estado no contemplado, y cualquier acción posterior podría derivar en datos corruptos, inconsistentes, o –por lo menos, y más probablemente- en nuevos, más molestos y más extraños errores.

La función ShowException del ejemplo debería diferenciar entre las excepciones de negocio y los errores. Para los primeros mostrará un mensaje al usuario, y para los segundos deberíamos grabar un archivo de log, y podríamos establecer dos comportamientos posteriores:

  • uno para desarrollo o pruebas, mostrar por pantalla toda la información disponible acerca del error, y dejar que la aplicación siga abierta (el desarrollador o tester decidirá por sí mismo si puede continuar o no sin problemas),
  • y otro para producción, limpiar completamente la pantalla y mostrar un mensaje de error escueto invitando al usuario a reiniciar la aplicación o a llamar al administrador del sistema (siempre y cuando no seamos nosotros).

Es importante que, en entornos de producción, no sólo la función ShowException no muestre más de lo que debe, sino que el servidor mismo no envíe más información que la estrictamente requerida.

Todos estos requerimientos deben implementarse, dentro de lo posible, en una forma transparente al programador de la funcionalidad. Personalmente considero esta transparencia como algo muy importante, y la resigno (obligando al programador a derivar de ciertas clases, a implementar o a invocar ciertas funciones especiales) sólo cuando la alternativa es imposible o extremadamente más complicada. Cuando programamos una funcionalidad tenemos la cabeza puesta en facturas, aprobaciones, balances, fechas de cierre… en ese contexto, dónde fue generada la excepción o qué tipos de excepción hay y cómo deben transmitirse desde su origen hacia el cliente, son sutilezas que de requerir tratamiento especial serán seguramente pasadas por alto.

Pero, en definitiva, qué tan importante es esta transparencia o cuándo una alternativa es “imposible” o “extremadamente complicada” dependerá de cada uno. El hecho es que gracias a .Net MVC y jQuery podemos lograr un nivel muy aceptable de transparencia a través de diferentes puntos de intercepción y extensión convenientemente ubicados en la infraestructura del proyecto.

Suficiente para una primera entrega. En la próxima –en breve- analizaremos más en detalle los requerimientos en busca de las situaciones más comunes y los problemas que deberemos enfrentar al momento de picar el código.

Continúa en .Net MVC + jQuery: manejo de excepciones. II: El problema.

No hay comentarios.: