Codequalitäts-Metriken

Die CRAP-Metrik: Ungetestete Komplexität finden

CRAP steht für Change Risk Anti-Patterns. Die Metrik multipliziert, wie verworren eine Methode ist, mit dem, wie untestet sie ist, und beantwortet eine Frage, die keine einzelne Metrik beantworten kann: welcher Code ist gefährlich zu ändern? Diese Frage ist deutlich dringlicher geworden, seit Agenten den Code schreiben.

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.

Wofür sie tatsächlich da ist

CRAP ist keine Dashboard-Dekoration. Jeder Wert mündet in eine von vier Entscheidungen, und genau darum berechnet man ihn.

Wohin der nächste Test geht

Absteigend sortiert ist der Bericht eine Arbeitsliste für Testaufwand. Oben steht, wo ein Test die größte Risikoreduktion pro Stunde bringt — eine viel bessere Antwort als das Jagen eines globalen Coverage-Prozentwerts.

Was vor dem Anfassen refaktoriert wird

Ein hoher Wert aus Komplexität warnt, dass die Methode sich wehren wird. Sie vor dem Hinzufügen von Verhalten zu teilen ist meist billiger, als Verhalten hinzuzufügen und das Ergebnis danach testbar machen zu wollen.

Welche Änderung ein Mensch genau lesen muss

Ein Diff, das CRAP auf berührten Methoden hebt, verdient ein zeilenweises Review. Ein Diff, das ihn senkt, darf überflogen werden. Diese Routing-Entscheidung ist der Punkt, an dem sich die Metrik in Review-Zeit bezahlt.

Was vor einem Release regressiv getestet wird

Änderungsrisiko plus jüngste Änderungsfrequenz sagt, welche Module in diesem Release einen Regressionslauf verdient haben und welchen man vertrauen kann, weil sich darin nichts bewegt hat.

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, und genau in diese Lücke läuft ein LLM ohne Umweg.

Wie ein QA-Team sie einsetzt

Dieser Teil fehlt in Metrik-Texten meistens. CRAP ist ein Zielinstrument, und QA ist die Instanz, die es ausrichtet.

Die Regressionssuite ausrichten statt vergrößern

  • - Methoden nach CRAP mal Commit-Häufigkeit ranken und den Release-Regressionslauf von oben aus dieser Liste bauen
  • - Module mit niedrigem CRAP und ohne Änderungen brauchen nicht jedes Release einen neuen Durchlauf, und genau daraus entsteht die Zeit für die Spitze der Liste
  • - Pro Release neu ranken statt eine statische Suite zu pflegen, die endlos wächst und nie beschnitten wird

Über Automatisierung versus Exploration entscheiden

  • - Hohe Komplexität mit niedriger Coverage ist eine Unit-Test-Lücke, keine Explorationslücke — manuelle QA 20 Zweige per Hand abdecken zu lassen verschwendet einen guten Tester
  • - Hohe Komplexität mit hoher Coverage ist der Ort, an dem exploratives Testen sich auszahlt, weil die Pfade laufen, die Anforderungen aber falsch sein können
  • - Undefiniertes Verhalten versteckt sich in unabgedeckten Zweigen komplexer Methoden, also entstehen dort zuerst Negativ- und Grenzfälle

Release-Gates in Zahlen statt in Meinungen

  • - Ein Gate, das keine geänderte Methode über dem Schwellwert erlaubt, ist durchsetzbar, prüfbar und am Sprintende schwer zu bestreiten
  • - Beide Eingaben neben dem Wert melden, damit die Behebung eindeutig ist: teilen oder testen
  • - Entwichene Defekte gegen den CRAP-Wert ihrer Methode verfolgen — diese Korrelation ist der Weg, den eigenen Schwellwert zu justieren statt unseren zu erben

Als gemeinsame Sprache mit der Entwicklung nutzen

  • - Qualitätseinwände als diese Methode ist unschön verlieren gegen Deadlines; als diese Methode hat Komplexität 24 bei 30 % Coverage meist nicht
  • - Es gibt QA einen legitimen, vorab vereinbarten Grund, ein Refactoring vor dem Feature einzufordern
  • - Es schützt auch Entwickler vor Beschäftigungstherapie, denn die Metrik sagt ausdrücklich, dass trivialer Code keine Tests braucht

CRAP nutzen, um Code zu beurteilen, den ein LLM schrieb

Agenten haben die Ökonomie dieser Metrik verändert. Sie produzieren plausiblen Code schneller, als jemand ihn lesen kann, und sie schreiben bereitwillig die Tests, die sie selbst messen. Komplexität gegen Coverage ist eines der billigsten ehrlichen Signale, das Ihnen bleibt.

Die konkrete Gefahr ist, dass ein LLM auf das sichtbare Ziel optimiert. Bitten Sie um Tests, bekommen Sie Tests; bitten Sie um Coverage, bekommen Sie Coverage. Beide Hälften von CRAP kann ein Agent bewegen, aber nur eine davon ehrlich. Komplexität ist eine strukturelle Eigenschaft des Codes, den er geschrieben hat — sie lässt sich nicht herbeireden. Coverage ist eine Zahl, die der Agent heben kann, indem er Zeilen ausführt, ohne etwas über sie zu behaupten. Damit ist das Paar diagnostisch: Komplexität sagt, was der Agent gebaut hat, Coverage sagt, was er darüber behauptet, und der Abstand dazwischen ist die Stelle, auf die man schaut.

Das Diff bewerten, nicht das Repository

  • - CRAP auf den vom Agenten berührten Methoden vor und nach der Änderung berechnen und das Delta am Pull Request melden
  • - Steigende Komplexität bei flacher Coverage ist die Signatur von Verhalten, das an eine bestehende Funktion angeschraubt wurde — die Standardweise, wie ein Agent ein Feature ergänzt
  • - Sinkende Komplexität bei steigender Coverage ist, wie ein guter Agentenlauf aussieht, und das darf man mit schnellerem Review belohnen

Eine Komplexitätsgrenze in die Anweisungen des Agenten schreiben

  • - Uncle Bobs Schwarm macht genau das: die cleaner-Rolle führt zuerst das CRAP-Werkzeug aus und drückt die Komplexität auf 6 oder darunter, bevor irgendetwas anderes passiert
  • - Das ist der klügere Einsatz der Metrik als ein Gate auf den Wert, denn bei Komplexität 6 kann die Zahl kaum steigen, egal was die Coverage tut
  • - Geben Sie dem Agenten Schwellwert und Befehl, nicht einen Absatz über Clean Code, dann hält er sich daran, weil die Prüfung einen Exit-Code hat

Die Werkzeugausgabe in den Kreislauf zurückgeben

  • - Ein CRAP-Bericht in einem Dashboard verändert nichts; derselbe Bericht als fehlgeschlagene Prüfung im nächsten Zug des Agenten wird behoben
  • - In einem separaten Schritt laufen lassen, getrennt von dem, der den Code schrieb, damit der Agent nicht seine eigenen Hausaufgaben bewertet
  • - Wiederholungen begrenzen — ein Agent, der es in zwei Versuchen nicht unter den Schwellwert bringt, sagt Ihnen, dass das Design falsch ist, nicht dass er noch einen Versuch braucht

Nie einen Agenten Coverage heben und die Metrik melden lassen

  • - Coverage ist die manipulierbare Hälfte, und ein Agent, der seinen eigenen Wert verbessern soll, findet den billigsten Weg zur Zahl
  • - Jedes CRAP-Gate mit einem Mutationslauf paaren, der fragt, ob diese neuen Tests überhaupt etwas behaupten
  • - Die Erklärung des Agenten zu einem Wert wie Werbetext behandeln; das Beweismittel ist die Werkzeugausgabe

Das Signal auf agentengeschriebenem Code lesen

Dieselben Zahlen bedeuten sehr konkrete, wiedererkennbare Dinge, wenn der Autor ein Modell ist.

Signal Was es meist bedeutet Was zu tun ist
Komplexität gestiegen, Coverage flach Neues Verhalten wurde als weitere Zweige in einer bestehenden Funktion ergänzt. Vor dem Review Extraktion verlangen. Das ist die häufigste Wachstumsform von Agentencode.
Coverage gesprungen, Komplexität unverändert Es wurden Tests ergänzt. Ob sie etwas behaupten, ist damit noch nicht belegt. Mutationstests auf den geänderten Dateien laufen lassen, bevor Sie der Zahl glauben.
Eine Methode weit über dem Schwellwert Das Modell hat Prompt für Prompt Fälle an die Stelle angehängt, die es zuerst gefunden hat. Nach Regeln aufteilen und neu messen. Der Wert bricht meist ohne einen einzigen neuen Test ein.
Komplexität in Code, den niemand verlangt hat Spekulative Zweige, Abwehrpfade und Optionen ohne Anforderung dahinter. Löschen. Ungetestete Komplexität, die keine Anforderung benennt, ist das billigste Aufräumen.
Wert gut, Coverage aus Snapshot-Tests Die Suite führt alles aus und prüft fast nichts. Die Metrik lügt über ihre Coverage-Eingabe. Reparieren Sie die Tests, nicht den Wert.

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. Für agentengeschriebene Branches ist das das ganze Gate.

# 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 — auch Regeln, die ein späterer Agent anhängt.

// 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;
}

Das Gate, an dem ein Agent nicht vorbeiredet

sh

Zwei Prüfungen, in dieser Reihenfolge, ausgeführt von einem Schritt, der den Code nicht geschrieben hat. Die erste sagt, dass der Code die Form von etwas hat, das man ändern kann; die zweite sagt, dass die schützenden Tests wirklich reagieren, wenn er sich ändert.

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

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 — Review, Mutationstests oder beides. Mit einem Agenten im Kreislauf sollten Sie damit rechnen, dass es passiert, sofern keine separate Prüfung es verhindert.

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, Menschen oder Agenten, die sie ohnehin angefasst hätten.

Ein gepflegtes Werkzeug erwarten

Das ursprüngliche Crap4j ruht seit Jahren. NDepend führt die Metrik unter .NET, es gibt Community-Implementierungen für Rust, .NET und Groovy, und Uncle Bob pflegt crap4j, crap4go und crap4clj für seinen Agentenschwarm. 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.

Damit ist es ein Zielwerkzeug für QA und ein Prüfwerkzeug für KI-gestützte Arbeit. Richten Sie es aufs Diff, gaten Sie neuen und geänderten Code an einem Schwellwert, ranken Sie den Rest nach Wert gegen Änderungsfrequenz, und halten Sie beide Eingaben sichtbar, damit die Zahl in eine Handlung mündet: diese Methode teilen oder sie testen. Denken Sie nur daran, welche Hälfte ein Autor fälschen kann. Komplexität ist, was der Code ist; Coverage ist nur, was die Tests behaupten, und Mutationstests sind die Art, die Behauptung zu prüfen.

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.

Ist CRAP nützlich, um KI-generierten Code zu prüfen?

Es ist eines der nützlichsten billigen Signale, weil Komplexität eine strukturelle Tatsache über den Code ist, den das Modell erzeugt hat, und sich nicht bestreiten lässt. Bewerten Sie das Diff statt das Repository, achten Sie auf steigende Komplexität bei flacher Coverage, und paaren Sie es immer mit einem Mutationslauf, damit die Coverage-Hälfte nicht durch Tests aufgeblasen wird, die nichts behaupten.

Welchen Schwellwert für agentengeschriebene Branches?

Strenger als für Menschen, und als Komplexität ausgedrückt statt als Wert. Das praktikable Muster, und das des Schwarms von Uncle Bob, ist eine harte Komplexitätsgrenze um 6 auf geänderten Methoden: dort kann der CRAP-Wert kaum steigen, was auch mit der Coverage passiert, also bleibt nichts zu verhandeln.

Kann ein Agent seinen eigenen CRAP-Wert reparieren?

Für die Komplexitätshälfte meist ja, und das ist echte Verbesserung, die sich zu automatisieren lohnt. Bei der Coverage-Hälfte Vorsicht: der billigste Weg zu mehr Coverage ist, Code auszuführen, ohne ihn zu prüfen. Lassen Sie das Gate in einem Schritt laufen, der den Code nicht geschrieben hat, und prüfen Sie die neuen Tests mit Mutationstests.

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.

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 Solutions Ltd. · Firmennr. 516681764