Métricas de código explicadas: ¿Qué es la complejidad ciclomática?
Estás revisando un pull request y una función ha crecido para manejar ocho condiciones diferentes: roles de usuario, feature flags, casos límite, fallbacks. Pasa las pruebas. Funciona. Pero algo no encaja. Esa sensación tiene un nombre y un número.
La complejidad ciclomática es una métrica de calidad de código que mide cuántas rutas de ejecución independientes existen a través de una función. Cuanto mayor sea el número, más lógica de ramificación contiene tu código, y más difícil resulta leerlo, probarlo y mantenerlo.
Puntos clave
- La complejidad ciclomática cuenta el número de rutas independientes a través de una función, ofreciendo una señal medible de la lógica de ramificación.
- Calcúlala rápidamente empezando en 1 y sumando 1 por cada sentencia de ramificación como
if,&&,||,caseo un ternario. - Se diferencia de la complejidad cognitiva, que mide qué tan difícil es de leer el código para un humano, en lugar de cuántas rutas contiene.
- Herramientas como ESLint y SonarQube pueden rastrear la complejidad automáticamente y señalar funciones que superen un umbral configurable.
- Reduce la complejidad con guard clauses, helpers extraídos, variables booleanas descriptivas y lookup tables.
Cómo se calcula la complejidad ciclomática
Desarrollada por Thomas McCabe en 1976, la métrica se deriva del grafo de flujo de control de una función. La fórmula práctica para un único componente conectado es:
M = E − N + 2P
Donde E es el número de aristas, N es el número de nodos, P es el número de componentes conectados (normalmente 1 para una sola función) y M es la puntuación de complejidad.
Para la mayoría de los desarrolladores de JavaScript, no es necesario calcularlo manualmente. El atajo: empieza en 1 y luego suma 1 por cada sentencia de ramificación: if, else if, &&, ||, for, while, case, operadores ternarios y cláusulas catch. Algunas herramientas también cuentan construcciones como el encadenamiento opcional, los valores por defecto y la asignación lógica. Las reglas exactas de cálculo pueden variar ligeramente entre herramientas como ESLint y SonarQube.
Un ejemplo en JavaScript: cómo la ramificación incrementa la complejidad
// Cyclomatic complexity: 1
function getDisplayName(user: User): string {
return user.name;
}
// Cyclomatic complexity: 6
function getDisplayName(user: User | null): string {
if (!user) return "Guest"; // +1
if (user.isAdmin) return "Admin"; // +1
if (user.displayName) return user.displayName; // +1
if (user.firstName && user.lastName) // +1 (if) +1 (&&)
return `${user.firstName} ${user.lastName}`;
return user.email;
}
Cada condición añade una rama. Más ramas significan más rutas que probar, y más maneras en que un cambio futuro podría romper algo de forma inesperada.
Este patrón aparece constantemente en el código de frontend: la lógica de renderizado de componentes React, los reducers de Redux con muchos tipos de acción, los handlers de validación de formularios y los flujos de UI basados en permisos.
Complejidad ciclomática vs. complejidad cognitiva
Son métricas relacionadas pero distintas. La complejidad ciclomática cuenta ramas estructurales: es una señal de testabilidad. La complejidad cognitiva (popularizada por SonarQube) mide qué tan difícil es de leer el código para un humano, penalizando con más fuerza el anidamiento y el flujo no lineal.
Una función puede obtener una puntuación baja en complejidad ciclomática y aun así ser difícil de seguir, por ejemplo, llamadas a métodos profundamente encadenadas sin variables intermedias. Ambas métricas son útiles, y ninguna cuenta la historia completa por sí sola.
Discover how at OpenReplay.com.
Cómo medirla en tu codebase de JavaScript
Dos herramientas prácticas para equipos de frontend:
- Regla
complexityde ESLint: señala las funciones que superan un umbral configurable directamente en tu editor. - SonarQube / SonarCloud: reporta tanto la complejidad ciclomática como la cognitiva en todo tu codebase.
Configura ESLint así:
{
"rules": {
"complexity": ["warn", { "max": 10 }]
}
}
El umbral es configurable, y debería serlo. Una utilidad de validación y un reducer de Redux no necesitan el mismo techo. Ajusta los umbrales al contexto de tu código, no a una regla universal.
Formas prácticas de reducir la complejidad innecesaria
Cuando la puntuación de una función aumenta, estas técnicas ayudan:
- Extraer funciones: separa la lógica diferenciada en helpers con nombre.
- Usar guard clauses: retorna de forma temprana en lugar de anidar condiciones.
- Simplificar condicionales: reemplaza cadenas booleanas complejas con variables descriptivas.
- Usar lookup tables: reemplaza sentencias
switchlargas con objetos oMap.
El objetivo no es obtener una puntuación baja por sí misma. Es tener código más fácil de probar, más fácil de modificar y más fácil de entender para el siguiente desarrollador.
Conclusión
La complejidad ciclomática te ofrece una señal concreta y medible sobre la lógica de ramificación en tu código. Usa ESLint o SonarQube para rastrearla, establece umbrales que se ajusten a tu codebase y trata las puntuaciones crecientes como un aviso para refactorizar, no como una crisis. Combínala con la complejidad cognitiva para obtener una visión más completa de la mantenibilidad.
Preguntas frecuentes
Una guía habitual es mantener las funciones en 10 o menos. Las puntuaciones de 1 a 10 se consideran manejables, de 11 a 20 sugieren que la función se está volviendo compleja, y cualquier valor por encima de 20 suele ser un candidato fuerte para refactorización. El umbral adecuado depende del tipo de código, así que ajústalo al contexto de tu equipo.
La complejidad ciclomática cuenta cada sentencia de ramificación por igual, sin importar qué tan profundamente esté anidada. Una función con tres sentencias if planas y otra con tres sentencias if anidadas pueden tener la misma puntuación. Esta es una de las razones por las que existe la complejidad cognitiva, ya que añade peso adicional al anidamiento y refleja mejor qué tan difícil es de leer el código.
No siempre. Una puntuación alta indica que una función merece una revisión más cuidadosa, pero algunas lógicas son genuinamente densas en ramificaciones, como parsers, máquinas de estado o pipelines de validación. Usa la métrica como un aviso para revisar, en lugar de una regla estricta. Si la función está bien probada, escrita con claridad y es estable, refactorizar puede añadir riesgo sin un beneficio real.
Las líneas de código miden el tamaño, mientras que la complejidad ciclomática mide los puntos de decisión. Una función de 200 líneas sin ramificaciones tiene una complejidad de 1, mientras que una función de 20 líneas llena de condiciones puede obtener una puntuación mucho mayor. La complejidad es un mejor indicador de la testabilidad y el esfuerzo de mantenimiento porque refleja cuántas rutas necesitan cubrir tus pruebas.
Gain control over your UX
See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.