08 enero 2011

Reingeniería de software (1era parte)

Diseñar un sistema desde cero es una actividad creativa y entretenida. Elegimos la tecnología más adecuada, pensamos un diseño que cumpla con las necesidades e intentamos hacer las cosas lo mejor posible.

Luego: nuevos requisitos, tiempos ajustados, parches en el diseño, nuevos desarrolladores, más funcionalidad,  interacción con otros sistemas y tecnologías; más parches, ese framework que era novedoso ya es obsoleto y problemático; cambios de presupuesto, rotación del equipo...

Y así, nuestro hermoso “bebe” devino en una bestia inmanejable.
Hombre lobo o bola de lodo, no importa, cambiamos de trabajo/proyecto y ahora empezamos de nuevo.
Ilusos -todo vuelve- ahora cae a nuestras manos una bestia.

Primer impulso: dinamitemos la cosa y empecemos de cero.
No es mala idea, hay quienes estiman que el costo de mantenimiento va del 50% al 75% del costo total de un sistema de software (Davis, Sommervielle).
El problema es que el costo de desarrollo no es la única variable a tener en cuenta: desde el punto de vista de los negocios comenzar de nuevo puede ser una maniobra de mucho riesgo.

¿Por donde empezar?
Bienvenidos a la reingeniería de software.
Para adentrarnos en este tema vamos a comenzar por ponerle nombre a algunas cosas que probablemente ya sabemos -una tarea muy aburrida pero útil a la hora de buscar y compartir información.

La mayoría de los investigadores en este área utilizan los términos descriptos en el articulo de Chikofsky y Cross y en el paper de Kazman, Woods y Carriere:


Reengineering. Reengineering, also know as both renovation and reclamation, is the examination and alteration of a subject system to reconstitute it in a new form and subsequent implementation of the new form.
Reengineering generaly includes some form of reverse engineering (to archive a more abstract description) followed by some form of forward engineering or re-structuring.

Es decir reingeniería es alterar un sistema existente y cambiarlo de forma para permitir su evolución. Esta tarea incluye entender lo que ya existe mediante ingeniería reversa:

Reverse engineering. Is the process of analyzing a subject system to
identify the system’s components and their interrelationships and
create representations of the system in another form or at higher level of abstraction.

La ingerniería reversa solo abarca la parte de examinar un sistema existente. Esta se puede dividir en dos sub-areas:

Redocumentation. Redocumentation is the creation or revision of a semantically equivalent representation within the same abstraction level.

Design Recovery. is a subset of reverse engineering in which domain knowledge, external information, and deduction or fuzzy reasoning are added to the observations of the subject system to identify meaningful higher level abstractions beyond those obtained directly by examining the system itself.

En ejemplos: Redocumentation es lo que hacen herramientas como Enterprise Architect que generan diagramas UML de clase a partir del código existente. El problema con este tipo de herramientas es que están al mismo nivel de abstracción que el código actual: recorrer un diagrama de clases de este tipo no es muy diferente a utilizar un “browser” de código en un IDE.

Sin embargo para poder comprender un sistema grande se necesita poder recrear otro tipo de información. Por ejemplo poder identificar módulos (más alla de la división de paquetes en el código)  y relacionarlos con la funcionalidad o poder entender la evolución del sistema y su relación con diferentes refactorings, funcionalidad y defectos.

Design Recovery se refiere a este segundo tipo de herramientas, y es donde se concentra el research en el área de reingeniería.

Para poder llegar a hacer herramientas de Design Recovery, primero es necesario tener herramientas que puedan analizar el código. Este tipo de herramientas se llaman herramientas de Program Analysis y las hay de tres tipos: estático (analizan la estructura del código), dinámico (analizan las interacciones en runtime) e histórico (analizan como el código fue cambiando historicamente). Hay varios desafios en el area de program analysis, para hacer un resumen de ellos:

  • análisis estático: lidiar con diferentes lenguajes de programación o scripting en un mismo sistema.
  • análisis dinámico: lidiar con enormes cantidades de información, un simple “trace” de una aplicación real puede ser enorme.
  • análisis histórico: lidiar con herramientas de SCM y el hecho que los “eventos” en el tiempo no son registrados de una forma uniforme.


Hacer Design Recovery significa buscar crear un modelo mental del sistema que permita comprenderlo, lo cual implica buscar formas de visualizar el software. Esto nos lleva a las herramientas de visualización.

Crear visualizaciones de un software no es fácil, por que hay muchas “dimensiones” e interrelaciones, asi que un área de estudio importante es como crear visualizaciones que permitan entender un sistema grande más facilmente.

En el próximo post voy a hablar un poco sobre herramientas de visualización.

Por ahora que ya saben los términos usados en este área de estudio les dejo algunos links a conferencias relacionadas con reingeniería de software:

13 abril 2010

Composición

Hace unos años atrás cuando dictaba cursos sobre POO y Java, di un ejercicio que consistía en diseñar el "checkout" de un Supermercado con soporte para ofertas del estilo "lleve 2 y pague 1".
La intención del ejercicio era que los alumnos piensen en como utilizar objetos para lidiar con la complejidad de aplicar las diferentes ofertas.
Esperaba también que se dieran cuenta que el precio de un producto depende de factores como: la fecha, el "mercado", ofertas u estrategias de negocio.
Imagine que comenzarían planteando la clase Producto con un "estilo base de datos":


Y que a lo largo del ejercicio iban a llegar a una especie de "lista de precios":


Sin embargo no fue como esperaba. Para uno de los alumnos la expresión "el producto tiene un precio" significaba que el un producto tenia que responder directamente su precio. Trate de justificar mi posición desde distintos puntos de vista, pero luego abandone la discusión.

Nota al margen:
Mi experiencia dando clases me enseño que es mejor no entrar en discusiones sin salida. Es preferible escuchar pacientemente y expresar cuales son los problemas del planteo desde distintos puntos de vista, con suerte la discusión evoluciona y se aprende mucho en el proceso. Pero si la discusión se estanca, es preferible abandonarla.

Escribo sobre este ejemplo después de tanto tiempo, por que veo que el problema de como expresar la relación de composición se repite en otros contextos.
Por ejemplo ¿Cómo modelarían que "un cliente que tiene contactos"? Una forma de hacerlo es:



Sin embargo, al hacer la traducción literal de este diagrama de clases, el modelo esta diciendo: "el cliente esta compuesto de contactos". La diferencia parece sutil, pero a medida que el sistema crece tiene consecuencias importantes:
  • Agregar nuevos contactos implica modificar la instancia de cliente y trae consecuencias en la forma de asegurar que se cumplan las reglas de negocio, por ejemplo: que un cliente cambie su numero CUIT es raro y puede afectar a los sistemas de facturación; pero es esperable que sus contactos cambien seguido sin mayores consecuencias para el resto del sistema, entonces hay que distinguir entre "modificaciones pesadas" y "modificaciones livianas".
  • Transmitir el cliente por "el cable" implica transmitir sus contactos, y como esta información puede ser pesada uno tiene que usar alguna estrategia como un objeto que represente una vista del cliente sin sus contactos (DTO), o bien hacer que la lista de contactos se cargue por demanda (lazy load).

Nota al margen: Creo que uno de los causantes de confusión es el uso del verbo tener. En nuestra manera de expresarnos es natural decir "el producto tiene un precio", sin embargo el "tiene" de nuestro lenguaje no expresa necesariamente una relación de composición. Erich Fromm, escribió un libro muy bueno llamado "Tener o Ser" que habla sobre la influencia del "tener" en la sociedad. Es curioso como el consumismo cambio nuestras formas de expresión, por ejemplo en lugar de decir "siento un dolor de cabeza" decimos "tengo un dolor de cabeza".

En cierto sentido el problema es similar al de Producto-Precio, una alternativa para expresar la relación del cliente con sus contactos es tener una "agenda de contactos" mediante la cual se pueden encontrar los contactos para un cliente:



¿Qué modelo expresa mejor la realidad?

Creo que pensar que un modelo es correcto por que expresa "la realidad" mejor que otro, sufre del problema de dar por sentado que nuestra percepción y traducción al sistema es absolutamente correcta: para uno de mis alumnos la realidad que "el producto tiene un precio", se traducía correctamente en "unProducto.getPrecio()".

Por eso es necesario tener en cuenta cuestiones que afectan a la percepción y traducción del problema:
  • El verbo "tener" tiene un uso extremadamente ambiguo en nuestra cultura, por ejemplo es más adecuado decir que a un producto se le asigna un precio y quizás de esa forma ya vemos el dominio con otra perspectiva.
  • Los "ritmos" de cambio pueden hacer que sea más conveniente pensar en objetos distintos. Por ejemplo el precio varia con el tiempo y el entorno, pero el producto no; algo similar ocurre con el cliente y sus contactos.
    Separar las cosas que cambian a distintas velocidades ayuda a evitar side effects innecesarios y simplifica la aplicación de reglas de negocio.
  • Ver si existe en el dominio un intermediario en la relación, como por ejemplo una "lista de precios". Es claro que el software es un medio distinto: la lista de precio existe físicamente por que es una herramienta para definir y recordar el precio de un producto, y ese rol puede cumplirse en software sin necesidad de tener una "lista de precios" propiamente dicha. Sin embargo explorar esas analogías puede servir para ver conceptos del dominio.