Complejidad ciclomática y como reducirla

Manu Pijierro
6 min readSep 27, 2018

--

Siguiendo con la serie de artículos orientados a comprender las métricas de calidad de un software (como por ejemplo “Métricas de calidad para componentes de software”), ahora voy a repasar la conocida complejidad ciclomática. Seguramente la hayas escuchado o leído más de una vez, incluso comprendido, pero ¿conoces que es la complejidad ciclomática? ¿que acciones podríamos llevar a cabo para reducirla? ¿cómo tendríamos que programar nuestras aplicaciones para tener una complejidad ciclomática lo más baja posible?

Complejidad ciclomática

La complejidad ciclomática es una métrica inicialmente propuesta por Thomas J.McCabe en un artículo suyo en 1976 en el que escribía sobre la complejidad en el desarrollo de software.

Esta métrica mide el número de flujos distintos de ejecución que puede tener el código de un artefacto de software, dicho llanamente, nos dice cuantos ifs-then-else, while, for, switch…etc, tenemos en nuestro código.

Nos va a dar una medida cuantitativa de como de complejo va a ser comprender el código analizado. A más complejidad ciclomática, más complejo será el código, más complicado de leer, de entender, de modificar, de mantener y, por lo tanto, más caro.

Además, obteniendo su valor, determina el número máximo de pruebas que hay que realizar para asegurarnos que nuestra cobertura de test pasan al menos una vez por cada línea del código.

Según la Wikipedia (y la mayoría de las páginas que he visto) los valores de referencia para hacernos una idea de lo complejo que puede ser un código son los siguientes:

Otras valoraciones que están en el libro Code Complete 2 (pag.458)de Steve McConnell, a la hora de valorar la complejidad ciclomática en una rutina lo hace de la siguiente manera:

0–5 La rutina es probablemente buena

6–10 Comienza a pensar formas de simplificar al rutina

10+ Extrae partes de las rutinas a otras y llámalas desde la primera

La medida de la complejidad ciclomática no es la única medida sobre la posible calidad del código. Hay otras medidas que se basan en la cantidad de datos usados, número de niveles anidados, número de líneas de código, cantidad de entradas y salidas…etc, pero si es la única que nos va a ayudar a pensar en el control de flujo de un programa y en la dificultad de trabajar con él.

Como calcular la complejidad ciclomática

El cálculo está definido en el artículo de McCabe enlazado anteriormente, pero no todas las herramientas las calculan de la misma forma. Por ejemplo, la aplicación NDepend calcula la complejidad ciclomática en base a las siguientes reglas:

  • Por defecto siempre comienza en 1
  • Se suma 1 por cada una de las siguientes expresiones encontradas en el cuerpo del método:

while | for | foreach | case | default | continue | goto | && | || | catch | ternary operator ?: | ??

  • No se tendrán en cuenta a la hora de sumar las siguientes expresiones:

else | do | switch | try | using | throw | finally | return | object creation | method call | field access

  • El resultado de la anterior suma nos daría la complejidad ciclomática del método, por lo tanto, la complejidad ciclomática de la clase sería la suma de la de los métodos que tiene.

SonarQube, por ejemplo, tiene su propio cálculo de la complejidad ciclomática dependiendo del lenguaje utilizado. En este enlace se puede ver cuando va incrementando el contador de la complejidad.

Reducir la complejidad ciclomática

No hay unas reglas escritas fijas que nos digan que hacer o que no hacer, pero si hay una serie de recomendaciones que podemos seguir para minimizar el riesgo de complicar nuestro código y aumentar la complejidad ciclomática de nuestros métodos y clases.

  • Principio de responsabilidad única

Nuestros métodos deberían hacer una sola cosa y hacerla bien. Parece una regla sencilla pero muchas veces no está muy claro que es una tarea y que no. Si un método mezcla responsabilidades aumenta la probabilidad de tener sentencias condicionales que separen los flujos de ejecución de cada una de esas responsabilidades y por lo tanto aumente nuestra complejidad ciclomática. Leer más sobre el Principio de Responsabilidad Única.

  • Métodos pequeños

No hay un límite de líneas para considerar un método pequeño o grande pero creo que todos cuando vemos un método podemos intuir si es pequeño o grande. Si un método tiene menos de 5 líneas, mejor que uno de 10, mucho mejor que uno de 20 y muchísimo mejor que uno de 50…y esperemos que no más :p . Es cierto que la podemos liar y complicar mucho en pocas líneas de código pero parece de sentido común que menos líneas escritas invitan a tener una menor complejidad del método.

  • Evitar retornar valores Null

Cuando retornamos valores nulos normalmente tenemos que protegernos de ellos para evitar el famoso NullPointerException. La forma de protegerse suele ser con expresiones condicionales. Si nuestros métodos retornan valores nulos seguramente tendremos muchas sentencias condicionales if después de las llamadas a esos métodos para controlar si la respuesta es un valor nulo o un valor correcto. Todos estos condicionales if, aumentan la complejidad ciclomática. Para evitar el retorno de nulos existen varias técnicas como el patrón de objetos nulos o lanzar excepciones.

  • Evitar parámetros opcionales

Aunque ya escribí sobre los parámetros opcionales, conviene recordar la importancia de evitarlos a fin de simplificar la complejidad de un método y reducir la complejidad ciclomática del mismo. Los parámetros básicamente pueden ser de dos tipos: operandos u opciones. Los operandos son aquellos parámetros necesarios con cuyos valores el método opera. Las opciones son aquellos que indican modos de ejecución, ¿qué significa esto? pues que si un método tiene varios modos de ejecución seguramente tendremos sentencias condicionales en su interior que desvíen en flujo por un sitio u otro en función del valor que tengan esos parámetros.

Los parámetros opcionales rompen el Principio de Responsabilidad Única comentado en el primer punto de esta lista. Evitando los parámetros opcionales, evitamos tener que hacer uso de la lógica If-then-else para evaluar los parámetros opcionales, reduciremos la complejidad ciclomática de nuestros métodos. ¿Y cómo lo evitamos? pues primero separando los distintos flujos que pudiera tener nuestro método en varios métodos y después que sea el cliente que llama a estos métodos el que decida a cual llamar, es decir, no tenemos un método con varios flujos de ejecución, el cliente llama y pasa un parámetro con un valor para seleccionar que se hace, sino que tenemos varios métodos cada uno con un flujo distinto y el cliente llama al que le interese.

  • Un solo nivel de indentación (sangrado) por método

También es una buena recomendación a seguir. Si nos obligamos a mantener un único nivel de indentación en un método conseguiremos evitar estructuras anidadas que hagan que nuestra complejidad ciclomática aumente. Si tenemos un bloque de código que va en un segundo, o mayor, nivel de indentación en un código deberíamos, por ejemplo, intentar extraerlo a otro método. Volvemos un poco a lo de siempre: conseguir métodos pequeños, que hagan una sola cosa…etc.

  • Evita las sentencias “Switch case

Las sentencias switch case tienen bastante argumentos en contra para su uso. Nuevamente, rompen el principio de responsabilidad única, rompen el principio abierto-cerrado, son complicadas de manejar y entender cuando el número de casos aumenta. Un switch case es en realidad un if-elseif-elseif-elseif-….

Para evitar su uso se puede resolver con polimorfismo de manera que el código de cada una de las opciones se lleva a una clase independiente. Martin Fowler habla de este tipo de refactorización. Aquí se puede ver otro ejemplo de código.

Ejemplo Análisis de la complejidad ciclomática de un caso real con PHP

En el último proyecto en el que he estado trabajando en Sngular he tenido que implementar una librería para usar el API de Amazon para PHP. Voy a pasar el código por un analizador de código estático para comprobar la complejidad ciclomática del código que he implementado. El analizador ha sido PHPLOC, que es una herramienta para realizar análisis de estático de código, implementada por Sebastian Bergmann creador también de PHPUNIT.

En la siguiente imagen se ven muchos datos, pero los más importantes los he marcado con una flecha verde. Abajo del todo se puede ver que se han analizado 77 clases. Para estas 77 clases, tenemos que la complejidad ciclomática máxima por clase tiene un valor de 6 y la complejidad ciclomática por método tiene 4. Estos valores son bastante buenos ya que entre 1 y 10 se consideran valores con baja complejidad y más sencillos de entender.

Ejemplo de complejidad ciclomática sobre un componente que desarrollé hace unas semanas

--

--