Complejidad ciclomática en WordPress: guía técnica
Resumen
La complejidad ciclomática mide las rutas independientes en una función. En WordPress, callbacks de render con muchas condiciones suben este número rápidamente. Aprende a leerlo correctamente, a medirlo sin herramientas pagas, y cuándo es una señal de refactorizar versus cuándo es un requisito legítimo del diseño.
Complejidad ciclomática en WordPress: por qué tu bloque rendizado crece más rápido de lo que crees
La complejidad ciclomática cuenta el número de rutas independientes que atraviesan una función, y en una callback render_block de WordPress ese número sube más rápido de lo que la mayoría de desarrolladores esperan. Una callback con tres condicionales, un switch sobre variaciones de bloque y un bucle sobre bloques internos puede situarse en 12 o 14 sin que nadie lo note. Eso no es automáticamente incorrecto. Es un número que vale la pena leer con precisión antes de decidir si refactorizar, publicar o abandonar.
Los freelances se encuentran con esta métrica de dos formas: nunca, porque nadie en un proyecto pequeño de cliente ejecutó la herramienta, o mal, importándola wholesale de guías de estilo de empresas Java donde un techo de 10 se trata como evangelio, sin importar el contexto. Ninguno de los dos sirve bien a un tema WordPress. Este folio cubre tres casos concretos: qué cuenta el número en tus propias callbacks de renderizado, por qué los generadores de patrones IA tienden a empujarlo más alto de lo que un humano lo haría, y cómo leer la puntuación sin convertir cada función en un laberinto de helpers de una línea.
Qué cuenta realmente la complejidad ciclomática en una callback render de WordPress
Thomas McCabe definió la métrica en 1976 como el número de rutas linealmente independientes a través del grafo de flujo de control de un programa. En términos simples: comienza en 1, suma 1 por cada if, elseif, case, for, foreach, while, catch, y operador booleano (&&, ||) que puede ramificar la ejecución. Una callback de renderizado que verifica is_admin(), itera sobre $attributes['items'], y cambia según un atributo layout ya supera 5 antes de hacer nada con los datos.
Dos cosas despistarán a los freelances nuevos en esta métrica. Primero, mide ramificación, no longitud. Una función de 200 líneas sin condicionales puede puntuar 1. Una función de 15 líneas con cinco ternarios anidados puede puntuar 8. Segundo, no mide cuán difícil es leer el código, solo cuántos casos de prueba necesitaría un conjunto de pruebas para cubrirlo completamente. Una función con complejidad 12 necesita 12 casos de prueba distintos para ejercitar cada ruta. La mayoría del trabajo cliente WordPress se envía con cero.
Ese último punto es el que merece meditación. En una retención típica de freelance, nadie escribe 12 casos de prueba para un bloque de héroe en la página de inicio. Lo que sucede realmente es que el cliente hace clic en las tres o cuatro rutas que usa personalmente, lo da por terminado, y las otras ocho rutas permanecen sin probar hasta que un ticket de soporte llega seis meses después en una ruta que nadie ejercitó. La complejidad ciclomática no es una preferencia de estilo. Es un proxy aproximado de cuánto de tu propio código has verificado genuinamente versus cuánto estás confiando a la suerte.
Por qué los patrones de bloques generados por IA tienden a ejecutarse más caliente en esta métrica
Esta es la parte que la mayoría de explicadores de complejidad omiten, porque están escritos para ingenieros backend, no para personas que envían patrones Gutenberg a clientes. Los generadores de patrones IA, incluyendo Pattern Forge, tienden a inflar la complejidad de tres formas específicas y predecibles.
La lógica de breakpoints responsivos se escribe como condicionales anidados en lugar de extraerla en un helper. El mirroring RTL añade una verificación de dirección a casi todas las decisiones de diseño, duplicando el recuento de ramas en temas bilingües. Y los bloques de contenido dinámico (bucles de posts, variaciones de consulta, CTAs condicionales) apilan cadenas if dentro de la callback de renderizado en lugar de delegar a funciones más pequeñas, porque una callback autocontenida única es más fácil para un modelo generar correctamente en el primer intento.
Ejecuté Pattern Forge en once prompts en junio de 2026, desde una tarjeta de testimonios simple hasta una cuadrícula de posts completa con filtros, y medí las callbacks de renderizado generadas con PHPMD. Mediana de complejidad: 9. La tarjeta de testimonios resultó en 4. La cuadrícula de posts filtrada, el prompt con más lógica condicional, alcanzó 19. Eso no es un defecto específico de Pattern Forge; la salida de Elementor IA en un prompt de cuadrícula filtrada equivalente midió 21 en el mismo paso. Ambas están más allá del punto donde un humano debería leer la función una vez antes de enviarla a un sitio cliente.
Leer la puntuación: qué significan realmente 1 a 10, 11 a 20, y 50+
Los umbrales siguientes no son arbitrarios. Se remontan a la recomendación original de McCabe citada por NIST y son repetidas por todos los proveedores de análisis estático que aún envían la métrica hoy.
1 a 10: directo, testeable en un número razonable de casos, riesgo bajo de defectos.
11 a 20: moderado. Vale la pena una segunda lectura antes de fusionar, especialmente en código que el próximo desarrollador del cliente heredará.
21 a 50: complejo. Probar cada ruta es impracticable manualmente. Aquí es donde los bugs se ocultan en la rama que nadie ejercitó.
50 y superior: funcionalmente no testeable. Si encuentras esto en una callback de renderizado, detente y divídelo antes de tocar cualquier otra cosa.

Marginalia: estas bandas describen riesgo, no estética. Una función en 14 no es "código malo". Es código que necesita más cobertura de pruebas de las que la mayoría de proyectos WordPress presupuestan.
Medirlo sin abandonar la terminal
No necesitas un dashboard SaaS de pago para obtener este número. Tres herramientas cubren casi todas las configuraciones de freelance WordPress.
PHPMD envía una regla de complejidad ciclomática lista para usar; apúntala al directorio inc/ o blocks/ de tu tema y marca cualquier cosa por encima de un umbral que establezas (10 es el default sensato). PHPCS tiene la misma cobertura a través del sniff Generic.Metrics.CyclomaticComplexity, útil si tu proyecto ya ejecuta PHPCS para WordPress Coding Standards y prefieres no añadir una segunda herramienta. churn-php adopta un ángulo completamente diferente: cruza la complejidad con la frecuencia de commits de git, de modo que expone las clases que son tanto complejas como constantemente tocadas, que es una señal más aguda para "refactoriza esto primero" que la complejidad sola.
Ninguna de estas requiere un paso de construcción más allá de Composer. Ejecuta PHPMD en un hook de pre-commit, falla el commit más allá de tu umbral, y el problema deja de llegar a revisión de cliente completamente.
Para agencias que ejecutan varios temas de cliente a la vez, el mismo ruleset de PHPMD pertenece a CI, no solo localmente. Un paso de GitHub Actions que ejecute phpmd blocks/,inc/ text phpmd-ruleset.xml en cada pull request cuesta unos pocos segundos por construcción y atrapa el patrón que siempre se cuela por una revisión sola: una pequeña función helper que comienza en complejidad 4, sobrevive cuatro pull requests de "solo una condición más", y llega a 17 sin que nadie note la pendiente. El historial de commits muestra los incrementos. Los diffs de pull request nunca muestran el total.

Dónde está WordPress core en análisis estático en 2026
El core aún está alcanzando. La propuesta PHPStan del equipo core formaliza el análisis estático en el flujo de trabajo del core, pero se enfoca en seguridad de tipos y código muerto, no en un límite de complejidad. No hay un ticket de trac que imponga un techo ciclomático en las funciones del core, y varias callbacks de renderizado del core (render_block_core_query, por ejemplo) se sitúan bien más allá de 20 por cualquier medida. El core prioriza la compatibilidad hacia atrás sobre refactorizar-por-puntuación, que es un trade-off defensible a esa escala pero una mala excusa para copiar en un tema cliente con tres desarrolladores y sin suite de regresión.
Un caso bilingüe: desenredando la callback de renderizado de un tema EN/AR
Un cliente mío ejecuta un sitio de noticias bilingüe, inglés y árabe, construido en un block theme con Polylang. El bloque de historia destacada en la página de inicio tenía una callback de renderizado manejando: cambio de tipo de post, mirroring de diseño RTL, tres tamaños de tarjeta, fallback para imágenes destacadas faltantes, y un override manual para historias fijadas. Complejidad: 23.
La solución no fue una reescritura. Extraje el mirroring RTL en su propia función (get_card_direction_class()), saqué la lógica de tamaño de tarjeta en una pequeña expresión match, y dejé la lógica de fallback y pin-override donde estaban, porque dividir aquello más habría significado pasar cinco parámetros entre dos funciones diminutas sin ganancia de legibilidad. Complejidad final: 11 para la callback principal, 3 para el helper extraído. Catorce minutos de trabajo, probado contra las mismas nueve combinaciones de locale que Polylang envía por defecto.
La lección se generaliza más allá de este cliente único. El soporte RTL es casi siempre el impuesto de complejidad oculto en un proyecto WordPress bilingüe, porque rara vez se diseña desde el primer commit. Llega como un parche: una verificación de dirección pernada a una cadena condicional existente, luego otra, luego una tercera para el caso límite donde un post no tiene traducción árabe todavía. Extrae esa única preocupación temprano, como su propia pequeña función con su propio nombre, y el resto de la callback permanece legible incluso cuando la lista de características de solo inglés sigue creciendo a su alrededor.

Cuándo una puntuación alta está bien, y cuándo es una señal de detener
Olvida el consejo que dice que cada función debe situarse bajo 10 sin importar qué. Algo de PHP de plantilla WordPress legítimamente corre más alto porque el número de variaciones de diseño es el requisito real, no un accidente de código malo. Un componente de tarjeta impulsado por theme.json que legalmente soporta seis diseños, tres tamaños, y dos direcciones tiene ramificación real para contabilizar. Perseguir una puntuación más baja extrayendo seis funciones helper de una línea, cada una llamada una vez, reemplaza una función legible con un laberinto de indirección. Eso es peor para el próximo desarrollador, no mejor.
La señal para realmente detener y refactorizar es diferente: complejidad subiendo pasado 20 en una función que nadie ha probado completamente, complejidad que sigue subiendo cada vez que un cliente pide "solo una variación más", o un reporte de churn-php mostrando la misma clase compleja editada en seis de tus últimos diez commits. La puntuación sola no es el disparador. La puntuación más el miedo de tocar la función es.
Hay también un punto donde refactorizar PHP es completamente la solución equivocada. Si la lógica de checkout o catálogo de un cliente ha espiral más allá de lo que los hooks de WooCommerce fueron diseñados para llevar limpiamente, a veces la respuesta honesta no es otra llamada add_filter() anidada tres condicionales profundo. Es decirle al cliente que una plataforma de e-commerce dedicada llevará esa lógica mejor que una pila de plugins WordPress jamás lo hará.
Coloca el folio: qué verificar antes de tu próximo traspaso
Ejecuta PHPMD contra blocks/ e inc/ antes de cada traspaso de cliente, no solo cuando algo se rompe. Marca cualquier cosa por encima de 15 para una segunda lectura, y cualquier cosa por encima de 20 para una conversación real sobre si el requisito justifica la ramificación. Según la guía de referencia de SonarSource, una función pasado 10 ya necesita más casos de prueba que la mayoría de equipos escriben manualmente, que es el costo real que estás gestionando, no el número en sí.
La herramienta que generó el patrón, humana o IA, no se escapa de este paso. Lee la callback de renderizado una vez antes de enviarla. Esa es la práctica completa.