Die CRAP-Metrik: Komplexität und Coverage in einer Zahl
CRAP steht für Change Risk Anti-Patterns. Die Metrik multipliziert, wie verworren eine Methode ist, mit dem, wie untestet sie ist, und das Ergebnis beantwortet eine Frage, die keine einzelne Metrik beantworten kann: welcher Code ist gefährlich zu ändern?
Ein bewusst grobes Instrument
Alberto Savoia und Bob Evans führten CRAP 2007 gemeinsam mit Crap4j ein, einem Java-Werkzeug, das jede Methode eines Builds bewertete. Die Grundannahme: weder Komplexität noch Coverage bedeuten allein viel. Eine sperrige Methode mit gründlichen Tests ist handhabbar. Eine triviale Methode ohne Tests ist unproblematisch. Wirklich weh tut Komplexität, die niemand abgedeckt hat, denn dort erzeugt eine kleine Änderung eine Überraschung, die niemand bemerkt. CRAP steckt beide Größen in einen Ausdruck, sodass die gefährliche Kombination schlecht abschneidet und die harmlosen nicht.
Der Name leistet bewusst Arbeit. Über eine Metrik namens Change Risk Anti-Patterns wird einmal gesprochen; eine Metrik, die einer Methode sagt, dass sie Mist ist, wird behoben.
Die Formel und warum die Exponenten ungleich sind
Komplexität wird quadriert. Der unabgedeckte Anteil wird kubiert. Diese Asymmetrie ist der ganze Entwurf.
- • CC ist die zyklomatische Komplexität der Methode, also die Zahl unabhängiger Pfade durch sie
- • cov ist die Coverage der Methode als Anteil von 0 bis 1, sodass (1 − cov) der ungetestete Teil ist
- • Das Kubieren des ungetesteten Anteils lässt den ersten Term mit steigender Coverage schnell zusammenfallen, weshalb sich das Testen einer hässlichen Methode sofort auszahlt
- • Bei voller Coverage ist der erste Term null und CRAP gleicht CC, dem Restrisiko, das Tests nicht beseitigen können
- • Das angehängte + CC verhindert, dass die Metrik jemals vorgibt, Komplexität sei kostenlos
Was die Zahlen tatsächlich fordern
Mit dem üblichen Schwellwert von 30 zeigt die Tabelle, wie viel Coverage jede Komplexitätsstufe braucht, um unter die Linie zu kommen.
| CC | 0% cov | 50% cov | 100% cov | To clear 30 |
|---|---|---|---|---|
| 5 | 30.0 | 8.1 | 5 | Keine. Einfacher Code besteht ungetestet. |
| 10 | 110.0 | 22.5 | 10 | Etwa 42 % |
| 15 | 240.0 | 43.1 | 15 | Etwa 60 % |
| 20 | 410.0 | 70.0 | 20 | Etwa 71 % |
| 25 | 650.0 | 103.1 | 25 | Genau 80 % |
| 30 | 930.0 | 142.5 | 30 | 100 %, und landet genau auf der Linie |
| 31+ | 961.0 | 155.2 | 31 | Unerreichbar. Tests retten diese Methode nicht. |
Was die Kurve Ihnen sagt
Ab Komplexität 30 hilft Coverage nicht mehr
Bei einem Schwellwert von 30 liegt eine Methode mit Komplexität 31 selbst bei 100 % Coverage darüber. Das ist kein Fehler der Formel, sondern ihre Botschaft: der einzige verbleibende Schritt ist, die Methode aufzuteilen.
Bei voller Coverage ist CRAP nur Komplexität
Tests verzeihen Komplexität nie, sie verhindern nur ihre Verstärkung. Eine gut abgedeckte komplexe Methode trägt ihre Komplexität als eingestandenes, gesteuertes Risiko statt als verborgenes.
Einfacher Code wird absichtlich verschont
Eine Methode mit Komplexität 5 liegt ohne jeden Test genau bei 30. Die Metrik lenkt die Aufmerksamkeit auf verworrenen Code statt Beschäftigung an Gettern und Mappern zu erzeugen.
Coverage ist das schwache Bein
cov misst Ausführung, nicht Prüfung. Eine Suite ohne Assertions hebt die Coverage und senkt CRAP, ohne am echten Risiko etwas zu ändern. Genau diese Lücke schließen Mutationstests.
Damit arbeiten
Die Formel sind zwei Zeilen Code, und das ist der Hauptgrund, warum sie in neuen Ökosystemen immer wieder auftaucht.
Die Metrik selbst
jsJeder Coverage-Report plus ein beliebiges Komplexitätswerkzeug liefert alles, was die Formel braucht.
// 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 Die Änderung gaten, nicht die Codebasis
shEine Ratsche auf geänderte Methoden hält neues Risiko draußen, ohne ein Aufräumprojekt zu eröffnen, das niemand finanziert hat. Altlasten bleiben auf einem Dashboard sichtbar, statt jeden Build zu blockieren.
# 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 Das Refactoring, um das die Zahl bittet
jsWenn der Wert aus Komplexität statt aus fehlender Coverage kommt, ist härteres Testen die falsche Antwort. Ziehen Sie jede Verzweigung in etwas mit Komplexität 1 heraus, dann bleibt der Treiber flach, egal wie viele Regeln noch kommen.
// 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;
} Wie man es einsetzt, ohne alle zu verärgern
Die zwei Hebel getrennt lesen
- - Ein hoher Wert aus Komplexität ist eine Refactoring-Aufgabe, keine Testaufgabe
- - Ein hoher Wert aus fehlender Coverage ist eine Testaufgabe, und eine günstige
- - Zeigen Sie CC und Coverage immer neben dem Wert, denn die Zahl allein sagt nicht, welcher Fall vorliegt
Nach Änderungshäufigkeit gewichten
- - Änderungsrisiko zählt nur dort, wo Änderungen passieren
- - Ein Wert von 200 in einer seit vier Jahren unberührten Datei ist weniger dringend als 60 in einer wöchentlich bearbeiteten
- - Sortieren Sie nach Wert mal Commit-Häufigkeit und arbeiten Sie diese Liste von oben ab
Ausnahmen explizit machen
- - Generierter Code, Adapter und vollständige switch-Anweisungen treiben die Komplexität ohne echtes Risiko
- - Schließen Sie sie in der Konfiguration aus, mit einem Kommentar zum Grund, statt still den Schwellwert zu heben
- - Prüfen Sie die Ausnahmeliste erneut, wenn der geschützte Code nicht mehr generiert wird
Häufige Fehldeutungen
Es als Qualitätsnote nehmen
CRAP schätzt das Risiko, Code zu ändern. Es sagt nichts darüber, ob der Code korrekt, gut benannt oder gut entworfen ist. Eine saubere, gut abgedeckte Methode mit echter Fachkomplexität erhält denselben Wert wie eine unangenehme.
Mit Coverage tricksen
Snapshot-alles-Tests und Durchläufe ohne Assertions bewegen die Zahl, nicht das Risiko. Wenn CRAP ein Gate ist, muss etwas die Tests ehrlich halten, sei es das Review, Mutationstests oder beides.
Ein Aufräumprojekt starten
Ein repositoryweiter CRAP-Report auf Altcode erzeugt eine so große Zahl, dass sie ignoriert wird. Setzen Sie stattdessen die Ratsche auf geänderten Code und lassen Sie die gefährlichen Stellen von denen beheben, die sie ohnehin angefasst hätten.
Ein gepflegtes Werkzeug erwarten
Das ursprüngliche Crap4j ruht seit Jahren. NDepend führt die Metrik unter .NET, und es gibt Community-Implementierungen für Rust, .NET und Groovy, aber auf den meisten Stacks berechnen Sie sie selbst aus Daten, die Sie schon erheben.
Eine Zahl, eine Frage
CRAP ist keine Qualitätsnote und war nie als solche gedacht. Es beantwortet eine engere und nützlichere Frage: wenn nächste Woche jemand diese Methode bearbeitet, wie wahrscheinlich bricht dann etwas leise? Komplexität sagt, wie viele Wege es gibt, es falsch zu machen, Coverage sagt, wie viele davon jemand beobachtet, und die Formel gewichtet das Zweite stärker als das Erste.
Das genügt, um sie in einen Build einzubauen. Gaten Sie neuen und geänderten Code an einem Schwellwert, sortieren Sie den Rest nach Wert gegen Änderungshäufigkeit und lesen Sie die beiden Eingaben getrennt, damit die Zahl in eine Handlung mündet: diese Methode teilen oder sie testen. Denken Sie nur daran, dass die Coverage-Hälfte der Formel nur so viel bedeutet, wie Ihre Assertions ihr bedeuten lassen.
Verwandte Engineering-Artikel
Die Coverage-Hälfte dieser Formel ist genau das, was Mutationstests hinterfragen, und in Agenten-Workflows hat sie schon eine eigene Rolle bekommen.
Mutationstests sind negativ. TDD-Tests sind positiv.
Mutationsläufe können nur melden, was eine Suite übersieht, während TDD-Tests sagen, was das System tun muss. Wie man beides entsprechend einsetzt.
SwarmForge: Was der Agentenschwarm von Uncle Bob richtig macht
Ein Review der Rollenaufteilung, Worktree-Isolation und des Handoff-Protokolls von SwarmForge und was davon auf jedes Team übertragbar ist.
FAQ
Was gilt als schlechter CRAP-Wert?
Dreißig ist der übliche Standardschwellwert, geerbt von Crap4j und von den meisten späteren Implementierungen übernommen. Senken Sie ihn für Neuentwicklung, wo das Halten der Linie wenig kostet, und behalten Sie ihn bei Altcode, während Sie die Ratsche anziehen, statt ihn zu erhöhen, damit ein Report besser aussieht.
Warum wird Komplexität quadriert, der ungetestete Anteil aber kubiert?
Damit Coverage den Wert schneller bewegt als Komplexität. Der kubierte Term fällt gegen null, wenn die Coverage sich der Vollständigkeit nähert, was das Testen komplexen Codes sofort belohnt, während der quadrierte Term plus die angehängte Komplexität einen Boden lässt, den kein Testen entfernt.
Ist die CRAP-Metrik noch relevant?
Das ursprüngliche Java-Werkzeug ruht längst, aber die Formel erscheint immer wieder in neuen Ökosystemen, weil sie trivial zu implementieren ist und eine Frage beantwortet, die Einzelmetrik-Dashboards nicht beantworten können. Wo Sie Coverage und Komplexität schon erheben, sind Sie zwei Zeilen davon entfernt.
Bedeutet ein niedriger CRAP-Wert, dass Änderungen sicher sind?
Er bedeutet, dass die Komplexität abgedeckt ist, und das ist nicht dasselbe wie gut getestet. Coverage zählt Ausführung, nicht Prüfung, also ist ein niedriger Wert auf schwachen Tests eine bequeme Zahl über einer unbequemen Realität. Mutationstests sind der übliche Weg zu prüfen, ob die Coverage echt ist.
Sollte CRAP den Build brechen?
Als Ratsche auf neue und geänderte Methoden ja, denn das ist durchsetzbar und muss von niemandem eingeplant werden. Als repositoryweites Gate auf einer bestehenden Codebasis nein. Das erzeugt ein Backlog statt einer Verhaltensänderung.