© Copyright Moisés Daniel DÃaz.
Empecemos con un ejemplo: AnotherAmazom.com
Imaginemos que recibimos el encargo de construir un portal comercial orientado a la venta por internet de artÃculos de consumo como libros, pelÃculas, DVDs, CDs, impresoras, ratones, cámaras fotográficas, reproductores mp3, televisores, y toda clase de consumibles electrónicos.
Cada uno de estos tipos de objetos se definen por sus caracterÃsticas. De hecho tendremos:
- CaracterÃsticas que aparecerán en todos los objetos, como por ejemplo el precio que el comprador debe pagar para poder obtenerlo, asà como un peso y volumen, elementos a tener en cuenta en cuanto al transporte, etc.
- Un conjunto de atributos particulares para cada tipo de objeto. Por ejemplo en las cámaras digitales fotográficas la resolución máxima de definición (medida en megapÃxeles), o por ejemplo en los CDs y DVDs la duración de las pelÃculas o discos de música que contengan.
Para la gestión de toda esta información, surge de forma evidente la necesidad de guardar todos los datos en bases de datos, de tal forma que podamos gestionar la información de forma adecuada, con todas las garantÃas que nos da este tipo de software. Para la finalidad de lo que asà se explica consideraremos que tendremos una tabla por cada tipo de objeto, cuyos campos serán las caracterÃsticas (tanto comunes como particulares) de ese tipo de objeto. De esta forma la información asociada a cada objeto se guardará en la tabla del tipo al que pertenezca, por ejemplo los libros se guardarán en la tabla ‘LibrosÂ’.
Evidentemente todo esto es una simplificación pero nos sirve para nuestro pequeño ejemplo.
Nuestra tarea consiste básicamente en gestionar la publicación web de toda esta información (olvidemos el carrito de la compra). Publicar esta información consiste básicamente en permitir al usuario, a través de un navegador web, el poder:
- Visualizar listados de productos de un determinado tipo.
- Filtrar estos listados.
- Visualizar los detalles de un producto especÃfico.
Tenemos básicamente dos posibilidades técnicas para llevar a buen puerto esta tarea:
- Construir o generar componentes web para cada una de estas categorÃas. De esta forma tendrÃamos un ‘componente ‘(conjunto de páginas JSPs o ASPs) distinto para cada tipo de objetos. De esta forma encapsularÃamos en cada componente el conocimiento sobre las caracterÃsticas de cada tipologÃa. TendrÃamos por ejemplo un Jsp/Asp para listar DVDs (ListDvd.jsp), y otro para visualizar la información asociada a un DVD (ShowDvd.jsp?id=83838). Esto implica tener mucho código que deberemos construir (costoso) o generar, y mantener (muy costoso).
- Tener un único componente web genérico de tal forma que leyendo las propiedades de cada tipo de objeto (datos que guardamos en la tabla MetaInformación), sea capaz de mostrar cualquier objeto sea cual sea su tipo. PodrÃamos verlo como tener un único Jsp/Asp para listados (List.jsp?type=DVD), y otro para visualizar registros (ShowElement.jsp?type=DVD&id=83838). A la información que describe la estructura de cada tipo de objeto se le llama metainformación. El coste de creación puede ser alto o no serlo, pero el de mantenimiento es muy bajo.
Generación de Código Fuente (o desarrollo manual en su defecto)
Cada tipo de objeto tiene un conjunto de caracterÃsticas comunes con otras tipos asà como otras caracterÃsticas diferentes al resto. Esto implica que en la base de datos tendremos tablas asociadas a cada tipo de objeto, y que por lo tanto los componentes web que listan y visualizan cada tipo (en definitiva páginas JSPs, ASPs, Servlets, etc) deben tener una estructura muy similar ya que su función es prácticamente la misma, pero a su vez esta estructura o código debe ser algo diferente debido a las caracterÃsticas particulares de cada tabla (tipo de objeto) a la que sirve de interfaz web.
De hecho el código de cada componente asociado a un tipo es totalmente dependiente de la estructura de la tabla a la que sive de interfaz, con lo que una vez definida cada tabla podrÃamos optar por generar de forma automatizada todo o parte del código que la lista, visualiza, etc, la otra posibilidad es utilizar la conocida técnica de copiar-pegar-modificar cuyos resultados suelen ser similares a la de la generación de código pero que es mucho más costosa y propensa a errores.
Existen multitud de herramientas que realizan funciones de generación de código, como por ejemplo CodeCharge, Xcripter, y montones de wizards de diferentes entornos de desarrollo.
Ya que con la generación automática de código fuente obtenemos los mismos resultados, pero a un coste bajÃsimo, podrÃamos pensar que son la solución al problema que hemos planteado al inicio del artÃculo (la construcción de ‘AnotherAmazon.comÂ’). Sin embargo la generación de código tiene numerosas pegas (no digamos ya la técnica de copiar-pegar-modificar) y por lo tanto no es la técnica apropiada para todos los proyectos.
Centrémonos por tanto en la generación de código fuente.
La generación de código tiene un escenario tÃpico que es el de las interfaces entre diferentes capas, componentes, etc, lugares en los que una pequeña cantidad de información puede ser usada para generar todo el código repetitivo que se requiere (ej: Wsdl à Soap, mapeo OO à RDBMS, tablas à formularios) (en nuestro caso de tablas a listados Html y visualización Html de sus registros).
Existen básicamente 3 tipos de generación de código [ACG]:
- Templating. Se genera un armazón (o esbozo) de código fuente no funcional para ser editado, con el que se evita tener que escribir la parte más repetitiva del código (generalmente poco compleja). Suele ser una opción recomendable. Ejemplo: Si usamos Oracle JDeveloper para construir un nuevo Servlet, éste nos genera automática una clase ‘Servlet’ con los imports básicos y los métodos que tiene esta clase.
- Parcial. Se genera código fuente que implementa parcialmente la funcionalidad requerida, pero que el programador usará como base para modificar, integrar y/o adaptar a sus necesidades. No suele ser recomendable. Por ejemplo: generamos una aplicación para el mantenimiento de tablas de bbdd (como aplicaciones web realizadas con CodeCharge, o componentes BC4J del JDeveloper), en el momento en que queramos modificarla, integrarla con otros desarrollos o adaptarla a necesidades ligeramente diferentes, tenemos que bucear en enormes cantidades de código que no entendemos y que debemos modificar extensivamente.
- Total. Se genera código fuente funcionalmente completo pero que no va a ser modificado por el programador, sino que si es necesario se vuelve a regenerar. Por lo general tampoco suele ser un código excesivamente complejo. Recomendable. Ejemplo: generación de un Stub para un WebService en JDeveloper.
Centrémonos en la generación parcial (es decir generación automática + modificaciones y desarrollos en el mismo). Suele ser problemática, ya que:
- El código generado es lo suficientemente complicado como para ser difÃcil de modificar con respecto a la funcionalidad implementada.
- Su regeneración suele ser costosa ya que hay si regeneramos el código hay que volver a modificarlo para volver a adaptarla al problema.
- Todo este código acaba siendo código a mantener, y no podemos olvidar que el mantenimiento es el mayor coste de cualquier proyecto.
- Hace que el código sea dependiente de una herramienta adicional de desarrollo. Esto es muy restrictivo, ya que complica el mantenimiento (en muchÃsimas ocasiones no lo hace la misma empresa que lo desarrolló, o surgen nuevos entornos que hacen inviable el uso de esas herramientas de generación (por ejemplo CodeCharge al principio generaba JSPs, ahora Servlets!!)), es una dependencia más y además fuerte.
- La generación de código funcional (parcial o semitotal) suele darte casi la funcionalidad que necesitas, pero no exactamente la necesitada. Modificarla es difÃcil.
Una de las grandes metas de la ingenierÃa del software es crear software reutilizable, lo cual suele traducirse en código robusto, poco propenso a errores.
Entonces, ¿por qué no parametrizar todo ese código repetitivo pero diferente unificándolo en uno solo (es decir refactorizarlo)? Todo depende de las dimensiones de la aplicación, ya que esa refactorización se consigue generalmente mediante arquitecturas reflexivas. No olvidemos que desarrollar una arquitectura tiene su coste.
Sólo hay dos aspectos positivos de la generación parcial de código:
- Por lo general tiene un mejor rendimiento respecto a las a.r.
- Es más rápido de desarrollar que construir una a.r. (a no ser que ya la tengas implementada).
La generación de componentes webs (JSPs, ASPs, etc) asociados a tipos de objetos entrarÃa en la categorÃa de generación parcial de código. Es evidente que podemos generar código que liste y visualize registros de tablas, pero este código generalmente forma parte de una aplicación que debe de integrarse con desarrollos especÃficos como código de seguridad, uso de tecnologÃas especÃficas de acceso a base de datos (por ejemplo un pool de conexiones), elementos de navegación, normas de estilo y visualización, uso de logs, mecanismos de personalización, servicios de impresión, etc, etc. Cuando nos enfrentamos a la construcción de aplicaciones medianas/grandes la generación de código suele ser muy problemática.
Qué es una arquitectura reflexiva
Tal y como aparece en [POSA] las arquitecturas reflexivas se han usado en multitud de sistemas y son especialmente apropiadas cuando necesitamos aplicaciones muy flexibles y adaptativas.
Una arquitectura reflexiva se caracteriza por tener una serie de elementos que contienen información estructural de parte del sistema, es decir que lo describe. El sistema usa esta metainformación como elemento básico que le permite interaccionar con esos elementos descritos (generalmente llamados base).
Una caracterÃstica fundamental es que por lo general todos los elementos base que son descritos comparten una serie de caracterÃsticas de tal forma que pueden ser sometidos a ciertas transformaciones (operaciones) de forma uniforme, y que sin embargo también contienen elementos diferenciadores que hace que estas operaciones sean bastantes dependientes de cada elemento base.
Estas caracterÃsticas diferenciadas hacen que por lo general se desarrolle código especÃfico por cada uno de estos elementos base, sin embargo existen ocasiones en las que nos es mucho más rentable desarrollar una arquitectura reflexiva que trate a todos estos elementos de forma uniforme.
Toda esta arquitectura puede verse también como una tÃpica estrategia de centralización o aplicación del metapatrón encapsulador-centralizador.
Beneficios:
- Cambiar un sistema software (añadir elementos, campos, etc) es fácil.
- Soporta numerosos tipos de cambios.
Perjuicios:
- Menor eficiencia.
- Se incrementa el número de componentes.
Las arquitecturas reflexivas no siempre son la mejor opción, sobre todo si no hay muchos elementos base de los que generar código.
Respecto al rendimiento, ciertamente son arquitecturas que añaden una capa adicional de indirección y que por lo tanto dependiendo de la aplicación especÃfica pueden ser lentas (aunque no tienen por qué serlo por norma general). Existen además medios para aumentar espectacularmente el rendimiento de esta arquitectura, como por ejemplo el uso de cachés.
Cuándo desarrollar una arquitectura reflexiva
Desarrollar una arquitectura tiene costes y no es apropiada en todas las circunstancias, sino que dependen de numerosos factores.
Por lo general implementar una arquitectura reflexiva requiere desarrollar elementos de código bastante complejo, necesita de ciertas clases adicionales, de ese nivel adicional de indirección que es precisamente el que nos proporciona la potencia y flexibilidad necesario.
Por esto si la aplicación es pequeña no es aconsejable implementar una arquitectura reflexiva, ya que ésta elevarÃa la complejidad de la aplicación, su coste y los conocimientos necesarios para su mantenimiento sin aportar mucho a cambio.
Por tanto, ¿cuándo implementarla?:
- Precisamente cuando necesitamos un sistema altamente flexible en ciertos aspectos (ya sea por su dinámica en explotación o en su desarrollo, desconocimiento de requisitos o que sean muy difusos).
- Cuando tengamos una gran cantidad de elementos base (como por ejemplo tablas) en los que la otra opción serÃa la generación de enormes cantidades de bloques de código similar pero áltamente dependiente del objeto al que sirven de interfaz funcional (por ejemplo tablas de bases de datos).
- Cuando nuestra actividad técnica consista en realizar aplicaciones en las que por lo general se utilizen técnicas de copiar-pegar-modificar o generación parcial de código. En este caso el desarrollo de una arquitectura reflexiva que reutilicemos en todos nuestros desarrollos queda plenamente justificada y amortizada.
Imaginemos un portal como el que hemos descrito al inicio de este artÃculo (AnotherAmazon.com) que gestione numerosas tablas distintas de objetos para su venta, es decir aquà existirÃan una gran cantidad de elementos base. Este es el escenario ideal para implementar una arquitectura reflexiva que además nos permita modificar de una forma centralizada y sencilla tanto la información que contienen estos elementos, añadir nuevos elementos de información como por ejemplo ‘Reproductores DVDsÂ’, etc.
Referencias
- [ACG] “Automated Code Generation”
- [CGIDS] “Code Generation is a Design Smell”
- [OAOO] “Only and Only Once”
- [RCG] “Reflection versus Code Generation”
- [POSA] Pattern-Oriented Software Architecture: A System of Patterns (POSA) Buschmann, Meunier, Rohnert, Sommerlad, Stal; Wiley and Sons, 1996.
- [WIR] “What is Refactoring”