archivo

Archivo de la etiqueta: abstracción

Me parece muy interesante la reflexión que la Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides) realizaron en el libro Design Patterns: “Una clase es más reutilizable cuando minimizas los supuestos que otras clases deben hacer para utilizarla”.

Es cierto que si piensas demasiado en quién la puede utilizar y cómo, estás diseñando condicionado por ese motivo y en muchos casos se aplicarán supuestos más orientados a un desarrollo a medida de que probablemente reducirán su capacidad de ser utilizada por otras.

¿Es extensible esta reflexión al desarrollo de infraestructuras de mayor nivel? Mi opinión es que sí, pero en estos casos es necesario analizar los pros y los contras de esa solución más genérica y abstracta porque en este caso puede condicionar demasiado el diseño del sistema que hace uso de esa infraestructura y restarle posibilidades de ser más productivo y eficiente, pero también y desde otro punto de vista, si se particulariza demasiado es posible que al final tengamos diferentes componentes software muy parecidos replicados con determinadas características específicas y estemos reinventando la rueda.

A priori es complicado decir qué es mejor (refiriéndome a infraestructuras de mayor nivel) hay que analizar qué cometido hace ese componente software y que aporta que sea un elemento genérico o abstracto.

La torre de vudú como antipatrón es consecuencia de la siguiente práctica de programación:

Se tiene un código que se sabe que funciona (aunque generalmente no se sabe muy bien cómo) y se pretende añadir algún tipo de funcionalidad adicional, en ocasiones no muy cohesionada con la ya implementada y se le coloca un envoltorio (wrapper) proporcionando una nueva interfaz de acceso a ese nuevo componente. Se puede considerar, por tanto, una extensión del antipatrón “Voodoo chicken coding“.

Recibe el nombre de torre de vudú porque todo parte de un código correcto a la que se le van añadiendo diferentes capas de abstracción que van proporcionando funcionalidades adicionales a la original, de manera que al final tenemos un módulo que puede funcionar pero que tiene como punto débil la parte de código que no comprendemos de manera adecuada (y que está en la base de la torre, es decir, fue la semilla a partir de la cuál se construyó la misma) y/o de código que a través de sucesivas abstracciones resulta complicado de entender al ir extendiéndose su funcionalidad para realizar tareas muy diferentes a la inicialmente prevista.

Ante un hecho o un problema lo más normal es que la percepción de cada observador sea diferente. En algunos casos serán solo matices, en otros la divergencia será mucho mayor.

Cuando vamos a desarrollar software lo que hacemos es realizar una abstracción de esa percepción que tenemos del mundo real. Para realizar esa abstracción, además de nuestros sentidos, nuestro conocimiento y nuestra experiencia intervendrán los usuarios y otros componentes del equipo de proyecto.

Mediante la abstracción se busca un consenso entre percepciones diferentes, priorizándose la visión del usuario porque al final serán ellos los que utilicen el sistema de información.

Pero la abstracción no solo se limita a aspectos funcionales, también se centra en aspectos técnicos del propio proceso de desarrollo. La arquitectura de un sistema de información no es más que la definición de un esqueleto que será rellenado con el código fuente de la aplicación o por terceros componentes en los que se delega funcionalidad. En esta abstracción entran en juego otros perfiles, aunque deben tener en cuenta las características del sistema que se abstrae desde el área funcional.

La abstracción puede entrar en todos los detalles que uno quiera pero realmente hasta que no se implementa una solución no se termina de poner forma a sus múltiples esquinas. En ocasiones se acertará a la primera, en la mayoría de los casos serán necesarias varias iteraciones para encontrar una solución que se considere satisfactoria.

La incertidumbre existente en el proceso de desarrollo de software ya de por sí es motivo suficiente para aportar por un enfoque ágil en los proyectos (al menos, en la mayoría de ellos), si a eso le sumamos la propia naturaleza de la transformación de percepciones heterogéneas de la realidad a un conjunto de líneas de código, encontramos otra justificación adicional a dicho enfoque.

Este antipatrón recibe el nombre de una canción infantil. Os la pongo tal cual:

Oh, The grand old Duke of York,
He had ten thousand men;
He marched them up to the top of the hill,
And he marched them down again.

And when they were up, they were up,
And when they were down, they were down,
And when they were only half-way up,
They were neither up nor down.

Como podréis ver describe simplemente la acción de subir una colina, para después volverla a bajar. Se considera como una metáfora del esfuerzo invertido de manera gratuita o innecesaria.

No obstante, este antipatrón, aunque en el fondo haga referencia a un esfuerzo de esas características, quiere hacer hincapié sobre todo en una de sus posibles causas.

De igual manera que roles de carácter funcional resultan de mucha utilidad como nexo de unión (que no intermediario) entre usuarios y equipo de proyecto, también es necesario destacar la figura de los arquitectos software y a más bajo nivel los analistas orgánicos.

En el caso de los primeros existe una visión más abstracta del comportamiento funcional del sistema de información y en el de los segundos existe una visión más abstracta de la arquitectura de la aplicación, en ambas situaciones se ve el bosque y no los árboles.

Es frecuente encontrarnos con proyectos donde alguno de los dos perfiles no interviene (cuando no los dos), dejando a los programadores (especialistas en la implementación) prácticamente todas las decisiones a nivel e ejecución de las especificaciones del usuario.

Esto suele provocar (de ahí la analogía con la metáfora de la canción de “El gran viejo Duque de York”) que determinadas decisiones, al tener una visión demasiado a bajo nivel de la aplicación, no busquen la solución más óptima y obligue a hacer esfuerzo de más o incluso a un esfuerzo innecesario.

Por tanto, lo que viene a decir este antipatrón es que equipos de proyecto descompensados entre visiones más abstractas y más concretas pierden productividad.

Responsibility-driven design es una técnica de diseño orientado a objetos propuesta por Rebecca J. Wirfs-Brock and Brian Wilkerson.

Rebecca J. Wirfs-Brock es una autora y consultora americana especializada en los ámbitos del diseño y programación orientada a objetos, con una gran experiencia desarrollada en diferentes empresas.

Brian Wilkerson es otro especialista en programación y diseño orientado a objetos, que ha desarrollado su carrera profesional en el mundo de la consultoría.

A principio de los 90 fueron coautores de los libros en los que formulaban esta técnica de diseño.

El diseño orientado a la responsabilidad se basa en dar respuesta para cada tipo de objeto a las siguientes preguntas:

– ¿De qué acciones es responsable el objeto?
– ¿Qué información comparte el objeto?

Proporcionando una alternativa a los diseños que consideraban a los objetos como datos y algoritmos. En este caso los objetos se consideran como la conjunción de roles y responsabilidades, los cuales conviven cooperando dentro de la aplicación.

Esta técnica de diseño está claramente inspirada en la mecánica de funcionamiento cliente/servidor, en la cual se define un contrato que rige el funcionamiento de los objetos implicados de manera que el cliente solo puede realizar los tipos de solicitudes/peticiones que se hayan definido y el servidor debe responder.

Es una técnica que también huye del conocimiento de los detalles de cómo un objeto realiza una acción u obtiene un resultado, fomentando de esta forma el concepto de encapsulamiento. Además, para reforzar esta cualidad, Wirfs-Brock y Wilkerson abogan por la existencia de un control de grano fino de visibilidad de los objetos.

No solo es una técnica que persigue el encapsulamiento sino la abstracción al ocultar, al menos en términos de diseño, la relación entre datos y comportamiento, ya que solo se debe pensar en responsabilidades a nivel de conocer, hacer y decidir.

En la técnica, por tanto, tendremos una comunidad de objetos que tienen asignadas responsabilidades específicas y que conforman un modelo colaborativo de funcionamiento en el que los mecanismos de colaboración están claramente definidos constituyendo de esta forma una arquitectura en la que existe un comportamiento distribuido.

De esta forma podemos realizar las siguientes definiciones:

¿Qué es una aplicación? Un conjunto de objetos que interactúan.
¿Qué es un objeto? La implementación de uno o más roles.
¿Qué es un rol? Un conjunto de responsabilidades
¿Qué es una responsabilidad? La obligación de realizar una tarea o de conocer un determinado tipo de información.
¿Qué es una colaboración? A la interección entre objetos y/o roles.

Tal y como se ha comentado anteriormente, esta técnica considera a los objetos como algo más que paquetes que solo encierran lógica y datos, ahora se en función de los roles que desempeñen a controladores, proveedores de servicios, titulares de información, interfaces con el exterior, etc…

Continuando con el estudio del grado de estabilidad (o inestabilidad) de un paquete en función de sus valores de acoplamiento y acoplamiento eferente, tal y como fueron enunciados por Robert Cecil Martin, toca establecer su relación con una de las características de la programación orientada a objetos como es la abstracción.

El mismo Robert Cecil Martin especificó que un paquete con una estabilidad máxima (o inestabilidad = 0) debería ser abstracto al máximo y que un paquete con estabilidad mínima tiene que ser concreto al máximo.

El razonamiento resulta lógico, ya que un paquete con inestabilidad 0 quiere decir que tiene no tiene acoplamiento eferente, es decir, no depende de ninguna clase de otro paquete y que como mucho tendrá valores de acoplamiento aferente o lo que es lo mismo hay clases de otro paquete que dependen de clases de este paquete, las cuales “fuerzan” a los desarrolladores a minimizar los cambios en el paquete por sus posibles consecuencias en dichas clases externas. ¿Cómo se consigue llevar la estabilidad a su máxima expresión? Pues forzando a que el mayor número de clases del paquete sean abstractas.

Recordemos que una clase abstracta es aquella que tiene al menos un método abstracto y que un método abstracto es aquel que solo presenta su especificación pero no su implementación. Una clase abstracta no puede ser instanciada y las clases que heredan de ella tienen que implementar los métodos abstractos definido en la superclase o volver a declararlos como abstractos, lo que daría lugar a que dicha subclase también sería abstracta.

El grado de abstracción de un paquete se define mediante la siguiente fórmula:

Abstracción = Clases Abstractas / (Clases Abstractas + Clases no Abstractas)

De esta manera, al igual que sucede con la inestabilidad, los valores de abstracción se encontrarán también en el rango de 0 a 1, de manera que tendremos el valor 1 cuando el paquete tenga todas las clases abstractas (o lo que es lo mismo no tenga clases no abstractas) y 0 cuando el paquete no tenga clases abstractas.

Robert Cecil Martin expone una relación interesante entre el grado de abstracción y el grado de estabilidad, pero como se puede apreciar ese grado se tiene que forzar, ya que ni la abstracción no la estabilidad son función del otro. Esto provoca que se puedan producir situaciones en cierto modo paradójicas como por ejemplo las siguientes:

– Inestabilidad = 1 y Abstracción = 1. Esto querría decir que nos encontramos con un paquete con clases de las cuales no depende ninguna clase de otro paquete y sin embargo todas las clases del paquete son abstractas y por tanto no instanciables. Esta situación es tan paradójica que será difícil de encontrar en un programa, ya que vendría a decir que no se estaría utilizando el paquete para nada.

– Inestabilidad = 0 y Abstracción = 0. En este caso nos encontramos con un paquete cuyas clases no dependen de ninguna clase de otro paquete y que además ninguna de sus clases es abstracta. Esta circunstancia es menos paradójica y por supuesto posible en un programa, vendría a hacer referencia a un paquete estable y concreto y por tanto rígido, siendo un paquete a vigilar si el acoplamiento aferente es alto, debido a que las posibilidades de efectos colaterales puede ser alta.

Tal y como enunció Robert Cecil Martin, los valores más deseables para cada paquete serían Inestabilidad = 1 y Abstracción = 0 e Inestabilidad = 0 y Abstracción = 1, no obstante, cualquier paquete que se encuentre en la recta que une ambos puntos se considera que tiene unos valores de estabilidad y abstracción compensados o equilibrados.

De lo anterior se puede deducir una nueva métrica que es la distancia de un paquete respecto a esa recta ideal, que se calcula mediante la siguiente fórmula:

D = ABS((Abstracción + Inestabilidad – 1) / SQRT(2))

devolviendo unos valores que se encontrarían en el rango entre 0 y 0’707, que indicarían que el paquete se encuentra sobre la recta o en el punto más extremo posible.

También hay cálculos de dicha distancia estableciendo el rango entre 0 y 1, aplicando la siguiente fórmula:

D = ABS(Abstracción + Inestabilidad – 1)

Por tanto, con lo estudiado en el anterior artículo y éste, basado en los enunciados de Robert Cecil Martin, es posible tener una serie de métricas que nos permitan servir de referencia a la hora de estudiar la calidad en el diseño de paquetes (y en general del diseño) de una determinada aplicación.

Otras métricas de calidad que resultan interesantes de estudiar están relacionadas con el cálculo de acoplamiento aferente (hacia dentro) y eferente (hacia afuera) de un paquete y la relación entre los mismos, dando lugar a una métrica resultado de ellas denominada inestabilidad de un paquete. Estas tres métricas fueron propuestas por Robert Cecil Martin aka “Uncle Bob” (más conocido posteriormente por liderar iniciativas orientadas a metodologías ágiles de desarrollo y programación extrema) en el año 1995.

Desde la perspectiva de un paquete concreto el acoplamiento aferente se produce cuando otro paquete hace uso de atributos y/o métodos de clases de dicho clase o hereda de alguna de ellas y el acoplamiento eferente se produce cuanto dicha clase hace uso de atributos y/o métodos de clases de otro paquete o hereda de clases de dicho paquete.

Por tanto el cálculo del acoplamiento aferente se obtiene mediante la suma de clases de otros paquetes que cumplen las características indicadas en el párrafo anterior y el acoplamiento eferente se obtiene como la suma de clases de otros paquetes de los cuales dependen clases del paquete con el que se está trabajando.

Como su propio nombre indica se trata de métricas de acoplamiento y como hemos estudiado previamente en las métricas de Chidamber y Kemerer, existe relación directa entre el acoplamiento, la complejidad, la mantenibilidad y la selección de clases (o paquetes) donde hay que prestar más atención a la hora de realizar las pruebas unitarias.

Un alto acoplamiento aferente hace referencia a un paquete con un alto grado de responsabilidad. La responsabilidad y la estabilidad son dos conceptos que suelen ir cogidos de la mano, precisamente porque al existir un elevado número de clases que dependen de clases del paquete, se tiene que ser necesariamente más prudente a la hora de realizar modificaciones en clases del paquete por los posibles efectos colaterales que se pueden producir. No obstante la estabilidad de un paquete no es función exclusivamente del acoplamiento aferente como veremos más adelante en la definición de inestabilidad.

Un alto acoplamiento eferente hace referencia a un paquete con un alto grado de dependencia. La dependencia y la inestabilidad son también dos conceptos íntimamente relacionados, ya que el funcionamiento de las clases del paquete dependen del comportamiento de clases externas, lo que las hace susceptibles de efectos colatarales en las modificaciones de las mismas y por tanto su funcionamiento entraña una mayor incertidumbre.

La inestabilidad de un paquete es la métrica que enfrenta entre sí al acoplamiento aferente y eferente a través de la siguiente fórmula:

Inestabilidad = Acoplamiento eferente / (Acoplamiento eferente + Acoplamiento aferente)

Por tanto los valores de inestabilidad se encontrarán dentro del rango de valores entre 0 y 1.

Estudiando los valores extremos podemos afirmar que si la inestabilidad de un paquete es 1 quiere decir que el acoplamiento aferente es 0 o lo que es lo mismo ninguna clase externa al paquete tiene una relación de dependencia respecto a una clase del paquete, por lo que el paquete sólo tiene dependencias hacia clases externas al paquete. El valor 1 indica una inestabilidad “extrema” del paquete (no obstante, habría que valorar también el valor individual del acoplamiento eferente, ya que, es una opinión personal mía, no todas las inestabilidad con valor 1 tendrían por qué ser iguales) ya que por un lado su comportamiento depende de clases externas, lo cual la hace posible víctima de efectos colaterales y por otro el hecho que no haya clases que dependan del paquete, da una mayor libertad a la hora de realizar modificaciones en el mismo ya que no hay que pensar en terceras clases a la hora de realizar modificaciones en el mismo (también habría que matizar esto, ya que puede haber dependencias entre clases del paquete, medibles mediantes distintas métricas, que provoquen que no sea tan ágil o sencillo modificar el paquete).

Si la inestabilidad es 0 quiere decir que el acoplamiento eferente es 0, lo que viene a decir que el paquete solo tiene responsabilidades y por tanto es menos proclive al cambio debido a que modificaciones en el mismo pueden tener repercusiones en clases externas.

Como la inestabilidad puede tomar cualquier valor entre 0 y 1, en función de a qué extremo se acerque más podemos darle una interpretación al resultado.