Métricas de calidad de código

La métrica CRAP: hallar complejidad sin probar

CRAP significa Change Risk Anti-Patterns. La métrica multiplica lo enredado de un método por lo poco probado que está, y responde a una pregunta que ninguna métrica suelta puede responder: ¿qué código es peligroso de cambiar? Esa pregunta se volvió mucho más urgente desde que los agentes escriben el código.

Un instrumento deliberadamente tosco

Alberto Savoia y Bob Evans presentaron CRAP en 2007 junto con Crap4j, una herramienta Java que puntuaba cada método de una compilación. La premisa era que ni la complejidad ni la cobertura significan mucho por separado. Un método enrevesado con pruebas sólidas es manejable. Un método trivial sin pruebas no molesta. Lo que de verdad duele es la complejidad que nadie ha cubierto, porque ahí una edición pequeña produce una sorpresa que nadie atrapa. CRAP pone ambos términos en una sola expresión para que la combinación peligrosa puntúe mal y las inofensivas no.

El nombre hace un trabajo deliberado. De una métrica llamada Change Risk Anti-Patterns se habla una vez; una métrica que te dice que un método es una porquería se arregla.

Para qué sirve en realidad

CRAP no es un adorno de panel. Cada puntuación se resuelve en una de cuatro decisiones, y esa es toda la razón para calcularla.

Dónde va la siguiente prueba

Ordenado de mayor a menor, el informe es una cola de trabajo para el esfuerzo de pruebas. Lo alto de la lista es donde una prueba compra más reducción de riesgo por hora invertida, que es una respuesta mucho mejor que perseguir un porcentaje global de cobertura.

Qué refactorizar antes de tocarlo

Una puntuación alta empujada por la complejidad avisa de que el método te va a pelear. Partirlo antes de añadir comportamiento suele ser más barato que añadir el comportamiento y luego intentar probar el resultado.

Qué cambio necesita una lectura humana cuidadosa

Un diff que sube el CRAP de los métodos que toca merece revisión línea por línea. Un diff que lo baja se puede leer en diagonal. Esa decisión de encaminamiento es donde la métrica se paga en tiempo de revisión.

Qué probar en regresión antes de un release

Riesgo de cambio más frecuencia reciente de modificación te dice qué módulos se ganaron una pasada de regresión este release y de cuáles puedes fiarte porque nada se movió dentro.

La fórmula y por qué los exponentes son desiguales

La complejidad va al cuadrado. La fracción no cubierta va al cubo. Esa asimetría es todo el diseño.

CRAP(m) = CC² × (1 − cov)³ + CC
  • • CC es la complejidad ciclomática del método: el número de caminos independientes que lo atraviesan
  • • cov es la cobertura del método como fracción de 0 a 1, así que (1 − cov) es la parte sin probar
  • • Elevar al cubo la parte sin probar hace que el primer término se derrumbe rápido al subir la cobertura, de modo que probar un método feo paga de inmediato
  • • Con cobertura completa el primer término es cero y CRAP equivale a CC, el riesgo residual que las pruebas no pueden quitar
  • • El + CC final impide que la métrica finja alguna vez que la complejidad es gratis

Lo que los números exigen de verdad

Con el umbral convencional de 30, esta es la cobertura que necesita cada nivel de complejidad para bajar de la línea.

CC 0% cov 50% cov 100% cov To clear 30
5 30.0 8.1 5 Ninguna. El código simple pasa sin pruebas.
10 110.0 22.5 10 Alrededor del 42 %
15 240.0 43.1 15 Alrededor del 60 %
20 410.0 70.0 20 Alrededor del 71 %
25 650.0 103.1 25 Exactamente el 80 %
30 930.0 142.5 30 100 %, y aterriza justo en la línea
31+ 961.0 155.2 31 Inalcanzable. Las pruebas no arreglan este caso.

Qué te está diciendo la curva

Pasada la complejidad 30, la cobertura deja de ayudar

Con un umbral de 30, un método de complejidad 31 queda por encima de la línea incluso al 100 % de cobertura. No es un defecto de la fórmula, es su mensaje: el único movimiento que queda es partir el método.

Con cobertura completa, CRAP es solo complejidad

Las pruebas nunca perdonan la complejidad, solo dejan de amplificarla. Un método complejo bien cubierto sigue cargando su complejidad como riesgo reconocido y gestionado en lugar de riesgo oculto.

El código simple se deja en paz a propósito

Un método de complejidad 5 está exactamente en 30 sin ninguna prueba. La métrica dirige la atención al código enredado en vez de generar trabajo inútil sobre getters y mappers.

La cobertura es la pata débil

cov mide ejecución, no aserción. Una suite sin aserciones sube la cobertura y baja el CRAP sin cambiar nada del riesgo real. Ese hueco concreto es el que existen para cerrar las pruebas de mutación, y es el hueco en el que un LLM cae de lleno.

Cómo lo usa un equipo de QA

Esta es la parte que suelen omitir los textos sobre métricas. CRAP es un instrumento de puntería, y QA es quien lo apunta.

Apuntar la suite de regresión en lugar de engordarla

  • - Ordena los métodos por CRAP multiplicado por frecuencia de commits y construye la pasada de regresión del release desde arriba de esa lista
  • - Los módulos con CRAP bajo y sin cambios no necesitan una pasada nueva cada release, y de ahí sale el tiempo para lo alto de la lista
  • - Reordena por release en lugar de mantener una suite estática que crece para siempre y nunca se poda

Decidir entre automatización y exploración

  • - Complejidad alta con cobertura baja es un hueco de pruebas unitarias, no de pruebas exploratorias: pedir a QA manual que cubra 20 ramas a mano desperdicia a un buen tester
  • - Complejidad alta con cobertura alta es donde la exploración rinde, porque las rutas se ejercitan pero los requisitos aún pueden estar mal
  • - El comportamiento indefinido se esconde en ramas sin cubrir de métodos complejos, así que ahí es donde se diseñan primero los casos negativos y de frontera

Hacer que las barreras de release se discutan en números, no en opiniones

  • - Una barrera de ningún método cambiado por encima del umbral es aplicable, revisable y difícil de discutir al final de un sprint
  • - Muestra las dos entradas junto a la puntuación para que el arreglo no sea ambiguo: partirlo o probarlo
  • - Registra los defectos escapados frente al CRAP del método del que venían: esa correlación es cómo ajustas tu propio umbral en lugar de heredar el nuestro

Usarlo como lenguaje común con desarrollo

  • - Una objeción de calidad planteada como este método está desordenado pierde contra las fechas; planteada como este método tiene complejidad 24 con 30 % de cobertura, normalmente no
  • - Le da a QA un motivo legítimo y pactado de antemano para pedir un refactor antes de que aterrice una funcionalidad
  • - También protege a los desarrolladores del trabajo inútil, porque la métrica dice explícitamente que el código trivial no necesita pruebas

Usar CRAP para juzgar código que escribió un LLM

Los agentes cambiaron la economía de esta métrica. Producen código plausible más rápido de lo que nadie puede leerlo, y escribirán con gusto las pruebas que los miden. Complejidad frente a cobertura es una de las señales honestas más baratas que te quedan.

El peligro concreto es que un LLM optimiza el objetivo visible. Pides pruebas y obtienes pruebas; pides cobertura y obtienes cobertura. Un agente puede mover las dos mitades de CRAP, pero solo una de forma honesta. La complejidad es una propiedad estructural del código que escribió: no se puede negociar. La cobertura es un número que el agente puede inflar ejecutando líneas sin afirmar nada sobre ellas. Así que la pareja es diagnóstica: la complejidad dice qué construyó el agente, la cobertura dice qué afirma sobre ello, y el hueco entre ambas es donde hay que mirar.

Puntuar el diff, no el repositorio

  • - Calcula el CRAP de los métodos que tocó el agente, antes y después, y publica el delta en el pull request
  • - Complejidad al alza con cobertura plana es la firma del comportamiento atornillado a una función existente, que es la forma por defecto en que un agente añade una funcionalidad
  • - Complejidad a la baja con cobertura al alza es el aspecto de una buena ejecución de agente, y merece premiarse revisándola más rápido

Poner un techo de complejidad en las instrucciones del agente

  • - El enjambre de Uncle Bob hace exactamente esto: el rol cleaner ejecuta primero la herramienta CRAP y baja la complejidad a 6 o menos antes de cualquier otra cosa
  • - Es un uso más inteligente de la métrica que poner barrera a la puntuación, porque con complejidad 6 el número apenas puede subir haga lo que haga la cobertura
  • - Dale al agente el umbral y el comando, no un párrafo sobre código limpio, y cumplirá porque la comprobación tiene código de salida

Devolver la salida de la herramienta al bucle

  • - Un informe CRAP en un panel no cambia nada; el mismo informe inyectado en el siguiente turno del agente como comprobación fallida se arregla
  • - Ejecútalo en un paso separado del que escribió el código, para que el agente no califique su propia tarea
  • - Limita los reintentos: un agente que no baja del umbral en dos intentos te está diciendo que el diseño está mal, no que necesita otra pasada

Nunca dejar que un agente suba la cobertura y reporte la métrica

  • - La cobertura es la mitad manipulable, y un agente al que le pides mejorar su propia puntuación buscará el camino más barato al número
  • - Empareja cada barrera de CRAP con una ejecución de mutación, que pregunta si esas pruebas nuevas afirman algo
  • - Trata la explicación del agente sobre una puntuación como texto publicitario; la evidencia es la salida de la herramienta

Leer la señal en código escrito por agentes

Los mismos números significan cosas concretas y reconocibles cuando el autor es un modelo.

Señal Qué suele significar Qué hacer
Subió la complejidad, la cobertura plana El comportamiento nuevo se añadió como ramas extra dentro de una función existente. Pide extracción antes de revisar. Es la forma más común de crecimiento del código de agente.
Saltó la cobertura, complejidad igual Se añadieron pruebas. Que afirmen algo aún no está establecido. Ejecuta pruebas de mutación sobre los archivos cambiados antes de creerte el número.
Un método muy por encima del umbral El modelo siguió añadiendo casos al primer sitio que encontró, prompt tras prompt. Parte por regla y vuelve a medir. La puntuación suele desplomarse sin una sola prueba nueva.
Complejidad en código que nadie pidió Ramas especulativas, rutas defensivas y opciones sin ningún requisito detrás. Bórralo. La complejidad sin probar que ningún requisito nombra es lo más barato de quitar.
Buena puntuación con cobertura de pruebas snapshot La suite ejecuta todo y comprueba casi nada. La métrica te está mintiendo a través de su entrada de cobertura. Arregla las pruebas, no la puntuación.

Trabajar con ella

La fórmula son dos líneas de código, y eso explica en buena parte por qué se reimplementa una y otra vez en ecosistemas nuevos.

La métrica en sí

js

Cualquier informe de cobertura más cualquier herramienta de complejidad da todo lo que la fórmula necesita.

// crap.js — coverage is a fraction, 0..1
export function crap(complexity, coverage) {
	const untested = 1 - coverage;
	return complexity ** 2 * untested ** 3 + complexity;
}

crap(15, 0);    // 240
crap(15, 0.5);  // 43.125
crap(15, 1);    // 15

Poner barrera al cambio, no a la base de código

sh

Un trinquete sobre los métodos cambiados mantiene fuera el riesgo nuevo sin abrir un proyecto de limpieza que nadie financió. Las puntuaciones heredadas siguen visibles en un panel en lugar de bloquear cada compilación. Para ramas escritas por agentes, esa es toda la barrera.

# CI: score only the methods this branch touched
git diff --name-only origin/main... -- '*.js' \
  | xargs node ./tools/crap-report.mjs --threshold 30 --changed-only

# exit non-zero when a touched method crosses the line;
# print the untouched offenders as a report, not a failure

El refactor que la puntuación está pidiendo

js

Cuando la puntuación viene de la complejidad y no de la cobertura, probar más fuerte es la respuesta equivocada. Saca cada rama a algo de complejidad 1 y el conductor se queda plano por muchas reglas que lleguen, incluidas las que añada un agente futuro.

// Before: complexity climbs with every rule anyone adds.
function validate(order) {
	if (!order.id) return 'missing id';
	if (order.items.length === 0) return 'no items';
	if (order.total < 0) return 'negative total';
	if (order.currency !== 'USD' && order.currency !== 'EUR') return 'bad currency';
	if (order.customer && !order.customer.email) return 'customer without email';
	return null;
}

// After: each rule is trivially testable, the driver stays at 2.
const RULES = [
	[(o) => !o.id, 'missing id'],
	[(o) => o.items.length === 0, 'no items'],
	[(o) => o.total < 0, 'negative total'],
	[(o) => !['USD', 'EUR'].includes(o.currency), 'bad currency'],
	[(o) => Boolean(o.customer) && !o.customer.email, 'customer without email']
];

function validate(order) {
	for (const [fails, message] of RULES) if (fails(order)) return message;
	return null;
}

La barrera que un agente no puede sortear hablando

sh

Dos comprobaciones, en este orden, ejecutadas por un paso que no escribió el código. La primera dice que el código tiene forma de algo que puedes cambiar; la segunda dice que las pruebas que lo protegen reaccionan de verdad cuando cambia.

# 1. structural: is this changeable code?
crap-report --changed-only --threshold 30 || exit 1

# 2. behavioural: do the new tests assert anything?
stryker run --incremental --mutate "$(git diff --name-only origin/main...)"

# report both on the PR. one number is about the code,
# the other is about the tests that claim to cover it.

Cómo usarla sin irritar a todo el mundo

Leer las dos palancas por separado

  • - Una puntuación alta empujada por la complejidad es tarea de refactor, no de pruebas
  • - Una puntuación alta empujada por la falta de cobertura es tarea de pruebas, y barata
  • - Muestra siempre CC y cobertura junto a la puntuación, porque el número solo no dice cuál de las dos es

Ponderar por frecuencia de cambio

  • - El riesgo de cambio solo importa donde ocurre el cambio
  • - Una puntuación de 200 en un archivo intacto desde hace cuatro años es menos urgente que un 60 en uno editado cada semana
  • - Ordena por puntuación multiplicada por frecuencia de commits y trabaja desde arriba de esa lista

Hacer explícitas las exclusiones

  • - El código generado, los adaptadores y los switch exhaustivos inflan la complejidad sin inflar el riesgo real
  • - Exclúyelos en configuración, con un comentario que diga por qué, en lugar de subir el umbral en silencio
  • - Revisa la lista de exclusiones cuando el código que protege deje de estar generado

Malas lecturas habituales

Tratarla como nota de calidad

CRAP estima el riesgo de cambiar código. No dice nada de si el código es correcto, está bien nombrado o bien diseñado. Un método limpio y bien cubierto con complejidad de dominio genuina puntúa igual que uno desagradable.

Trucarla con cobertura

Las pruebas que capturan todo en snapshot y los recorridos sin aserciones mueven el número, no el riesgo. Si CRAP es una barrera, algo tiene que mantener honestas las pruebas: la revisión, las pruebas de mutación, o ambas. Con un agente en el bucle, asume que ocurrirá salvo que una comprobación separada lo impida.

Lanzar una épica de limpieza

Un informe CRAP de todo el repositorio sobre código heredado produce un número tan grande que se ignora. Pon el trinquete sobre el código cambiado y deja que las partes peligrosas las arreglen quienes, personas o agentes, ya iban a tocarlas.

Esperar una herramienta mantenida

El Crap4j original lleva años dormido. NDepend lleva la métrica en .NET, hay implementaciones comunitarias para Rust, .NET y Groovy, y Uncle Bob mantiene crap4j, crap4go y crap4clj para su enjambre de agentes. En la mayoría de los stacks la calculas tú mismo con datos que ya recoges.

Un número, una pregunta

CRAP no es una nota de calidad ni pretendió serlo. Responde a una pregunta más estrecha y más útil: si alguien edita este método la semana que viene, ¿qué probabilidad hay de que algo se rompa en silencio? La complejidad dice de cuántas formas se puede equivocar uno, la cobertura dice cuántas de ellas está vigilando alguien, y la fórmula pesa más lo segundo que lo primero.

Eso la convierte en herramienta de puntería para QA y en herramienta de auditoría para el trabajo asistido por IA. Apúntala al diff, pon barrera al código nuevo y cambiado en un umbral, ordena el resto por puntuación frente a frecuencia de cambio y mantén visibles las dos entradas para que el número se traduzca en una acción: partir este método o probarlo. Solo recuerda qué mitad puede falsear un autor. La complejidad es lo que el código es; la cobertura es solo lo que las pruebas afirman, y las pruebas de mutación son la forma de comprobar esa afirmación.

Preguntas frecuentes

¿Qué se considera una mala puntuación CRAP?

Treinta es el umbral por defecto convencional, heredado de Crap4j y reutilizado por casi todas las implementaciones posteriores. Bájalo en código nuevo, donde mantener la línea cuesta poco, y consérvalo en código heredado mientras aprietas el trinquete en lugar de subirlo para que un informe se vea mejor.

¿Sirve CRAP para revisar código generado por IA?

Es una de las señales baratas más útiles, porque la complejidad es un hecho estructural del código que produjo el modelo y no se puede discutir. Puntúa el diff en lugar del repositorio, vigila la complejidad subiendo mientras la cobertura se queda plana, y emparéjalo siempre con una ejecución de mutación para que la mitad de cobertura no se infle con pruebas que no afirman nada.

¿Qué umbral poner para ramas escritas por agentes?

Más estricto que para personas, y expresado como complejidad en lugar de puntuación. El patrón práctico, y el que usa el enjambre de Uncle Bob, es un techo duro de complejidad en torno a 6 sobre los métodos cambiados: a ese nivel la puntuación CRAP no puede subir mucho pase lo que pase con la cobertura, así que no queda nada que negociar.

¿Puede un agente arreglar su propia puntuación CRAP?

Normalmente sí en la mitad de complejidad, que es mejora genuina y merece automatizarse. En la mitad de cobertura, cuidado: la forma más barata de subir cobertura es ejecutar código sin comprobarlo. Ejecuta la barrera en un paso que no escribió el código y verifica las pruebas nuevas con pruebas de mutación.

¿Por qué la complejidad al cuadrado y la parte sin probar al cubo?

Para que la cobertura mueva la puntuación más rápido que la complejidad. El término al cubo se desploma hacia cero cuando la cobertura se acerca al total, lo que premia de inmediato probar código complejo, mientras el término al cuadrado más la complejidad final dejan un suelo que ninguna prueba puede quitar.

¿Debería CRAP romper la compilación?

Como trinquete sobre métodos nuevos y cambiados, sí, porque es aplicable y nadie tiene que planificarlo. Como barrera de todo el repositorio sobre una base existente, no: eso produce un backlog en lugar de un cambio de comportamiento.

© 2026 Ryware Solutions Ltd. · N.º de empresa 516681764