archivo

Archivo de la etiqueta: Chidamber & Kemerer

En mi organización vamos a realizar un estudio de una serie de sistemas de información (entre el 50 y el 75% aproximadamente de los mismos) con el objeto de detectar en ellos una serie de clases que pueden resultar conflictivas desde el punto de vista de la mantenibilidad.

Cualquier clase que cumpla alguna de las siguientes condiciones la consideraremos sospechosa (son solo métricas, que pueden encender determinadas alarmas, pero después resulta aconsejable revisar si existe justificación o no en que tomen esos valores) y será objeto de estudio con la finalidad de tomar una decisión sobre la necesidad de su refactorización:

1) Acoplamiento. Utilizaremos como base la métrica RFC de Chidamber y Kemerer.

RFC(clase)>=50 y RFC(clase)/Nº Métodos(clase)>=10

2) Cohesión. Utilizaremos como base la métrica LCOM4 de Chidamber y Kemerer.

LCOM4(clase)>=2

3) Complejidad Ciclomática de Thomas J. McCabe.

Complejidad Ciclomática(clase)/Nº Métodos(clase)>=10

Los valores anteriores por cada clase se obtendrán utilizando las métricas obtenidas a partir de Sonar.

Cuando se habla de programación orientada a objetos se dice que un buen diseño es aquel que consigue una alta cohesión y un bajo acoplamiento. Como es lógico, estoy de acuerdo, no obstante de los dos conceptos la cohesión, tal vez por entenderse menos su utilidad, es el hermano pobre, es decir, si se tiene en cuenta alguno de los dos (algo que desgraciadamente no ocurre demasiado, ya que no se suele prestar suficiente atención a la calidad del diseño y de la codificación) se suele echar más en cuenta al acoplamiento, además de ser más inteligible por ser más evidentes los problemas existentes cuando el acoplamiento es alto.

La cohesión mide la relación entre los distintos elementos que componen una clase: atributos y métodos. Una baja cohesión da lugar a las denominadas clases tutti frutti, clases que pretenden hacer muchas cosas y diferentes.

Cuando se tienen clases con baja cohesión implica principalmente:

– Falta de comprensibilidad: La clase no sirve a un aspecto concreto, sino a muchos. Cuando pensamos en un objeto lo hacemos entendiendo que sus partes son necesarias para que exista su todo (es decir, las partes por sí solas tienen poca utilidad y es mediante su relación cuando dan entidad al objeto y hacen que en su conjunto (el todo) valga más que la suma de las partes por separado). Cuando tenemos clases cuyos elementos forman un todo, pero ese todo no es identificable con nada, nos encontraremos probablemente con clases que tienen una baja cohesión, con métodos que no guardarán relación con otros muchos de la misma y que servirán a propósitos distintos. Habrá que tener bien documentada la clase para mantenerla posteriormente, porque si la coge un equipo de desarrollo distinto o ha pasado mucho tiempo desde que se implementó resultará complicado entender para qué sirve y qué utilidad tienen muchos métodos y atributos de la misma.

– Dificulta la mantenibilidad: Además de por la disminución de la comprensibilidad de la clase, al tener más código del que debería tener, se incrementará en consecuencia la complejidad ciclomática de la misma y por tanto será más costoso realizar cambios.

– Dificulta la reutilización: Cuando tenemos una baja cohesión quiere decir que hemos modelado una realidad rara, un híbrido de muchas cosas. Se ha implementado una clase para algo muy específico y lo concreto pocas veces es reutilizable.

– Suelen tener un acoplamiento alto (en comparación a una cohesión baja): Tiene su lógica ya que la clase tendrá más métodos que si los diferentes objetos reales que están dentro de la misma se hubieran encapsulado en sus correspondientes clases. Más métodos implican por regla general llamadas a más métodos de clases y paquetes externos y en consecuencia un mayor acoplamiento.

– Tendentes a sufrir modificaciones: Como estas clases son híbridos de varias la probabilidad de tener que sufrir operaciones de mantenimiento crece (tanto como clases distintas pudieran contener en su interior), lo cual tiene su importancia sobre todo si tenemos en cuenta que este tipo de clases son más complicadas de mantener.

Una forma de medir la cohesión la tenemos con la métrica LCOM4, propuesta a partir de la métrica inicial LCOM1 de Chidamber y Kemerer. Esta métrica forma parte del conjunto de las mismas que calcula Sonar haciendo análisis estático de código.

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).

La métrica RFC (Response for class) forma parte, como sucede con el caso de LCOM4 del conjunto de métricas enunciadas por Chidamber y Kemerer y que tienen como objeto obtener valores de referencia sobre el diseño orientado a objetos de una determinada aplicación.

En el caso de RFC lo que se mide es el acoplamiento entre clases, ya que su valor se obtiene a partir del número total de métodos que pueden potencialmente invocados en respuesta a un mensaje enviado a un objeto de la clase, es decir, el número de métodos que pueden ser ejecutados, cuando un método de la clase es invocado.

Dicho de otra manera, vendría a ser el número de métodos de una clase más el número de métodos de otras clases que son invocados por métodos de la clase (no contando más de una vez cada método, es decir si un método de una clase Y puede ser invocado por tres métodos de una clase X sólo se suma una unidad).

¿Qué es el acoplamiento? Es el grado de dependencia entre clases, por lo que cuando nos encontramos con situaciones donde el acoplamiento es alto crecerán las posibilidades de que el mantenimiento de las clases sea más complejo, ya que un cambio en el comportamiento de un método puede afectar a todos los que lo llaman y un cambio de interfaz del método puede provocar la necesidad de modificar el código de los métodos que lo llaman. También se considera que clases con un acoplamiento alto son más complicadas de entender y dificultan las tareas de testeo.

Un ejemplo de acoplamiento, fuera del mundo de la orientación a objetos, lo tenemos por ejemplo en la dependencia entre una aplicación y una serie de objetos (por ejemplo tablas o vistas) de un esquema de base de datos de otra, en este caso, sobre todo si las aplicaciones son gestionadas por equipos distintos y todavía no están lo suficientemente maduras, se correrá el riesgo permanente de que un cambio en el modelado de datos provoque que la aplicación que hace uso de ellos deje de funcionar adecuadamente. Además se podrá ver afectada por otros factores (cambio de instancia del esquema, cambio de sistema de gestión de base de datos, etc…).

El acoplamiento entre clases es inevitable, ya que los objetos cooperan y precisamente ofrecen servicios a través de sus APIs públicas para poder ser utilizados por otros. No se trata por tanto de eliminar el acoplamiento ya que eso no será posible, sino de intentar conseguir niveles de acoplamiento entre clases bajos, ya que permitirán reducir el número de efectos colaterales en el mantenimiento del sistema, haciendo más sencillas estas tareas y reduciendo las tasas de errores en tiempo de compilación y en tiempo de ejecución (cuando se cambian comportamientos a los métodos). Además de tener en cuenta en el diseño de las clases la existencia de un bajo acoplamiento, se pueden utilizar diferentes estrategias, como por ejemplo la creación de clases donde se centralice ese acoplamiento e incluso se implementen en las mismas fachadas que orquesten llamadas a métodos, de manera que tengamos algunas clases que sí tendrán valores altos o muy altos de RFC, pero que a cambio permitirá que se reduzcan el RFC de un buen número de clases.

A través de Sonar se puede obtener la media de RFC de la aplicación, de cada paquete de la misma y del conjunto de clases que la conforman. Se trata de medias aritméticas puras, sin aplicar ningún tipo de ponderación o umbral de valor de RFC de una clase.

Ahora viene la pregunta de siempre en este tipo de métricas, ¿a partir de qué valor se puede considerar que un RFC es demasiado alto?. Como sucede en el resto de métricas (salvo algunas excepciones como LCOM4) hay interpretaciones para todos los gustos, también como sucede con las otras, queda bastante claro cuando un RFC es muy alto y un RFC es muy bajo, por lo que “desde lejos” se pueden apreciar valores que pueden recomendar el estudio del nivel de acoplamiento de determinadas clases. Se pueden considerar aceptables valores de RFC <= 50.

También es posible obtener valores relacionando el RFC de una clase y su número de métodos (NOM). En programas realizados en Java Se pueden considerar aceptables valores de RFC/NOM <= 10.