Dieses Bild zeigt eine geöffnete Hand, die einen klassischen Taschenkompass mit aufgeklapptem Deckel hält. Im Hintergrund verschwimmt eine hügelige Landschaft im warmen Licht der tiefstehenden Sonne, durch die sich ein Weg zieht. Die Bildsprache trifft den Kern des Artikels: Der CRAP-Index ist ein altgedientes Instrument, das wie ein Kompass die Richtung weist. Er zeigt, wo du anfangen solltest, aber er bewertet nicht, wie gut deine Arbeit ist.

In meinem Artikel „Wenn die Metrik zum Ziel wird“ habe ich über Goodhart's Law geschrieben und darüber, was passiert, wenn Code Coverage vom Indikator zum Ziel gemacht wird. Beim Schreiben musste ich immer wieder an eine andere Metrik denken, die schon so lange Teil der Code Coverage-Funktionalität von PHPUnit ist, dass viele Entwicklerinnen und Entwickler davon ausgehen, sie sei schon immer da gewesen: der CRAP-Index. Seine Geschichte ist es wert, erzählt zu werden, nicht zuletzt weil sie recht gut zeigt, wie ich über Metriken denke.

Entstehungsgeschichte

Im Jahr 2007 beschäftigten sich Alberto Savoia und Bob Evans bei Agitar Labs mit der Frage, wie sich Code identifizieren lässt, dessen Änderung riskant ist. Ihre Antwort kombinierte zwei Messungen, die für sich genommen längst etabliert waren: die zyklomatische Komplexität, die die Anzahl der linear unabhängigen Pfade durch eine Code-Einheit zählt, und die Code Coverage, die misst, wie viel dieses Codes von automatisierten Tests ausgeführt wird.

Das Ergebnis war der Change Risk Analysis and Prediction Index, kurz CRAP, ein Name, der offensichtlich rückwärts vom Akronym her gewählt wurde. Für eine Methode m mit zyklomatischer Komplexität comp(m) und Coverage cov(m), ausgedrückt als Wert zwischen 0 und 1, ist er definiert als

CRAP(m) = comp(m)² × (1 − cov(m))³ + comp(m)

Die Exponenten sind der interessante Teil. Die Komplexität geht im Quadrat ein, der nicht abgedeckte Anteil in der dritten Potenz. Eine vollständig abgedeckte Methode erhält als Wert ihre bloße Komplexität; eine ungetestete komplexe Methode ihre Komplexität zum Quadrat. Komplexer Code ist akzeptabel, sagt die Formel, aber nur, wenn er gründlich getestet ist. Savoia und Evans schlugen 30 als Schwellenwert vor, oberhalb dessen eine Methode als, nun ja, crappy gelten sollte.

Vorgestellt haben sie die Metrik in einer Reihe von Blogbeiträgen im Sommer und Herbst 2007, die in der Ankündigung von Crap4j gipfelte, einem experimentellen Eclipse-Plug-in, das CRAP-Werte für Java-Code berechnete. Dabei waren sie erfrischend ehrlich: Die Metrik wurde ausdrücklich als experimentell bezeichnet, und die Zahlenwerte in der Formel galten als Ausgangspunkt, der mit wachsender Erfahrung verfeinert werden sollte.

CRAP in PHPUnit

Mir gefiel die Idee sofort. Am 6. August 2007 implementierte ich die CRAP-Berechnung im Software-Metriken-Code von PHPUnit, und PHPUnit 3.2.0, veröffentlicht am 19. November 2007, war die erste Version, die sie auslieferte. Das war, bevor Crap4j selbst richtig gestartet war. Das PHP-Ökosystem war ein Early Adopter.

Als ich die Code Coverage-Funktionalität aus PHPUnit in die heutige Bibliothek php-code-coverage auslagerte, zog der CRAP-Index mit um. Im Dezember 2009 fand er seinen Weg in das Clover-XML-Logfile und in den HTML-Report für Code Coverage, wo er seither zu Hause ist. Als die Metrik 2010 von „Change Risk Analysis and Prediction“ in „Change Risk Anti-Patterns“ umbenannt wurde, zog ich nach, und 2013 ergänzte ein Beitrag aus der Community die Unterstützung für das Crap4j-XML-Berichtsformat.

Die Verfeinerung, die nie stattfand

Die Metrik war, du erinnerst dich, als Ausgangspunkt angekündigt, der mit wachsender Erfahrung verfeinert werden sollte. Diese Verfeinerung fand nie statt. Crap4j wurde nach wenigen Jahren aufgegeben, und mir ist bis heute keine einzige begutachtete empirische Studie bekannt, die den CRAP-Index validiert: nichts, das CRAP-Werte mit tatsächlichen Fehlern oder mit Wartungsaufwand korreliert, nichts, das die Exponenten in der Formel oder den Schwellenwert von 30 rechtfertigt. Die Formel, die php-code-coverage heute berechnet, ist Zeichen für Zeichen die experimentelle Formel von 2007.

Was die Forschungsliteratur bietet, sind Erkenntnisse über die beiden Zutaten, und die sind ernüchternd. Die zyklomatische Komplexität korreliert so stark mit der Anzahl der Codezeilen, dass mehrere Studien zu dem Schluss kommen, sie messe kaum mehr als Größe: Eine Methode mit vielen Pfaden ist vor allem eine lange Methode. Und Code Coverage korreliert nur schwach mit der Fähigkeit einer Testsuite, Fehler tatsächlich zu finden. CRAP kombiniert zwei Kennzahlen, deren jeweilige Aussagekraft in Frage gestellt wurde, und die Kombination selbst wurde nie validiert.

Eine Heuristik, kein Qualitätsmaß

Warum also ist der CRAP-Index seit fast zwei Jahrzehnten Teil des Code Coverage-Berichts von PHPUnit? Weil ich ihn nie als Qualitätsmaß behandelt habe. Für mich ist er eine Heuristik zur Priorisierung, und in dieser Rolle hat er mir gute Dienste geleistet.

Wenn du den Code Coverage-Bericht für Legacy-Code öffnest, willst du vor allem wissen, wo du anfangen sollst. Genau das verrät dir die Sortierung der Methoden nach ihrem CRAP-Index: Sie zeigt auf den Code, der komplex und zugleich ungetestet ist, also dorthin, wo Änderungen am ehesten schiefgehen und wo neue Tests die meiste Sicherheit bringen. Einen CRAP-Wert senkst du, indem du Tests schreibst, durch Refactoring die Komplexität reduzierst oder am besten beides tust, mit den Tests zuerst, damit das Refactoring sicher ist. Das ist ein guter Rat, ganz gleich, ob die Exponenten in der Formel empirisch gerechtfertigt sind oder nicht.

Was der CRAP-Index nicht werden darf, ist ein Ziel. Alles, was ich in „Wenn die Metrik zum Ziel wird“ über Code Coverage geschrieben habe, gilt uneingeschränkt auch für den CRAP-Index, denn die Coverage ist eine seiner beiden Eingangsgrößen. Ein Team, das alle CRAP-Werte unter 30 halten muss, wird sie unter 30 bekommen, und Goodhart's Law sagt uns, wie: mit Tests, die den komplexen Code ausführen, ohne sein Verhalten wirklich zu prüfen. Die Werte fallen unter 30, und das Risiko bleibt genau dort, wo es war.

Notwendig, aber nicht hinreichend

Das bringt mich dazu, wie ich über Code Coverage selbst denke. Für mich ist Code Coverage ein notwendiges, aber kein hinreichendes Kriterium für Testqualität. Code, der von keinem Test ausgeführt wird, ist trivialerweise ungetestet. Eine hohe Abdeckung des Codes, der mir wichtig ist, ist deshalb eine Voraussetzung dafür, dass ich meiner Testsuite vertraue. Aber die Coverage sagt mir nur, dass meine Tests den Code erreichen, nicht, dass sie es bemerken würden, wenn der Code falsch wäre.

Diese zweite Frage beantwortet Mutation Testing. Ein Werkzeug wie Infection nimmt kontrollierte Änderungen am Code vor, die typische Programmierfehler simulieren, und prüft, ob die Tests das bemerken. In „Path Coverage oder Mutation Testing?“ habe ich ausführlich beschrieben, wie das funktioniert und warum es Teil meiner täglichen Arbeit ist. Erst die Kombination aus beidem gibt mir Vertrauen: Die Code Coverage zeigt, dass meine Tests den Code erreichen, Mutation Testing zeigt, dass sie fehlschlagen würden, wenn der Code sich falsch verhielte. Mutation Testing ist übrigens auch die deutlich besser erforschte der beiden Ideen: Die Gültigkeit von Mutanten als Stellvertreter für echte Fehler wurde empirisch untersucht, wie es beim CRAP-Index nie geschehen ist.

Der CRAP-Index bleibt also, wo er seit 2007 ist: im Code Coverage-Bericht, wo er meine Aufmerksamkeit sortiert und nie meine Arbeit benotet. Eine neunzehn Jahre alte experimentelle Formel taugt nicht als Quality Gate, aber als Hinweis darauf, welchen Code ich mir als Nächstes ansehen sollte, hat sie sich bemerkenswert gut gehalten.