archivo

Archivo de la etiqueta: orientación a objetos

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.

Otra de las métricas de Chidamber y Kemerer es WMC. El cálculo para una clase es bastante sencillo ya que es el resultado de la suma de la complejidad de cada método. ¿Qué valor se coge para la complejidad? Pues depende de la convención o implementación que se utilice en cada caso, en unos casos se utiliza como base la complejidad ciclomática, en otros, se asigna el valor 1 como complejidad a cada método, lo que hace que coincida la métrica con el número de métodos de la clase.

Esta métrica además de darnos la complejidad de una clase desde otro punto de vista diferente a las otras propuestas por Chidamber y Kemerer, puede servir de referencia para determinar la capacidad de reutilización de la misma, ya que teóricamente clases con más métodos son más específicas y por tanto de menos utilidad de cara a la reutilización.

En cualquier caso, tanto complejidad general de la clase como su capacidad de reutilización depende sobre todo de las características de los objetos que se quieren implementar con la clase y de la buena codificación que se aplique, ya que también podemos encontrarnos con clases con uno o dos métodos kilométricos que además de resultar bastante complejos de mantener, propicien que la clase no sea útil desde el punto de vista de la reutilización.

Por lo comentado anteriormente, particularmente me parece más acertado ponderar la complejidad de los métodos de una clase por su complejidad ciclomática (o utilizar cualquier otra estrategia para medir la complejidad, como por ejemplo su nivel de acoplamiento con métodos o atributos de otras clases) que darles a todos el mismo peso. La utilización de la complejidad ciclomática tiene también sus desventajas, ya que hace que la métrica real sea esa (la complejidad ciclomática) y no se tenga en cuenta a la hora de realizar la medición el número de métodos real de la clase (cuando puede ser una métrica muy sencilla para analizar el diseño de una clase), es decir, pueden ser métricas complementarias, perdiendo esa complementariedad en el momento en que todo se convierte en complejidad ciclomática.

NOC es otra de las métricas de Chidamber y Kemerer. Como no podía ser de otra forma, mide la complejidad de una clase desde un punto de vista diferente al del resto de métricas propuestas por estos autores. En este caso su cálculo se basa en contar el número de subclases que directamente heredan de la clase para la cual se calcula esta métrica.

Las subclases están acopladas con la clase de la que heredan, esto quiere decir que cuanto más clases hijas tenga una clase más prudencia hay que tener con cambios relacionados con el comportamiento y especificación de los métodos, ya que podría tener trascendencia sobre sus subclases. Por ese motivo, es necesario ser más exhaustivo en las pruebas de clases con un NOC alto, ya que cualquier problema en las mismas tiene un impacto importante en el programa. Por tanto, se puede deducir de esto que clases con un número de hijos elevado son más complicadas de mantener que otras con un número más bajo.

En el árbol de jerarquía de clases de un determinado programa resulta razonable que las clases situadas en los niveles más alto de la jerarquía tengan un valor de NOC superior a las clases que se encuentren en niveles inferiores, por ese motivo, la existencia de clases con un NOC alto en comparación con otras que se encuentran en niveles superior de la jerarquía, puede ser un indicador de un mal diseño de la clase o una utilización no adecuada de la herencia.

Por otro lado, clases con un NOC alto favorecen su comprensibilidad y la capacidad de implementación de nuevas reutilizaciones, ya que existe un número de clases de ejemplo donde se puede estudiar cómo han aplicado la herencia y como se ha reutilizado la clase.

CBO es otra métrica de las propuestas por Chidamber y Kemerer y se utiliza para medir el acoplamiento entre clases.

Originalmente, el cálculo del CBO para una clase, según las especificaciones de sus autores, se realizaba teniendo en cuenta las dependencias hacia dentro de la clase y las dependencias de la clase respecto a otras. Esto era por la propia definición que se realizó para acoplamiento entre dos clases, ya que se decía que dos clases estaban acopladas cuando una utiliza/accede a métodos y atributos de otra (por tanto se establecía una relación bidireccional). En este cálculo sólo se contaba cada ocurrencia una sóla vez, es decir, si una clase utilizaba dos métodos de otra, sólo contaba una vez. Además tampoco se tenían en cuenta las relaciones de herencia entre clases.

Posteriormente diversos autores reinterpretaron el concepto, lo que ha dado lugar a que en la actualidad en el cálculo del CBO en la mayoría de los casos solo se tengan en cuenta las dependencias hacia fuera de una clase (Fan Out). También existen interpretaciones de esta métrica donde se tienen en cuenta las relaciones de herencia.

La existencia de diferentes interpretaciones de la métrica provoca que en ocasiones la lectura de bibliografía sobre la misma no especifique con claridad si se cuenta solo las dependencias salientes o si hay que contar (como originalmente) las dependencias entrantes y salientes. Por ese motivo, si tomais la decisión de buscar más información sobre la materia tened en cuenta que podéis encontraros con definiciones y métodos de cálculo diferentes en función de la fuente que consultéis.

En cualquier caso e independientemente de la interpretación de la forma de cálculo, tal y como comenté anteriormente y tal y como el mismo nombre de la métrica indica, lo que pretende medir es el acoplamiento o lo que es lo mismo el grado de impredecibilidad del comportamiento y funcionamiento de una clase por la dependencia que tiene de otras y si se toma la definición original de Chidamber y Kemerer además el grado de responsabilidad o influencia de esta clase respecto de otras, es decir, el grado en el que modificaciones de la clase puede afectar al funcionamiento o compartamiento de otras.

Como sucede en el caso de otras métricas que miden acoplamiento, como por ejemplo RFC o DIT, es una medida de la complejidad de una determinada clase y por tanto, valores de CBO altos sugieren que puede ser una clase candidata a ser tratada mediante pruebas unitarias (aunque como bien sabemos las pruebas unitarias no entran en aspectos funcionales) y a estar en cuarentena en las tareas de mantenimiento por posibles efectos colaterales provocados por la modificación de clases con las que tiene dependencia hacia afuera.

DIT es otra de las métricas propuestas por Chidamber y Kemerer. Como su propio nombre indica calcula para cada clase su distancia o profundidad respecto a la clase más alta en la jerarquía (en el caso de java, por ejemplo será Object, que es superclase (directa o indirectamente) de todas las clases).

DIT es otra medida de complejidad, ya que conforme se va heredando de clases que heredan a otras, es mayor la “carga” de métodos que puede soportar la clase y si por ejemplo la métrica RFC cuenta los métodos propios de la clase, estos métodos heredados también podrían considerarse como propios y por tanto ser tenidos en cuenta como un factor de complejidad.

Utilizando el mismo razonamiento la herencia podría considerarse como otra forma de acoplamiento, por lo que una modificación en la definición de un método de una clase superior o de su comportamiento pueden tener impacto en clases inferiores de la jerarquía cuando se instancian objetos que hacen uso de dichos métodos. Como es lógico, existen determinadas estrategias de diseño y programación que pueden reducir el nivel de acoplamiento (de la misma manera que malas prácticas pueden incrementarlo).

En resumen, una clase que se encuentra en un nivel bajo en la jerarquía de herencias, tendrá teóricamente un comportamiento más impredecible que otras situadas en niveles más altos.

No se trata de criticar la herencia, ni mucho menos, ya que es uno de los pilares de la programación orientada a objetos y de la reutilización de código (algo fundamental para la reducción del número de errores y por tanto conseguir sistemas de mayor calidad, cuando se hereda de clases suficientemente probadas y documentadas y sobre todo para reducir los costes de desarrollo), sino de intentar explicar por qué una clase de una determinada aplicación que se encuentra en un nivel inferior en el árbol de herencias de la misma puede considerarse de mayor complejidad (desde este punto de vista, porque como he publicado en otros artículos, la complejidad de una clase puede verse desde muy diversas formas y esta es una más) que otra que se encuentra en un nivel superior.

En el caso de java, el hecho de que únicamente se pueda heredar cada vez de una clase (herencia simple) permite reducir la complejidad en el árbol de herencia (cierto es que la profundidad no varía, pero el hecho de que cada nodo pueda tener más de un nodo padre “multiplica” los atributos y métodos potencialmente heredables). Cierto es que en java existe se permite la implementación de más de una interfaz (y de esta forma conseguir una especie de herencia múltiple), pero desde mi punto de vista (supongo que puede haber otras opiniones) el nivel de complejidad que se añade es inferior a si se permitiese herencia múltiple.

Como siempre, surge la pregunta del millón, ¿a partir de qué profundidad podemos considerar este tipo de complejidad como un factor de riesgo para una clase? he estado consultando y parece que está bastante aceptado que a partir de una profundidad mayor o igual a 5 empieza a existir ese riesgo (también hay quienes incrementan ese riesgo a profundidades mayores o iguales a 8).

En resumen, no se puede “castigar” a nadie por intentar reutilizar y la herencia es fundamental para ello, no obstante no debemos olvidar que árboles de herencia de mucha profundidad hacen teóricamente más impredecible el comportamiento y funcionamiento de las clases situadas en niveles más bajos de la jerarquía, así como se incrementa su dependencia respecto de otras.

Shyam R. Chidamber y Chris F. Kemerer enunciaron en el año 1994 un total de seis métricas para la medición de la calidad en el desarrollo de aplicaciones orientadas a objetos: WMC (Weighted Methods Per Class), DIT (Depth of Inheritance Tree), NOC (Number of Children), CBO (Coupling Between Object Classes), RFC (Response for Class) y LCOM1 (Lack of Cohesion of Methods).

En este blog, he analizado alguna de sus métricas (o derivadas de las mismas), como por ejemplo LCOM4 y RFC y más adelante seguiré tratando otras de las que fueron enunciadas por ellos.

Esta definición de métricas no cayó en saco roto y ha sido objeto de estudio y discusión desde su especificación, ya que lo primero que se suele plantear cuando se especifican un conjunto de métricas es si realmente sirven para algo y en este caso se trata de comprobar si realmente a partir de ellas se puede saber si un determinado software o componente del mismo puede presentar problemas que influyan en la calidad o no del software (y en su correspondiente deuda técnica).

Un ejemplo de estudio de estas métricas lo tenemos en la NASA que a finales de 2001, publicó un estudio realizado por el Centro Tecnológico de Aseguramiento de la Calidad del Centro de Vuelo Espacial Goddard (principal laboratorio de investigación de la NASA) que tenía como objetivo encontrar la manera o un método de desarrollar software más barato y con una mayor calidad (finalidad esta que es como la piedra filosofal de todos los que nos dedicamos al desarrollo de software).

Para ello en dicho estudio se pretendía seleccionar, del conjunto de métricas que utilizaban para medir la calidad del código, una serie de ellas a las que denominan conjunto ortogonal de métricas orientadas a objetos. Se les llama ortogonales porque cada una de ellas mide características diferentes del código y su diseño y el aspecto u objetivo que quiere medir una, no se ve afectado directamente por variaciones en el valor de la otra (dicho de otra manera, cada métrica tiene una única interpretación que dependerá intrínsecamente de su valor). El objetivo era obtener un conjunto de métricas lo más reducido posible a partir de la cual se pudiera medir la calidad general de un determinado software orientado a objetos. Este conjunto de métricas coincidía con las especificadas por Chidamber y Kemerer.

Calcularon el valor de estas métricas en tres sistemas diferentes desarrollados siguiendo el paradigma de la orientación a objetos, dos en lenguaje Java y el último en C++, para los cuales se disponía de un etiquetado de su calidad, mediante un análisis previo utilizando el conjunto completo de métricas que se estaban utilizando para medir la calidad del software (que es mayor, como es lógico que el conjunto reducido de métricas que se pretenden obtener). Entre las conclusiones que se obtuvieron se encuentra que este conjunto de métricas podía servir para determinar la calidad de un determinado software orientado a objetos.

Esto vino a refrendar estudios realizados anteriormente (por ejemplo, uno realizado por la Universidad de Maryland) en el que se obtuvo como conclusión que este conjunto de métricas resultaba válido.

Sin embargo, el gran problema que encontramos con estas métricas (sucede lo mismo con otras), es la dificultad de poder comparar los valores que se obtienen para cada una de ellas con otros que nos permitan indicar si el nivel de calidad de la aplicación, de una clase o de un método es el adecuado. Esto además se complica porque habría que tener en cuenta también la naturaleza de la aplicación desarrollada y también los umbrales de calidad que una organización estaría dispuesto a admitir. En cualquier caso, en ocasiones el simple valor que se obtiene para una métrica o la comparación de una misma métrica para dos sistemas distintos permite hacernos una idea de su nivel de calidad. Además existen herramientas que para algunas métricas tienen unos valores umbrales por defecto definidos (los cuales además, suelen ser parametrizables).