PHP bekommt frischen CRAP
Nein, keine zwanzig neuen Active Record basierten Frameworks mit eigenen Template Engines oder was ihr euch sonst so gedacht habt beim Lesen der Überschrift.
Der „CRAP Index“ ist eine junge Code-Metrik die versucht nah am Wortschatz und an den Gedankengängen von Entwicklern zu sein. Man könnte auch sagen: „Der WTF-Faktor einer Funktion“.
Die meisten von euch werden einfache Metriken wie die „LOC, Codezeilen“ und die „ELOC, Ausführbare Codezeilen“ kennen. Treue Leser des Blogs kennen vielleicht auch die „Zyklomatische Komplexität, Anzahl der Verzweigungen in einer Funktion“ und die „NPath Complexity, Anzahl der Ausführungspfade durch eine Funktion“. Diese altgedienten Metriken leisten bei der QS gute Dienste sind aber etwas abstrakt. „Die Zyklomatische Komplexität des Codes den du gerade commited hast ist ziemlich hoch“ ist ein Satz den man seltener hören wird als, mal ganz unverblümt kommuniziert, „was für ein unverständlicher Mist ist das denn“.
Die CRAP Metrik, übrigens mit dem lange nicht so nett klingenden „Change Risk Analysis and Predictions“ ausgeschrieben, kommt aus der Javawelt und wurde vor etwa drei Jahren mit Crap4j jedem einfach zugänglich. Für uns PHPler wird diese Metrik mit dem bevorstehenden Release von PHPUnit 3.5 interessant da sie im neuen „Code Coverage Report“ für jede Funktion mit aufgelistet wird. Eine Beta Version bzw. RC1 ist schon einige Zeit verfügbar.
Die Berechnung des CRAP Index ist realtiv trivial und zeigt auch gleich auf wieso wir dafür PHPUnit, bzw. Unittests im allgemeinen, brauchen:
Der Crap Index einer Funktion (f) berechnet sich aus der Zyklomatische Komplexität / „Cyclomatic Complexity“ (comp), und der Code Coverage (cc) der Funktion, also dem prozentualen Anteil der Codezeilen die von unseren Unittests durchlaufen werden.
Die Formel sieht nicht trivial aus aber wird hoffentlich durch das folgende Bild deutlich greifbarer.
CRAP-Index(f) = comp(f)^2 * (1 – cc(f)/100)^3 + comp(f)
Auf der X-Achse ist die Code Coverage von 100 bis 0 Prozent, auf der Y-Achse die Zyklomatische Komplexität von 1 bis 30. Mit der Grafik wird, hoffentlich, schnell deutlich das eine Funktion umso besser bewertet wird je einfacher sie ist und je ausführlicher getestet. Der Schwellenwert für „This is Crap“ ist hier mit 30 gewählt, aber das soll nur eine Richtlinie sein. Um eine bessere, d.h. niedrige, Bewertung zu erreichen kann man also entweder die Komplexität einer Funktion reduzieren oder sie besser Testen. Gerade beim Implementieren von Algorithmen wird man hier nicht „gezwungen“ eine Funktion ggf. unsinniger weiße aufzuteilen sonst kann sie durch Tests „akzeptabel“ machen.
Mit dieser Zahl kann man also einfach(er) Aussagen über die Verständlichkeit und Wartbarkeit von Code treffen. Ein einfacher „getter“ oder „setter“ wird selbst wenn er garnicht getestet ist ziemlich verständlich sein. Es ist nicht sonderlich schwer ihn zu warten. Eine halbwegs komplexe Funktion wird ohne Tests schnell über den Schwellenwert geraten und Änderungen sind dort auch nicht mehr einfach durchzuführen. Gibt es allerdings einige Tests, so was wir uns beim Refactoren darauf verlassen können nichts Kaput zu machen, ist auch der CRAP-Index unter dem Schwellenwert. Funktionen die eine Zyklomatische Komplexität von 30 oder mehr haben können wir allerdings auch mit noch so guter Code Coverage nicht mehr „retten“, sie sind oftmals zu komplex und gut wartbar zu sein.
Wenn also morgen euer Kollege zu euch kommt und sagt „Dein Code ist ganz schön CRAPpy“ wisst ihr jetzt was gemeint ist.
Danke für den Post. Sehr schöne und auch einfache Erklärung.
Ich schiebe das Thema Code Metriken schon lange vor mir her und lese auch immer wieder Artikel darüber. Bis jetzt habe ich die Zeit aber noch nicht gefunden, mich mit dem Thema intensiver auseinanderzusetzen.
Danke für den Artikel und die Erläuterungen.
Ich habe allerdings so meine Probleme mit der zyklomatischen und der N-Path Komplexität. Meiner Ansicht nach ist da ein Denkfehler drin. Wenn jemand in PHP (das häufig statt Fehlermeldungen zu liefern einfach FALSE als Rückgabewert gibt) ordentlich arbeitet, muss er viele Rückgabewerte auf potentielle Fehler prüfen. Dazu benötigt er jedes Mal eine Verzweigung.
Sauberer, gut geprüfter Code hat somit automatisch mehr Verzweigungen als schlechter Code, der im Fehlerfall den Leuten um die Ohren fliegt. Damit hat sauberer Code aber automatisch eine höhere „N-Path Komplexität“. Die echte „Komplexität“ der Funktion ist trotz größerer Anzahl Verzweigungen aber geringer, weil sie ja robuster ist als ohne.
Als Folge davon ist der CRAP-Faktor für PHP in der Regel nicht aussagekräftig. Er müsste auch berücksichtigen, wie viel Code eine Verzweigung hat. Damit nicht automatisch jeder Error-Stub, der nur eine Exception wirft, genauso hoch gewichtet wird, wie eine Verzweigung welche eine ganze Funktion in 2 Hälften spaltet.
Ich habe mich mit dem Thema auch noch nicht wirklich beschäftigt, ber dieser Artikel bietet einen wirklich interessanten Einblick.
Vielen Dank
Interessant: In dem von Nils Langner geschriebenen Artikel zur zyklomatischen Komplexität, der in diesem Artikel verlinkt wurde, steht „Einen Wert, den man immer wieder hört, den die Komplexität nicht überschreiten soll ist 10. Meiner Meinung ist der Wert schon viel zu hoch, aber die Allgemeinheit scheint da anders zu denken.“
In dem hier vorliegenden Grafik-Beispiel ist der doppelte Wert mit „popeligen“ 75% Code-Coverage auch noch in Ordnung, und noch höhere Komplexität soll auch noch akzeptiert werden. Ungeheuerlich!
Um ehrlich zu sein, habe ich in meinem Code Schwierigkeiten, Methoden oder Funktionen zu finden, die überhaupt 20 Zeilen lang sind, geschweige denn eine zyklomatische Komplexität von 20 haben!
Und ich akzeptiere nur Code Coverage von 100%. Wenn meine Unit-Tests richtig arbeiten, decken sie alle denkbaren Fälle ab, wenn aber bei allen denkbaren Fällen trotzdem nicht der ganze Code durchlaufen wird, kann er nur überflüssig sein.
@Tom
Ich sehe deinen Punkt fände es aber schade deswegen auf Metriken zu verzichten. Die Zahl sagt erst mal relativ Wertungsfrei aus wie „komplex“ eine Funktion ist und nicht wie gut der Code ist. Also nur weil eine Funktion eine höhere Komplexität hat als eine andere ist sie nicht schlechter. Es ist eher ein Schwellenwert (den man im Team selbst festlegen kann 🙂 ) an dem man sich Gedanken über Wartbarkeit machen kann.
Bei der ‚zyklomatischen Komplexität‘ könnte man sagen „jede Funktion mit mehr als 10 Verzweigungen steht mal unter dem ‚Verdacht‘ schwer wartbar zu sein. Eine Funktion die 10 PHP Funktionen aufruft und deren Rückgabewerte prüft ist sicher schon nicht ganz trivial. Eine Funktion die das 30 mal macht (oder zuerst 10 ‚preconditions‘ prüft und dann einen algo ausführt) ist wohl schon ziemlich sicher hart zu lesen.
Hast du nun Tests die Prüfen ob diese Fehlerbehandlungen wirklich nötig sind (d.h. kannst du Code schreiben der dazu führt das sie angesprungen werden) und den andere lesen können wird dadurch die Wartbarkeit verbessert. Mehr würde ich aus dem CRAP-Index nicht ablesen wollen.
Metriken sind erst mal da, was wir da rein interpretieren ist unsere Sache. Ich bin aber entschieden dagegen zu sagen „Meine Implementierung hat eine CCN von 6, deine eine von 9. D.h. meine ist ‚BESSER'“. Das ist, für mich, weit am Sinn des Werkzeugs vorbei.
(Keine Ahnung ob das deinen Punkt getroffen hat, ich meinte jetzt auch nicht dich persönlich sondern hab die Chance genutzt hier ggf. noch was klarzustellen 😉 )
@Timo
Sucht ihr gerade Leute ? 🙂 Klingt nach einer tollen Codebase die ihr euch da erarbeitet habt ! Ich bin ein bischen neidisch.
Was ich sagen will: Die vorgeschlagen Schwellenwerte für jede Metrik sind imho. für jedes Team und für jedes Projekt anzupassen.
Wenn man ein neues Projekt mit einer langen Laufzeit beginnt das hohe Qualitätsansprüche an sich stellt und die Entwickler diese über den weg dieser Metriken mittragen wollen ist jede „default“ Einstellung viel zu hoch.
Beginnt man aber in einem 2,5 Mio Zeilen legacy Projekt langsam solche Werte einzuführen bringt es wenig das die Tools über 60.000 Violations mukieren, hier will man erst einmal die „schlimmsten“ Probleme lösen und über die Zeit die Werte senken.
Es ist gerade bei riesigen Codebasen nicht immer möglich alles zu refactoren (vor allem wenn man keine Tests hat) und deswegen kann ‚erst Tests schreiben und wenns immer noch schrecklich aussieht den Code umbauen, sonst erst mal sehen wo noch Baustellen sind‘ auch ein Arbeitsablauf sein. Denke ich ?
Für Projekte in denen „100% Code Coverage“ gelebt wird ist der CRAP-Index natürlich ziemlich egal da er nur die CCN wiederspiegelt.
Danke für den Artikel, netter Einstieg in die Thematik.
Die Schwell- und Richtwerte muss wohl, wie es immer so ist, jeder für sich selbst finden. Jede Anwendung und jedes Team sieht es ja gern etwas anders.