Complejidad ciclomática

La complejidad ciclomática es una métrica a tener en cuenta para evaluar la mantenibilidad, comprensión, testeabilidad y probabilidad de errores de una determinada aplicación.

Este término fue propuesto por Thomas J. McCabe en 1976 y tiene como una de sus características principales su independencia respecto a los lenguajes de programación (por lo menos, de los más convencionales), por lo que teóricamente podrían ser comparables aplicaciones realizadas con tecnologías distintas en base a su complejidad ciclomática. Digo teóricamente, porque además de la algorítmica las características propias del lenguaje de programación pueden influir en que sean más sencillas o no su inteligibilidad, mantenibilidad, etc… No obstante y en cualquier caso, resulta una aproximación muy a tener en cuenta de esos factores.

¿Qué mide la complejidad ciclomática? Pues el número de posibles caminos lógicos que tiene una aplicación. ¿Quiénes crean esos posibles caminos lógicos? Los métodos, las sentencias selectivas (if, case), sentencias iterativas (while, for), lanzamiento y tratamiento de excepciones (throw, catch), devolución de resultados de un método cuando no es lá última línea del mismo (return), operadores lógicos en guardas, etc…

Cuanto más caminos lógicos tenga una aplicación, una clase o un método más complejo resulta y por tanto, requiere un mayor esfuerzo desde el punto de vista de las pruebas unitarias (el número de pruebas unitarias para recorrer todo el código crece linealmente con la complejidad ciclomática (además de cumplirse eso, se verifica que: 1) la complejidad ciclomática es menor o igual que el número de casos de pruebas que se requiere para hacer cobertura de todo el código de la aplicación (téngase en cuenta que los else no suman complejidad ciclomática), 2) la complejidad ciclomática es mayor o igual que el número de casos de prueba que se requieren para verificar que realmente se «abren» los distintos caminos físicos)).

Por tanto, una baja cobertura de una aplicación viene a decir que existe una gran cantidad de código que no se ha verificado (utilizando esta estrategia), que se recorra, por lo que probablemente si no se ha recurrido a pruebas intensivas de la aplicación, muy probablemente se traduzca en errores evitables que se detectan posteriormente, incluso en producción).

También se requiere un mayor esfuerzo para la mantenibilidad y comprensión del código (¿cuántas veces nos hemos perdido en un mar de sentencias if anidadas?) y por todo lo anterior se incrementan las posibilidades de que existan errores, porque ¿cuántas veces nos hemos encontrado con que un problema en un método era provocado porque no se metía por la guarda correspondiente de una condicional o porque no entraba en un determinado bucle?, por lo que simplemente la mera experiencia nos indica que debe existir una relación entre la complejidad ciclomática y el número de errores de una aplicación.

Como la complejidad ciclomática es el resultado de los diferentes caminos lógicos que puede tener una aplicación es lógico que esta crezca conforme más extensa (en líneas de código) es una aplicación y por regla general una aplicación con muchas funcionalidades, grande, etc…, es más compleja, requiere mayor codificación y tendrá como consecuencia una mayor complejidad ciclomática (de esta manera existirá prácticamente una relación entre una clasificación de las aplicaciones por líneas de código y una clasificación de las aplicaciones por complejidad ciclomática, es importante señalar que no tienen por qué conservar el mismo orden aunque sí más o menos estarán por el mismo entorno, es decir, la segunda aplicación con más líneas de código, puede ser la cuarta en complejidad ciclomática o la quinta aplicación con más código, la segunda en complejidad. Por tanto este tipo de complejidad es inherente a las aplicaciones y existe por el simple hecho de la existencia de las mismas y por tanto, aplicaciones con envergadura se traducen en aplicaciones complejas de mantener y eso es consecuencia de su complejidad ciclomática intrínseca.

Independientemente de que una codificación eficiente puede eliminar caminos lógicos innecesarios, existe un límite y es el que da la propia dinámica de funcionamiento de un sistema de información que provoca que se codifique su comportamiento en algoritmos que como se han comentado anteriormente pueden ser complejos. Cuando se aproxima a este límite, lo más que se puede hacer es utilizar la complejidad ciclomática para determinar si determinados métodos o clases son demasiado complejas y requerirían ser analizadas de cara a dividirlas en otras más simples, mejorando así la mantenibilidad de las mismas.

Existen algunas clasificaciones basadas en rangos de complejidad ciclomática que determinan cuando es recomendable hacer la simplificación de determinadas clases y métodos y también hay casos de éxito de la aplicación de los mismos en algunas compañías.

En muchas ocasiones a simple vista se puede determinar si un método o una clase tienen una complejidad ciclomática alta, no obstante, esta tarea puede ser agotadora si la aplicación a analizar es muy grande, por ese motivo, lo mejor es delegar esa función en analizadores estáticos de código, como es el caso de Sonar, que permite obtener métricas de la complejidad ciclomática tanto a nivel de aplicación, como de clases y de métodos y utilizando su implementación de funcionalidades de drill down ir de datos más generales a información más de detalle.

Como todas las métricas de ingeniería del software, no hay que tomarse los resultados de la complejidad ciclomática al pie de la letra, sino saber interpretar qué es lo que están diciendo y tomar medidas para reducir el impacto de dicha complejidad en la aplicación (mayor cobertura de pruebas unitarias) y para tomar decisiones de revisión de la codificación en métodos y clases complejos y su posible simplificación en otros más sencillos.