Rafactorización Orientada al Aspecto

En esta p�gina, examinaremos t�cnicas AO para refactorizar el manejo de excepciones, el control de concurrencia, el goteo de argumentos, la creaci�n de objetos worker, la implementaci�n de interfaces, la sobreescritura de m�todos, y el refuerzo de contratos. Por motivos de brevedad nos enfocaremos en el antes-y-despu�s del c�digo afectado por la refactorizaci�n y no mucho en los pasos que la conducen. El proceso de cada t�cnica es similar a los presentados en la p�gina anterior. Adem�s, asumimos que se han aplicado todas las t�cnicas de refactorizaci�n convencionales antes de empezar con la Refactorizaci�n AO. Esto evita la repetici�n de informaci�n cubierta en otros lugares, y muestra que la Refactorizaci�n AO de hecho mejora, y no reemplaza, a la refactorizaci�n convencional.

Muchas de las t�cnicas de Refactorizaci�n AO presentadas en esta p�gina son �tiles en implementaciones de crosscutting concerns en un gran �mbito. Sin embargo, la diferenc�a est� en el �nfasis aplicado a ciertos principios subrayados en la secci�n "Peculiaridades de la Refactorizaci�n AO" de la p�gina anterior. En la pr�ctica, es muy com�n empezar escribiendo un aspecto para refactorizar una clase, luego utilizar el aspecto para refactorizar varias clases, y eventualmente modificarlo para implementar el crosscutting de un modo general en el sistema.

Hay muchas t�cnicas que cubrir, empecemos ya!

.�Extraer el Manejo de Excepciones

El manejo de excepciones es un crosscutting concern que afecta a la mayor�a de las clases no triviales. Debido a la estructura del c�digo de manejo de excepciones (bloques try/catch), la refactorizaci�n convencional no puede realizar la extracci�n del c�digo com�n. Toda clase (o conjunto de clases) podr�a tener su propia forma de manejar excepciones encontradas durante la ejecuci�n de su l�gica. Con la Refactorizaci�n AO, puedes extrear el c�digo de manejo de excepciones a un aspecto separado.

Ilustraremos la t�cnica a trav�s de un esquema de manejo de excepciones, donde los manejadores lanzan una nueva excepci�n convirtiendo la excepci�n capturada en otro tipo. Es posible ampliar este ejemplo a otras t�cnicas de manejo de excepciones como el "logging" o el "re-lanzado", el "abortado de transaciones", y el "re-intento de la operaci�n".

Consideremos el patr�n J2EE Business Delegate. Casi todos los m�todos de un clase business delegate capturan excepciones lanzadas por la implementaci�n subyacente y relanzan una excepci�n espec�fica de la aplicaci�n. Implementar este esquema de manejo de excepciones requiere un bloque try/catch en todos los m�todos. En todos los bloques catch, creamos y lanzamos una nueva excepci�n que envuelve la excepci�n capturada, despu�s realizamos otras tareas como el logging y la vuelta atr�s de la transaci�n actual. La misma situaci�n ocurre en muchos otros patrones de dise�o como Data Access Object y Service Locator.

Consideremos el siguiente listado, donde la clase LibraryDelegate utiliza el patr�n Business Delegate. Hay l�gica duplicada en casi todos los m�todos:

Listado 1: LibraryDelegate.java antes de la refactorizaci�n:

No hay ninguna t�cnica de refactorizaci�n convencional que pueda extrear los bloques try/catch repetidos y el c�digo asociado con cada uno. Para refactorizar toda la l�gica del manejo de excepciones, escribriremos el siguiente aspecto. (Debido a un bug en la implementaci�n actual de AspectJ 1.1.1, no podemos crear este aspecto como un aspecto anidado. Por lo tanto, haremos lo siguiente mejor -- crear un aspecto emparejado):

En el aspceto, todas las sentencias declare soft de LibaryExceptionHandlingAspect causan que el lanzamiento de una excepti�n de los tipos especificados (RemoteException, ServiceLocatorException, CreateException) durante una llamada al punto de corte especificado sea tratado como una excepci�n de tiempo de ejecuci�n. Cuando se lanza dicha excepci�n, se envuelve en una SoftException, que es una excepci�n en tiempo de ejecuci�n. El consejo after throwing captura cualquier SoftException lanzada y lanza una nueva LibraryException envolviendo la excepci�n original obtenida llamando a getWrappedThrowable() sobre la excepci�n capturada. Ahora podemos extraer todo el manejo de excepciones de la implementaci�n principal. El Listado 2 muestra la clase LibraryDelegate.java despu�s de aplicar la refactorizaci�n �Extract exception handling�. Observa que aunque los puntos de corte del aspecto anterior utilizan comodines, podr�as llegar a la misma definici�n siguiendo el proceso descrito en la p�gina anterior.

Listado 2: LibraryDelegate.java despu�s de la refactorizaci�n:

Claramente hay un ahorro substancial de c�digo y el c�digo principal est� m�s claro. El aspecto ha localizado el manejo de excepciones, simplificando as� la modificaci�n de la pol�tica de manejo de excepciones. Por ejemplo, es f�cil a�adir logging modificando el consejo.

Una t�cnica de refactorizaci�n normalmente lleva a otra. La refactorizaci�n descrita a continuaci�n normalmente empieza con una combinaci�n de �extract method calls� y �extract exception handing�.

.�Extraer el Control de Concurrencia

El control de concurrencia normalmente es un crosscutting concern de importancia cr�tica, pero igualmente dif�cil de implementar. Adem�s de ser dif�cil de entender, una implementaci�n de control de concurrencia requiere que el c�digo est� esparcido por muchos m�todos. Hay disponibles unos cuantos patrones para el control de concurrencia, y aunque los conceptos son reutilizables, sus implememtaciones no. AOP ofrece implementaciones reutilizables de estos patrones, aliviando un poco el dolor de la implementaci�n del control de concurrencia.

En el Listado 3 hay un ejemplo sencillo que utiliza el patr�n read-write lock. El patr�n de concurrencia requiere el manejo de dos tipos de bloqueo: lectura y escritura. Antes de entrar en un m�todo que s�lo lee datos, se adquiere el bloqueo de lectura y este bloqueo se libera antes de abandonar el m�todo. El bloqueo de escritura se adquire y libera de forma similar en todos los m�todos que modifican los datos.

Listado 3: Account.java antes de la refactorizaci�n:

La soluci�n utiliza un aspecto ReadWriteLockSynchronizationAspect reutilizable (puedes ver su c�digo fuente). El aspecto declara dos puntos de corte abstractos: readOperations() y writeOperations(), y les aconseja manejar los bloqueos de lectura y escritura. El aspecto utiliza una asociaci�n perthis para maximizar la reutilizaci�n del c�digo. Sin embargo, los detales de esta asociaci�n van m�s all� del �mbito de este tutorial. Observa que la base del aspecto reutilizable podr�a ser un resultado de seguir el paso "Refactorizar el Aspecto de Refactorizaci�n" que hemos visto en la p�gina anterior.

Hemos refactorizado el c�dido del control de concurrencia de la clase Account en un aspecto anidado. Este aspecto es un sub-aspecto concreto de ReadWriteLockSynchronizationAspect. Proporcionamos las definiciones para los puntos de corte asbtractos: readOperations() y writeOperations(). En el caso de la clase Account (depsu�s de seguir el proceso descrito en la p�gina anterior) hemos definido operaciones de lectura en todos los m�todos cuyo nombre empieza con get adem�s del m�todo toString(). Consideramos cualquier otro m�todo como una operaci�n de escritura:

El siguiente listado muestra la clase Account.java despu�s de aplicar la refactorizaci�n para encapsular el control de concurrencia en el aspecto anidado.

Listado 3: Account.java despu�s de la refactorizaci�n:

La refactorizaci�n ahorra gran cantidad de c�digo, asegura una adquisi�n y liberaci�n consistente de los derechos de bloqueo, y aisla los detalles del patr�n de bloqueo lectura-escritura. Como algo extra, podremos cambiar f�cilmente la implementaci�n. Por ejemplo podemos utilizar un esquema m�s simple que rodea cada operaci�n de lectura o escritura en un bloque synchronized. Todo lo que necesitamos es modificar el aspecto base:

El aspecto base reutilizable SimpleSynchronizationAspect (puedes ver su c�digo fuente) simplemente rodea cada punto de uni�n capturado con un bloque synchronized. El uso de AOP cre� la separaci�n de conceptos entre la implementaci�n de Account y la implementaci�n del control de concurrencia, lo que nos permiti� realizar f�cilmente sta modificaci�n.

Las t�cnicas descritas hasta ahora utilizan las construcciones de crosscutting concerts m�s simples. El uso de patrones de dise�o AOP ayuda a la creaci�n de t�cnicas de refactorizaci�n poderosas. Las dos t�cnicas hacen uso de patrones de dise�o AOP para extraer el c�digo com�n que se repite en muchos m�todos.

.�Extraer la Creaci�n de Objetos Worker

Un objeto worker es un ejemplar de una clase que encapsula un m�todo (llamado m�todo worker) para que ese m�todo pueda ser tratado como un objeto. Necesitamos crear objetos worker en muchas situaciones: ejecuci�n as�ncrona de m�todos, autorizaci�n utilizando el API Java Authentication and Authorization Service (JAAS), implementaci�n de threads seguros en aplicaciones Swing/SWT, etc. En todas las ocasiones, se necesita mucho c�digo extra para crear dichos objetos worker. Por cada m�todo worker necesitamos crear una clase con nombre o an�nima que encapsule la llamada al m�todo requerido y crear un ejemplar de dicha clase. Si utilizamos clases con nombre, terminaremos con muchas clases que s�lo ejecutan un m�todo worker y si utilizamos clases an�nimas, el c�digo para crear la clase emborrona la l�gica principal. El resultado es un c�digo dif�cil de entender y de mantener.

Podemos idear una Refactorizaci�n AO que utilice el patr�n de creaci�n de objetos worker para ayudarnos a dejar los m�s sencilla posible la implementaci�n principal y para localizar la creaci�n del worker y la l�gica utilizada.

El siguiente listado muestra la clase ATM antes de la refactorizaci�n. El uso del esquema de autorizaci�n JAAS requiere la ejecuci�n del m�todo pasando un objeto worker a Subject.doAsPrivileged(). Por lo tanto, utilizamos una clase an�nima que ejecute la l�gica de negocio en su m�todo run(). Pasamos un ejemplar de la clase an�nima a Subject.doAsPrivileged() (en la implementaci�n general, deber�amos llamar a checkPermission() en los m�todos que necesiten el chequo de autorizaci�n).

Listado 5: ATM.java antes de la refactorizaci�n:

Claramente, el c�digo es dif�cil de entender y cuesta mucho imaginarse la l�gica de negocio enmara�ada entre esa cantidad de c�digo de clases an�nimas. Podemos refactorizar la l�gica de autorizaci�n utilizando el patr�n de creaci�n de objetos worker mostrado en el siguiente c�digo:

Listado 6: ATM.java despu�s de la refactorizaci�n:

Observa que si buscamos los m�todos que lanzan una excepci�n espec�fica del negocio, necesitaremos l�gica adicional en el aspecto para tratarlos. Por ejemplo, en el aspecto anterior, cuando lo aplicamos al m�todo debit() que lanza InsufficientBalanceException, el cliente recibe una BankingException gen�rica. Sin embargo, no consideremos este problema en est� tutorial.

Hasta ahora, nos hemos enfocado en la refactorizaci�n de funcionalidades comunes en una clase a la vez. Las dos siguientes t�cnicas de refactorizaci�n consideran un conjunto de clases relacionadas como el objetivo de la refactorizaci�n.

.�Reemplazar el Goteo de Argumentos por Agujeros de Gusano

Con frecuencia, hay necesidad de pasar una parte del contexto actual (ejecuci�n actual/objeto objetivo, argumentos de m�todos, etc.) a los m�todos invocados, que a su vez se siguen pasando, para que un m�todo m�s abajo en la cadena de llamadas eventualmente pueda utilizar el contexto para realizar su tarea. El resultado es un API contaminado debido a un crosscutting concern que incrementa el embrronado de la clase del medio del cadena. Con la Refactorizaci�n AO, podemos evitar pasar par�metros a los m�todos de la cadena de llamadas. Esta es una de las t�cnicas de Refactorizaci�n AO m�s invasivas ya que normalmente se aplica a dos o m�s clases.

Consideremos los fragmentos de unas clases (Listado 7) de un sistema bancario. La clase ATM utiliza un ejemplar de BankingLiaison (que representa al banco correspondiente a la tarjeta ATM) que, a su vez, llama a operaciones sobre el ejemplar Account. La generaci�n de sentencias Account necesita informaci�n sobre la ATM adem�s de la cuenta, la cantidad, y la operaci�n implicada. Para facilitar la generaci�n de sentencias, los m�todos de ATM pasan el propio ATM a los m�todos de BankLiaison, que los propagan a los m�todos de la clase Account. Los m�todos de la case Account llaman a appendAccountActivities() que a�ade las actividades, a una tabla de la base de datos.

Listado 7: clases del sistema bancario antes de la refactorizaci�n:

El problema con el c�digo anterior es que el par�metro ATM gotea a trav�s de capas de llamadas. Observa que el ejemplo s�lo utiliza tres niveles de clases y un par�metro. El problema se acucia seg�n se incrementan el n�mero de clases y de par�metros.

Idearemos una t�cnica de Refactorizaci�n AO basada en el patr�n Wormhole (agujero de gusano) para ayudarnos en esta situaci�n. Este patr�n utiliza dos puntos de corte - uno para el llamador que tiene el contexto y otro para el llamado que necesita el contexto. Luego el patr�n crea un wormhole utilizando un punto de corte cflow() y transfiere el contexto llamador al punto de lamada. Observa que hay una alternativa utilizando una variable ThreadLocal, para contener la variable ATM, pero la soluci�n que utiliza esta patr�n es m�s clara porque modulariza el acceso a la informaci�n, en vez de utilizar una variable global. Como efecto lateral, incluso el esquema de utilizaci�n de una variable ThreadLocal es m�s efectivo cuando se utiliza con aspectos porque ya est� modularizado!

Refactorizamos la l�gica de actualizaci�n de las tabla de actividades de account en un aspecto anidado en la clase Account. Aunque mostremos directamente el resultado final de la refactorizaci�n, aplicar �Extract method calls� antes de utilizar el patr�n wormhole podr�a ser una buena idea. Aqu� est� el aspecto que realiza la refactorizaci�n:

La raz�n por la que hemos elegido implementar el aspecto como un aspecto anidado de la clase Account y no en la clase ATM, es evitar una dependencia adicional comparada a la implementaci�n convencional. Un aspecto separado de nivel superior tambi�n hubiera sido una buena idea. Observa que hemos movido el m�todo appendAccountActivities() desde la clase Account al aspecto, ya que s�lo lo utiliza �l.

Listado 8: Las clases del sistema bancario despu�s de la refactorizaci�n:

Las clases ATM, BankingLiaison, y Account ya no necesitan el par�metro ATM adicional que se utiliza con el �nico prop�sito de actualizar la tabla de actividades de cuentas de la base de datos.

Hasta ahora, hemos tratado con t�cnicas de crosscutting din�micos para Refactorizaci�n AO. La siguiente t�cnica ilustra el uso de crosscutting est�tico ofrecido por AspectJ para refactorizar c�digo ya existente.

.�Extraer las Implementaciones de Interfaces

La t�cnica de refactorizaci�n convencional de �Extract interface� permite mejorar el desacoplamiento de los clientes y las implementaciones. Si m�s de una clase implementa ese interface, podr�a terminar duplicando el c�digo requerido para implementar el interface. Con AOP, se puede llevar a cabo la idea y evitar cualquier duplicaci�n.

La Refactorizaci�n AO utiliza el mecanismo de declaraci�n inter-tipos de AspectJ (tambi�n conocido como presentaci�n). Podemos escribir un aspecto que presente la implementaci�n por defecto en el interface extra�do. En esencia, AspectJ permite implementar mezclas. Este tipo de refactorizaci�n es especialmente �til cuando la implementaci�n de un interface es el punto de ebullici�n.

Consideremos el interface ServiceCenter del siguiente listado:

Listado 9: ServiceCenter.java antes de la refactorizaci�n:

La clase ATM implementa el interface ServiceCenter.

Listado 10: ATM.java antes de la refactorizaci�n:

La clase ATM contiene una implementaci�n primordial de ServiceCenter. Otras clases como BrickAndMortarBank, SuperStoreServiceCenter ser�an similares - todas repiten el c�digo de la implementaci�n del interface ServiceCenter. Aunque podr�amos evitar el c�digo duplicado creando la implementaci�n por defecto del interface y hacer que las clases desciendan de esta implementaci�n, la t�cnica no funcionar� para las clases que descienden de otras clases.

Con la Refactorizaci�n AO, presentamos la implementaci�n por defecto en el interface ServiceCenter utilizando un aspecto anidado que podemos ver en el siguiente listado:

Listado 11: ServiceCenter.java despu�s de la refactorizaci�n:

En el listado anterior, el aspecto anidado IMPL presenta los datos miembros as� como los m�todos de la implementaci�n por defecto del interface ServiceCenter. Con este aspecto, cualqueir clase que implemente el interface autom�ticamente hereda la implementaci�n por defecto.

Ahor apodemos tomar la implementaci�n de los m�todos declarados en ServiceCenter en la clase ATM, como se ve en el siguiente listado:

Listado 12: ATM.java despu�s de la refactorizaci�n:

Las otras clases que implementan el interface, como BrickAndMortarBank, SuperStoreBranch tambi�n pueden eliminar la implementaci�n de los m�todos declarados en el interface. La implementaci�n de las clases ya no incluye el c�digo importante para implementar los interfaces. Pero a�n as�, estas clases pueden sobreescribir los m�todos proporcionados por la implementaci�n por defecto del aspecto.

Hay algunas variaciones para esta t�cnica de refactorizaci�n. Primero podr�amos permitir que las implementaciones de las clases eligieran si heredar o no la implementaci�n por defecto proporcionada por el aspecto. Una forma de implementar este esquema es crear un subinterface del interface original, digamos ServiceCenterDefaultAspectImpl, y dejar que el aspecto presente la implementaci�n por defecto de este interface. Las clases heredar�n la implementaci�n por defecto s�lo si declaran que implementan ServiceCenterDefaultAspectImpl. Otra variaci�n es proporcionar la implementaci�n por defecto como parte del interface. Aunque en algunos casos, dicha elecci�n podr�a pasar a ser una necesidad (debido a una suerte de informaci�n necesaria para la implementaci�n), en otros casos, esto podr�a pensarse como una decisi�n de dise�o para forzar a los desarrolladores de la clase a pensar en la sem�ntica de la implementaci�n correcta.

.�El Resto en Breve

En esta p�gina, hemos examinado algunas t�cnicas de Refactorizaci�n AO. Concluiremos con una breve explicaci�n de otras t�cnicas.

.�Reemplazar la Sobreescritura con un Consejo

Es frecuente que necesitemos una mejora adicional en el comportamiento com�n de muchos m�todos de una clase. Una soluci�n t�pica es crear una subclase y sobreescribir los m�todos para realizar alguna l�gica adicional. Por ejemplo, podr�amos tener una clase modelo sin ning�n tipo de soporte para notificaci�n de observadores. Puedes a�adir dicho soporte creando una subclase y sobrescribiendo todos los m�todos modificadores del estado para notificarselo a los observadores.

Con Refactorizaci�n AO, puedes utilizar un aspecto para aconsejar al m�todo necesario con la l�gica adicional. Por ejemplo, en lugar de sobreescribir, simplemente podr�as aconsejar los m�todos de la subclase que notifiquen a los observadores. La implementaci�n es muy parecida a la t�cnica �Extract method calls�. La diferencia es que una vez extraidas las llamadas a m�todos, los m�todos de la subclase simplemente llaman al m�todo de la correspondiente clase base, y por lo tanto no necesita existir en la clase derivada.

.�Extraer la Inicializaci�n Lenta

La inicializaci�n lenta de recursos costosos es una t�cnica de optimizaci�n com�n. La soluci�n convencional requiere chequeos de los recursos no inicilizados en cada lugar en que se usen esos recursos y provoca el c�digo-borroso y el c�digo-mezclado. Podr�a parecer que se pueden solucionar estos problemas simplemente accediendo a la variable de ejemplar mediante su m�todos get; sin embargo, hay una pega: si una porci�n de la clase contenedora de la referencia al recurso accede a �l directamente, la inicializaci�n no ocurrir� y experimentaremos consecuencias indeseadas. Los tests podr�a revelar bugs como �ste, pero s�lo si se llama al m�todo err�neo antes de cualquier otro m�todo que si haga la inicializaci�n del recurso. Observa que seleccionando el accceso como private a los miembros de la clase evitar� el acceso directo desde otras clases, pero no desde dentro de la propia clase. Con la Refactorizaci�n AO, podemos aconsejar a los accesos de lectura del recurso (utilizando un punto de corte get()) para que inicialicen.

.�Extraer el Refuerzo de Contratos

El refuerzo de contratos normalmente requiere la duplicaci�n de c�digo en muchos m�todos de una clase. Esto es especialmente cierto cuando se implementan clases que no var�an y condiciones pre y post que son comunes a muchos m�todos. Las implementaciones convencionales requieren a�adir c�digo id�ntico - chequeo condicional y sentencias de asserto - en muchos m�todos. Con la Refactorizaci�n AO, podemos refactorizar dichos chequeos de contratos en un aspecto separado. Este tipo de refactorizaci�n es muy parecida a �Extract method calls�, excepto en que normalmente utiliza sentencias de assertos para realizar la l�gica adicional.

.�Conclusi�n

Los aspectos que nacen de la refactorizaci�n normalmente se construyen afectando a una peque�a porci�n del sistema, empezando normalmente con una clase. Aunque la l�mitaci�n del �mbito reduce los beneficios de esos aspectos, tambi�n reduce el riesgo de cambios no deseados en el comportamiento del sistema. Con el tiempo, las t�cnicas de refactorizaci�n se pueden construir sobre �mbitos m�s amplios. En este tutorial hemos examinado varias t�cnicas con ejemplos t�picos que se puede encontrar un desarrollador Java. Inicialmente, podr�as utilizar las t�cnicas en los escenarios descritos. Con el tiempo, desarrollar� un buen ojo para aplicar esas t�cnicas a diferentes escenarios e incluso podr�as descubrir nuevas t�cnicas.

Las t�cnicas de refactorizaci�n son �tiles para entenderlas por s� mismas. Sin embargo, ser� mucho mejor cuando los IDEs ayuden en la Refactorizaci�n AO como ya lo hacen con la refactorizaci�n convencional. Un simple soporte de Refactorizaci�n AO permitir�a a los programadores elegir entre m�ltiples bloques de c�digo y el tipo de refactorizaci�n deseada. El IDE podr�a crear una versi�n sencilla de un apsecto que encapsulara los elementos comunes del c�digo seleccionado. Si fuera apropiado, los desarrolladores podr�a mejorar las definiciones de los puntos de corte del aspecto creado por el IDE.

La AOP beneficia significativamente a la programaci�n del mundo real. Sin embargo, de forma poco entendible, hay muchas precauciones para adoptarla. Un camino de adaptaci�n seguro para cualquier tecnolog�a es aquel que permite un uso gradual. Para AOP, dicho camino parece ser la utilizaci�n de aspectos desarrollados inicialmente, seguido por la refactorizaci�n utilizando t�cnicas de AO, luego implementando los crosscutting concerns complejos, y finalmente dise�ando sistemas con AOP desde la concepci�n del proyecto. Dicho camino minimiza los riesgos asociados, mejora el entendimiento de los fundamentos de AOP, crea confianza, da una realizaci�n a los usos apropiados o inapropiados, recurriendo a los patrones de dise�o, y todo ello disparando la confianza en la AOP.

Los beneficios de la AOP son demasiado reales para ignorarlos, y una aproximaci�n cautelosa es demasiado v�lida como para perd�rsela. La utilizaci�n del camino seguro para incorporar los aspecto del desarrollo y la Refactorizaci�n AO ayudan a sobrellevar este dilema. Primero, acomodate con el uso de los aspectos de desarrollo en AOP. Luego cuando veas c�digo duplicado que no puedas refactorizar utilizando las t�cnicas convencionales (no tendr�s que mirar mucho!), aprovecha la oportunidad para utilizar la Refactorizaci�n AO. Obtendr�s beneficios inmediatos y habr�s dado los pasos para alcanzar el poder de la Programaci�n Orienta al Aspecto.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP
SIGUIENTE ARTÍCULO