Codequalitäts-Metriken

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.

CRAP(m) = CC² × (1 − cov)³ + CC
  • 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

js

Jeder 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

sh

Eine 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

js

Wenn 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.

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.

© 2026 - Ryware.