Follow my new blog

Mittwoch, 4. Januar 2012

Bei häufigem Kontakt mit Gift refaktorisieren

images/refactoring-toxicity.pngGraham Brooks bricht eine Lanze dafür, Code nicht mit der Gießkanne zu refaktorisieren, sondern den Aufwand dort zu treiben, wo es gerade besonders nutzt. Das hört sich sinnig an.

Noch sinniger wird es, wenn Graham als Nützlichkeitsindikatoren Giftigkeit (Toxicity) und Unbeständigkeit (Volatility) angibt.

Giftig (als Steigerung von schmutzig) ist der Code dort, wo bestimmte Metriken besonders schlimme Werte annehmen. Unbeständig ist er dort, wo häufig an ihm gearbeitet wird.

In Kombination ergibt sich eine Giftigkeit x Unbeständigkeit Matrix, die klar sagt: Refaktorisiere, wo häufiger Veränderungskontakt mit hoher Giftigkeit besteht; aber lass Code stabilen (relativ) ungiftigen Code in Ruhe. Das hört sich sinnig an.

image
Quelle: Graham Brooks

Nun fragt Jens Schauder in einem Tweet aber zurecht: Und wie misst man nun Giftigkeit? (Die Unbeständigkeit ist kein Problem; sie lässt sich aus einem VCS Log herauslesen.)

Graham sagt nichts dazu. Er verweist vielmehr auf diesen Artikel von Erik Dörnenburg. Der zeigt dann ein konkreteres Giftigkeitsdiagramm und gibt konkrete Metriken an. Auch sehr sinnig.

Wenn Sie nun aber einmal diese Grafik genauer anschauen,…

image
Quelle: Erik Dörnenburg

…dann fällt Ihnen sicher auf, dass sich im Grunde nur bei vielleicht 10 (7,5%) von 132 Dateien die Giftigkeit aus mehr als Zyklomatischer Komplexität (ZK) und Methodenlänge (ML) ergibt.

Die Werte anderer Metriken spielen diesen beiden gegenüber kaum eine Rolle - oder sie korrelieren mit ihnen positiv: wo ZK+ML groß ist, da ist z.B. auch der Fan-Out (efferente Kopplung) groß.

Ich frage mich deshalb: Was soll dieser ganze komplizierte Metrikenkram? Wenn man sonst nichts zu tun hat, kann man sich damit einen schönen Tag machen und wichtig aussehen. Für die Entscheidung an der Praxisfront jedoch reichen einfachere Heuristiken. Und um nicht mehr als Heuristiken geht es ohnehin. Denn auch die exaktesten Berechnungen von Metriken sollten nicht darüber hinweg täuschen, dass Giftigkeit vor allem im Auge des Betrachters entsteht.

Ich mache es mir in der Codebeurteilungspraxis einfacher. Mein Fokus liegt auf nur zwei Metriken:

  • LOC: Je länger eine Codeeinheit, desto schmutziger und schließlich giftiger ist sie. (Oder genauer: Es geht eigentlich um Anweisungen und nicht um Lines. Aber LOC hat sich einfach eingebürgert als Begriff.)
    Wieviele LOC eine Codeeinheit hat, kann man sehen.
  • Zyklomatische Komplexität: Je höher die ZK, desto schmutziger und schließlich giftiger ist eine Codeeinheit.
    Wie die ZK einer Codeeinheit ist, kann man abschätzen nach der Zahl der Kontrollanweisungen (Fallunterscheidungen, Schleifen) und ihrer Schachtelungstiefe.

Auch hier gibt es natürlich eine gewisse positive Korrelation: bei wenigen LOC wird die ZK eher gering sein, bei vielen LOC wird sie tendenziell auch steigen.

Dennoch möchte ich auf keine der Metriken verzichten. Auch wenn sie korrelieren, repräsentieren sie unterschiedliche Aspekte des Codes. LOC steht für mich eher im Zusammenhang mit dem SRP. Und ZK steht für mich eher im Zusammenhang mit Testbarkeit.

Ab welcher Länge oder Komplexität wird Code nun aber nicht nur schmutzig oder giftig? Meine persönlichen Alarmglocken gehen an, wenn eine Methode länger als eine Bildschirmseite ist, also bei LOC > 20-30, und/oder mehr als 2-3 Kontrollanweisungen enthält und/oder die Schachtelungstiefe > 2 ist.

Letztlich geht es aber für das Refaktorisieren weniger um absolute Zahlen. Refaktorisiert werden sollte dort, wo die Giftigkeit “nur” größer ist als woanders. Das gilt für Methoden mit 10.000 LOC und ZK 20 vs 8.000 LOC mit ZK 15 genauso wie für Methoden mit 100 LOC und ZK 10 vs 80 LOC mit ZK 6.

Und dieses Verhältnis sollte dann auch noch im Lichte der Unbeständigkeit betrachtet werden. Da stimme ich Graham Brooks zu.

Darüber hinaus interessiert mich allerdings noch die afferente Kopplung (AK, Fan-In). Denn wenn Codeeinheiten von vielen anderen referenziert werden, haben sie eine große Strahlkraft. Änderungen/Fehler an/in ihnen wirken sich leicht auf weite Teile der Software aus. Das bedeutet, Codeeinheiten mit hoher afferenter Kopplung verdienen ein besonderes Augenmerk.

Wenn ich eine Liste mit Codeeinheiten und ihren Metriken wie die folgende hätte

Codeeinheit(Name, LOC, ZK, AK, Unbeständigkeit)

würde ich sie so sortieren:

  1. Zuerst Codeeinheiten mit hoher Unbeständigkeit...
  2. ...dann solche mit hoher afferenter Kopplung...
  3. ...dann solche mit hoher LOC...
  4. ...dann solche mit hoher ZK.

Komplizierter muss die Entscheidungsgrundlage für die Refaktorisierung nicht sein, finde ich. Es sind keine komplizierten Schwellenwerte nötig, keine weiteren Metriken. LOC und ZK kann man sogar mit dem unbewaffneten Auge recht gut beurteilen. Für Unbeständigkeit und AK ist Werkzeugunterstützung nützlich.

Kommentare:

Sebastian Jancke hat gesagt…

Die Korrelationen zwischen den Metriken (insb Loc und McCabes CyclComlexity) ist nachgewiesen. Mir fällt adhoc das Paper nicht ein, kann ich aber bei Interesse raussuchen. Ich meine es war eine Metastudie zu Metriken. Meiner Erfahrung nach reichen die Metriken in deiner Heuristik völlig aus.
Du scheinst anzudeuten, die Beschaffung beliebiger Werte sei "schwierig" - mit aktuellen Werkzeugen wie Sonar kriegt man diese plus Visualisierug fast geschenkt.

Anonym hat gesagt…

Vielleicht das hier:


http://www.qsm.com/resources/function-point-languages-table

Florian Fanderl hat gesagt…

Und was mache ich mit Coder der overdesigned ist? Auch solchen Code gibt es. Der erfüllt beide Metriken ganz wunderbar, würde also überhaupt nicht auffallen. Aber das Design der Klassen ist dermaßen verhunzt, dass ich mich erstmal 1 Stunde damit beschäftigen darf bevor ich verstehe was da passiert. Ich denke das sind die wirklich schlimmen Fälle die man noch dazu sehr schlecht erkennt. Da würde dann der Ansatz "wie oft ändere ich etwas an dieser Codestelle und habe Schmerzen dabei" sehr gut helfen.
Letzten Endes wird doch nur gesunder Menschenverstand helfen die Stellen zu finden die wirklich ein Refactoring nötig haben. Wenn ich dort nie etwas ändern muss und das Teil funktioniert, werde ich den Teufel tun und dort etwas ändern. Zumal für solch "giftigen" Code meistens auch keine Tests existieren.

Ralf Westphal - One Man Think Tank hat gesagt…

@Sebastian: Die Beschaffung von Messwerten ist nicht schwierig. Das Problem ist eher, dass es zu einfach ist :-) Die Schwierigkeit liegt in der Auswahl. Da gibt es kein Halten mehr. Man kann grübeln und grübeln und finetuning betreiben, um den besten Metrikmix zu finden.

Das ist dann, als würden wir über die 12. Nachkommastelle von Pi reden, wenn wir herausfinden wollen, wieviele Orangen in unsere Einkaufstüte passen.

Ich denke, hier haben wir wieder mal ein Beispiel, wo Tools uns den Blick fürs Wesentlich verstellen. Weil wir dollste Sachen messen können, messen wir, bis der Arzt kommt. Mit den Tools geht das Augenmaß verloren und damit der Blick fürs Wesentliche.

Eine andere Analogie: Apparatemedizin. Wir können heute Blut- und Genomanalyse betreiben... nur leider verlieren wir dabei immer mehr den Patienten mit seinen wahren Problemen aus dem Blick.

Deshalb: Reduktion. So wenige Metriken wie möglich, soviele wie nötig.

LOC+ZK sagen, wie schlimm die Vergiftung ist. V(olatility)+AK sagen, wo Entgiftung am ehesten Not tut.

Sven Peters hat gesagt…

Ralph, ich stimme mir dir überein, dass zu viele Metriken den Blick fürs Wesentlichen vernebeln. Eigentlich finde ich den alten Begriff von Martin Fowler "Code Smells" für mich immer noch am Besten. Als relativ erfahrener Programmierer, hat man ein gutes Gespür für giftigen Code. Wenn man öfter mehr ein Mal über solchen Code stolpert, ist das ein guter Ansatz für ein Refactoring. Meistens refactored man ja auch nicht wie wild an seinem Code rum, sondern implementiert neue Funktionalität. Wenn man ehrlich ist, kennt man als Senior-Programmierer eigentlich die Stellen, an denen der Code "wehtut". Ich bin also auch der Meinung, dass man das Thema nicht zu sehr akademisch betrachten sollte. Wir verbessern unsere Programmierkenntnisse halt ständig und auch mit den Besten Metriken, wird ich wohl in einem Jahr über meinen Code von heute denken: "Was sollte das eigentlich?"

kimkulling hat gesagt…

Merke: man kann jede metrik missbrauchen, den gesunden Menschenverstand kann keine Messung ersetzen. Und ich denke,das will man auch gar nicht.

Ralf Westphal - One Man Think Tank hat gesagt…

@Kim: Gesunder Menschenverstand ist ne wichtige Sache.

Allerdings: Wir sollten ihn eben auch nicht überschätzen. Ein wenig Unterstützung durch "Objektivierung" kann nicht schaden. Deshalb finde pragmatisches Messen angezeigt.

Ansonsten kann es nämlich passieren, dass auch der gesunde Menschenverstand keine Grenze findet. Wenn alles nach Gefühl geht, zieht schnell Maßlosigkeit ein. Gefühl braucht ein Gegengewicht: Rationalität.

kimkulling hat gesagt…

Richtig, die Analyse einer Metrik muß halt immer mit gesundem Menschenverstand erfolgen. Schließlich bieten Metriken Berwertungshilfen an, keine endgültigen Lösungen. Die Kunst liegt meiner Ansicht nach darin, aus den verschiendensten Analysen / Metriken / Methodiken die richtigen Schlüsse zu ziehen.

Ralf Westphal - One Man Think Tank hat gesagt…

@Kim: Deshalb habe ich diesen Artikel geschrieben. Es geht um eine "good enough" Heuristik, um aus wenigen Werten schnell Schlüsse zu ziehen.

Denis hat gesagt…

So wenig wie moeglich, so wenig wie nötig -- das ist, denke ich, eine gesunde Herangehensweise bei Metriken.
Wenn man eine Metrik versteht und entsprechend interpretieren kann, macht es auch durchaus Sinn Trends aus Metriken fuer eine spezifische Codebasis abzulesen und daraus Konsequenzen abzuleiten. Messwerte aus Metriken sollte also immer relativ bewertet werden, nie absolut. Und schon gar nicht sollten Metriken zum Vergleich unterschiedlicher Codebasen verwendet werden (leider ein oft verwendetes Mittel falsch verstandenen Managements).
Denis

mohk hat gesagt…

Ich glaube, das hier verschiedene Dinge miteinander vermischt werden.
Toxic betrifft nur den sich aendernden Code. Da muss man aber keine Metrik erfinden, sondern nur dem Code arbeiten, wenn er als schlecht empfundunden wird, ja: EMPFUNDEN, wird er refaktorisiert, bis die die neue Funktionalitaet leicht hinzugefuegt werden kann. Als Begruendung moechte ich hier hinterschieben: Jeder Handwerker sortiert sein Werkzeug auf der Werkzeugbank so um, wie er es gewohnt ist, sonst sitzen seine Handgriffe nicht. So gehe ich mit Code auch um: Ich bereite den Code vor, damit das neue Stueck Funktionalitaet passgenau eingearbeitet werden kann. Das nebenbei und hinterher saubergemacht wird, ist normal, in der Werkstatt als auch im PC-Labor.

Was bleibt ist die Frage, was wird durch die blosse Betrachtung von LOC uebersehen. Mit etwas Bauchgefuehl und einer Portion Wissen fuehle ich mich deutlich besser als mit einer Heuristik. Ich moechte hier nochmal auf "Refactoring" von Fowler verweisen: Code smells if it's bad.

Ralf Westphal - One Man Think Tank hat gesagt…

@mohk: Dass du Code, den du gleich verändern musst, "dir zurechtlegst", ist selbstverständlich. Darum geht es mir und Graham aber nicht. Sowas passiert unter dem Radar. Das machst du für dich. Und du urteilst da mal nach Gefühl.

Mir geht es um größere Brocken. Auch das Team hat als Ganzes ja ein Gefühl dafür, ob es mit der Arbeit leicht/schwer voran geht, ob also der Code stinkt.

Die Frage ist dann: Wo und in welchem Umfang sollte er refaktorisiert werden, während das vom Radar beobachtet wird?

Hier sollte vom Gefühl Abstand genommen werden. Sonst gibt es kein Halten. Irgendwer hält immer irgendwas für ziemlich refaktorisierungswürdig.

Um den Ressourceneinsatz zu begrenzen, muss ein Entscheidungskriterium her. Das habe ich beschrieben.

PS: "Refactor on smell" ist übrigens auch eine Heuristik. Lies mal hier: http://de.wikipedia.org/wiki/Heuristik

Frank hat gesagt…

Warum stellst du hohe Unbeständigkeit über hohe Komplexität? Für mich macht die Tabelle von Graham Brooks mehr Sinn. Dort wo beides zusammenkommt, besteht der höchste Bedarf.

Die Ergänzung um die hohe afferente Kopplung macht schon Sinn, aber m.E. auch nur in Kombination mit den anderen Kriterien. Warum sollte man sauberen, stabilen Code ändern, nur weil er eine hohe afferente Kopplung hat?

Außerdem sollte man noch bedenken, dass alleine die Unbeständigkeit in der Zukunft relevant ist. Ob eine Methode oder Klasse schon hundertmal geändert wurde, spielt für sich gesehen keine Rolle. Es kommt darauf an, ob man sie noch hundertmal ändern wird. Die bisherige Frequenz der Änderungen taugt nur bedingt zur Beurteilung, wo in Zukunft eine hohe Frequenz zu erwarten ist. Es kann genauso gut sein, dass die hundertmal geänderte Klasse jetzt endlich fertig ist (z.B. weil endlich alle benötigten Features eingebaut wurden), wie eine noch nie geänderte Klasse plötzlich in den Fokus rücken kann (z.B. aufgrund einer Gesetzesänderung, die diesen bislang unberührten Teil betrifft).

Auch hier gewinnt der gesunde Menschenverstand über reinen die Zahlen aus dem VCS.

Ralf Westphal - One Man Think Tank hat gesagt…

@Frank: Ich stelle die Afferente Kopplung nicht über andere Kriterien, sondern addiere sie als Kriterium. Sie wirkt weiter priorisierend.

Natürlich ist der Blick ins Log eines VCS ein Blick in den Rückspiegel oder auf den Himmel. Angesichts unserer Unfähigkeit, in die Zukunft zu schauen, hat es sich als probate Heuristik herauskristallisiert, aus der Vergangenheit auf die Zukunft zu schließen. Das funktioniert erstaunlich gut:

Der Verkehr vor uns entspricht mit hoher Wahrscheinlichkeit dem hinter uns. (Es ist eben viel wahrscheinlicher, dass du mittem im Stau bist als an seiner Spitze oder an seinem Ende.) Das Wetter morgen entspricht mit hoher Wahrscheinlichkeit dem von heute.

Wenn du es nicht besser weißt (!), dann tust du also gut daran, dort zu refaktorisieren, wo sich lt. Log viel tut. Das ist keine perfekte Vorhersage, aber "good enough". Wie gesagt: Man muss sich nicht dümmer stellen als man ist. Wenn du also kenntnis darüber hast, dass morgen ein Bereich dran kommt, der seit 3 Jahren nicht angefasst wurde, dann schau dir seine Giftigkeit an und refaktorisiere dort.

PS: Das mit dem gesunden Menschenverstand ist so eine Sache. Wir rufen ihn gern an, wenn wir uns nicht mit (scheinbar) komplizierten Regeln auseinandersetzen wollen - allemal, wenn die dann auch nicht perfekt sind. Leider gibt es aber nicht den einen gesunden Menschenverstand. Frag 3 Leute und erhalte 3 Meinungen darüber, was "nach gesundem Menschenverstand" in einer Lage zu tun sei. Wäre der gesunde Menschenverstand eine klare Kompassnadel, dann sähe die Welt nicht so bunt aus. Ich wäre also vorsichtig mit seiner Anrufung.

Da scheinen mir Metaheuristiken wie "Wende Heuristiken mit Augenmaß an." (d.h. setze nicht alles auf ein Pferd; verstehe, dass es meist mehrere Werte gibt, die es auszugleichen gilt) oder "Wende Heuristiken in kleinen Schritten an." hilfreicher.