viernes, 27 de agosto de 2010

Pseudo-AOP usando delegados en C#

Estaba yo tan feliz programando un servicio en WCF y me di cuenta de que en todos los métodos de servicio que escribía estaba repitiendo el mismo esquema de código una y otra vez: "Ejecuta todo el método en un bloque try-catch, y si algo va mal, escribe una traza del error y devuelve null (o cero, o el valor por defecto adecuado)". Decidí entonces que debía hacer algo al respecto, primero porque estaba violando el principio DRY (algo inadmisible para alguien que programa con educación), y segundo por vagancia (si se puede ahorrar aunque sea un simple copypaste, hay que hacerlo).

Encontré una solución muy mona que se basa en el uso de un método auxiliar que se encarga del trabajo sucio (capturar la posible excepción y escribir la traza en ese caso) más un delegado que contiene el código que queremos ejecutar realmente. El resultado es algo que, visto de lejos y en una noche sin luna y con niebla, se parece remotamente a la Programación Orientada a Aspectos. Ahora vamos a ello.

Supón que tienes un método (expuesto al exterior por medio de un servicio WCF en este caso, pero no tiene por qué ser así) tal que este:

Bien, quieres añadir control y traza de errores de forma genérica, es decir de forma que no tengas que escribir el mismo código para otros métodos similares. ¿Cómo podemos conseguir esto?

La solución que yo uso pasa por definir un método auxiliar llamado ExecuteMethod, que en su forma más básica es algo así:

Entonces modificamos la firma de nuestro método de servicio de forma que pasa a ser privado, y creamos un método nuevo que invoca a ExecuteMethod pasando el método original como parámetro. Se entiene mejor viendo el código directamente:

Nótese cómo usamos una expresión lambda para pasar de forma limpia y elegante (¡oh, ah!) un delegado que apunta a nuestro método original.

Ahora, podemos aplicar esta técnica de control a todos los métodos que queramos, por ejemplo:


La gracia de esta técnica es que podemos cambiar nuestra estrategia de control de errores y traza cambiando sólo el método ExecuteMethod. Por ejemplo, podemos decidir escribir trazas de error usando el método Debug.Write en un principio, para más adelante usar el registro de eventos de Windows, quedando entonces el método algo así:


También podemos decidir volver a lanzar la excepción en vez de devolver el valor predeterminado del tipo de retorno, o actuar de forma distinta según el tipo de excepción... en fin, las posibilidades son infinitas, o casi.

Para terminar, he aquí la versión de ExecuteMethod para invocar métodos de tipo void (es decir sin valor de retorno):

Esta versión se invoca de la misma forma que la anterior, excepto por supuesto que no es necesaria la palabra return:

En resumen, como dicen por ahí: se non è AOP, è ben trovato.

jueves, 19 de agosto de 2010

Envoltorio para las clases File y Directory

Si en tu código usas algún motor de inyección de dependencias (deberías), sabes que tus clases nunca deben depender de otras clases concretas sino de interfaces, y que los objetos que implementan dichos interfaces deben ser suministrados externamente (típicamente mediante el constructor). Es decir, que en vez de hacer esto:

...tienes que hacer esto otro:

...y usar un inyector de dependencias para registrar la clase apropiada que implemente IMiClaseAuxiliar y para construir los objetos de tipo MiClase. Esto facilita enormemente la creación de pruebas unitarias, especialmente si usas algún mecanismo de mocking.

La cosa pinta de entrada muy simple y muy bonita, pero, ¿qué pasa cuando en vez de objetos propios tenemos que usar las clases de sistema? Por ejemplo si accedemos al sistema de ficheros, ¿cómo simulamos la existencia de determinados ficheros en nuestras pruebas unitarias?

La solución es no usar directamente las clases de sistema implicadas (File y Directory en caso de ficheros), sino crear clases "envoltorio" que tengan los mismos métodos públicos que la clase de sistema original; dichos métodos simplemente delegarán el trabajo en los métodos "reales" de la clase de sistema correspondiente. Creando interfaces para las clases envoltorio, ya podemos usar mecanismos de mocking en nuestras pruebas unitarias.

Como me explico peor que un libro sumergido en cemento, mejor pongo un ejemplo. Si por ejemplo antes teníamos esto:

...ahora definiríamos una clase FileWrapper con su correspondiente interfaz:

...y nuestra clase quedaría:

Pues bien, resulta que alguien más ya había pensado esto y ha creado sendos envoltorios para las clases File y Directory. Os pego el código aquí para que sea de más fácil acceso. De nada.