Selbst wenn man für seinen Code nicht bezahlt wird, programmiert man ja meistens nicht ziellos vor sich hin, sondern möchte etwas Bestimmtes entstehen lassen. Nur wenn Sie rein aus Spaß programmieren oder gerade eine neue Sprache ausprobieren, bringt die Entscheidung für das Refactoring keinerlei Risiken und Nebenwirkungen mit sich. ln jedem anderen Fall hat Refactoring Kosten.
Wenn Sie nur Funktions- oder Variablennamen umbenennen oder die Einrückungen auf den lokalen Styleguide umschreiben wollen, dann lassen Sie es lieber sein. Sie bringen keine strukturellen Verbesserungen in den Code, machen es sich und anderen in der Zukunft aber möglicherweise schwerer, den Sinn Ihrer Änderungen zu verstehen. Gerade unter Zeitdruck ist es unschön, wenn man in der History des Versionskontrollsystems sehr viele Änderungen findet, die man dann in echtes Refactoring und rein kosmetische Veränderungen auseinandersortieren muss.
Auch das Auftauchen neuer Technologien ist noch kein ausreichender Grund. Wenn Sie nicht außerordentlich stichhaltige Argumente dafür vorbringen können, dass der Code durch den Einsatz einer neuen Technik oder eines neuen Frameworks wesentlich schneller, besser wartbar oder nützlicher wird, dann lassen Sie lieber alles so, wie es ist. Suchen Sie sich ein kleines Privatprojekt, bei dem Sie mit der attraktiven Neuerung herumspielen können.
Für das Nichtstun spricht insbesondere dann manches, wenn man mit anderen zusammenarbeitet. Der Wunsch nach Refactoring in einem Gemeinschaftsprojekt erwächst oft aus Gründen, die mit dem Sourcecode an sich wenig zu tun haben, z. B. Angeberei, Pro-krastination oder Machtspielchen.
Wer den Code eines anderen umschreibt, der behauptet dadurch, dass er die Aufgabe besser verstanden hat als der ursprüngliche Entwickler. Das ist dann kein Problem, wenn ein tatsächliches Kompetenzgefälle existiert und von allen anerkannt ist. Sind die Teammitglieder aber ungefähr gleich erfahren, dann sendet Refactoring von fremden Code eine nicht ganz so nette Botschaft. Um den sozialen Frieden zu wahren, sollten Sie hier vorsichtig auftreten und lieber Ihren eigenen Code überarbeiten. Zwar sollte in Entwicklerteams die goldene Regel »Code ist kein Privateigentum« gelten, und jeder sollte den Code jedes anderen erweitern und reparieren dürfen. Trotzdem empfiehlt es sich, ausführliches Refactoring möglichst vorher mit allen Beteiligten abzusprechen.
Wenn man im Zuge einer Refactoring-Orgie die gesamte Projektstruktur umbaut, bringt man die Mitstreiter durcheinander. Vielleicht nur in ihren Arbeitsabläufen und Gewohnheiten, vielleicht fühlen sie sich aber auch zu Programmierern zweiter Klasse degradiert.
Bedenken Sie auch, dass Sie in der Zeit, in der Sie bestehenden Quellcode umbauen, nicht das tun, was die anderen sich vielleicht dringender wünschen, nämlich dass Sie unangenehme Aufgaben erledigen, die Ihre Kollegen genauso ungern übernehmen wollen wie Sie. Jeder hat unproduktive Phasen, in denen die Implementierung von neuen Features sehr zäh läuft. Diese Phasen kann man manchmal zur Codepflege nutzen, aber man sollte nicht aus purer Prokrastination den Code einmal von rechts auf links wenden. Man geht dann zwar mit dem Gefühl nach Hause, etwas getan zu haben, hat aber netto die Produktivität der Gruppe möglicherweise verringert. Auch wenn Sie nur ganz allein vor sich hinprogrammieren: Widerstehen Sie der Verlockung, Ihren Code zu verschönern, um schwierigeren Aufgaben aus dem Weg zu gehen. Das ist das Äquivalent zum gründlichen Wohnungsputz, wenn man eigentlich gerade eine Diplomarbeit schreiben sollte.
Selbst wenn das alles nicht zutrifft und Sie nur die alleredelsten Beweggründe für Ihr Refactoring haben: Man wird es Ihnen nicht unbedingt danken. Sie werden viel Zeit in
Wann manaufRefactoring besser verzichtet I 209
Änderungen investieren, nach denen der Code exakt dasselbe tut wie vorher. Wenn alles gutgeht, dürfen Sie nicht viel Beifall ervarten. Und wahrscheinlich wird es nicht gutgehen - wie oft haben Sie schon Code geändert, ohne dabei neue Fehler einzubauen? Im schlimmsten Fall machen Sie aus unwichtigem Grund etwas Wichtiges kaputt. Damit ziehen Sie dann schnell den berechtigten Ärger der Mitprogrammierer auf sich, denn Refactoring ist kein Selbstzweck, sondern soll die zukünftige Arbeit vereinfachen. Erschwert es die derzeitige Arbeit, dann machen Sie etwas falsch.
Von Gerald Weinberg stammt dieser Aphorismus: »There is no code so big, twisted or complex that maintenance can't make it worse.« Er mahnt zur Vorsicht, komplexen Code nicht durch gut gemeinte Wartungsarbeiten zu verschlechtern. Refakturieren Sie also keinen Code, den Sie nicht verstanden haben, denn sonst ergeht es Ihnen wie einem der Autoren in dem Fall, der im nachfolgenden Kasten beschrieben wird.
Ist das Code oder kann das weg?
In einem meiner ersten Projekte arbeitete ich mit dem Lead-Developer an einer JavaApplikation. Ein Teil des von ihm geschriebenen Codes erschien mir überkomplex und schwer verständlich, also überarbeitete ich den Quellcode mit dem Ziel, Überflüssiges rauszuwerfen und den Code besser an die konkrete Aufgabe anzupassen.
Am nächsten Tag zitierte er mich dann etwas säuerlich zu sich und fragte mich, warum ich die von ihm geschriebene State-Machine in Stücke gehauen hätte. Es stellte sich heraus, dass ich sie nicht als solche erkannt hatte und auch nicht wusste, dass er sie für konkrete Erweiterungen vorgesehen hatte. Zwar konnte man meine Änderungen durch das Versionskontrollsystem schnell rückgängig machen, aber es war kein schöner Moment, eingestehen zu müssen, dass ich den Code nicht richtig verstanden hatte.
Johannes
State-Machine
Ein Programmierparadigma, bei dem bestimmte Zustände und die Übergänge zwischen ihnen besonders wichtig sind - beispielsweise, um zu garantieren, dass eine Finanztransaktion entweder erfolgreich abgewickelt oder im Fehlerfall vollständig rückgängig gemacht wird.
Das war ein sehr kurzer Überblick über ein Thema, das anderswo ganze Bücher füllt. Wenn Sie nach der Lektüre eine Vorstellung davon haben, wann Refactoring eine gute Idee sein und wie es ungefähr aussehen könnte, hat das Kapitel seinen Zweck erfüllt.
Falls Sie es etwas genauer wissen wollen, empfehlen wir die Lektüre der sehr laienfreundlichen Refactoring-Tipps in »The Art of Readable Code« von Dustin Boswell und Trevor Foucher. Auch Steve McConnells »Code Complete« enthält sehr ausführliche und verständliche Beispiele. Wer gründlich in das Thema einsteigen möchte, dem sei Martin
Fowlers >>Refactoring: Improving the Design of Existing Code<< (Addison Wesley Professional 1999) ans Herz gelegt. Das Buch wendet sich an Fortgeschrittene und enthält etwas anspruchsvollere Konzepte, in weiten Teilen sind Fowlers Anleitungen aber auch für Anfänger gut nachvollziehbar. Unter martinfowler.com/refactoring/catalog/ kann man eine Kurzfassung aller Verbesserungstechniken aus seinem Buch nachlesen.
Kathrin: >>Was ist zu tun, wenn man schlauer geworden ist und den Horror der früheren Taten sieht, aber das zu Ändernde ganz tief in den Fundamenten des Codes sitzt? Die Nummerierung der Riesenmaschine-Beiträge ist ein Timestamp, der sich so lange immer wieder ändert, wie der Beitrag intern bearbeitet wird, und erst bei der Freischaltung dauerhaft festgelegt wird. Diese Flüchtigkeit der internen Beitragsnummern bringt zahlreiche Ärgernisse mit sich. Wie hätte ein weniger unbedarfter Programmierer das Problem von vornherein besser gelöst? Und wie funktioniert Refactoring, wenn man dafür einen der untersten Bausteine austauschen müsste?«
Johannes: >>Das zugrunde liegende Problem ist, dass die IDs zu viel wollen, einerseits Timestamp des letzten Edits sein, andererseits Unique-ID und drittens noch so etwas wie Session-Tracking: Welche Beiträge werden gerade editiert, sollten also mit einer Warnung versehen sein, wenn jemand anderer sie editieren will? Diese Mehrfachfunktion verletzt das Prinzip, dass eine Variable nur für eine Aufgabe zuständig sein soll. Gerade bei IDs wird gerne übersehen, dass sie als unverwechselbare, eindeutige Bezeichner eine wichtige Funktion haben. Sie laden offenbar dazu ein, zu denken: >Ach, das ist nur ein String, da kann ich gleich was Sinnvolles reintun.< IDs mit Zweitbedeutung (Timestamps oder E-Mail-Adressen) sind aber ein häufiger Fehler, der immer wieder für Ärger sorgt.«
Diese Überladung der Variable würde man auflösen, indem man als Unique-ID bei der Anlage des Beitrags eine große Zufallszahl berechnet, die man nie mehr verändert. Andere Funktionalität würde man in neue Felder im XML abspalten: timeLastEdited und lastEditor zum Beispiel. Diese Felder könnte man jetzt jederzeit updaten, ohne dass sich die Unique-ID verändert.
Häufig kann man das Problem nicht in einem Durchgang lösen, sondern muss es in Teile zerlegen, um die man sich der Reihe nach kümmert. Man schreibt sogenannte Bottleneck-Funktionen, das sind Funktionen, die das Monopol darauf haben, bestimmte Daten zu verwalten. Im obigen Fall gäbe es also beispielsweise eine getPostingID(), eine setPostingID() und eine updatePostingID()-Funktion.
Diese Funktionen würden weiterhin zunächst die unglücklich gewählte Art der Beitragsnummerierung fortführen, aber zentralisiert - nirgends sonst im Programm würden IDs berechnet oder geändert. Da sich an der Logik der Nummerierung nichts ändert, ist dieser Umbau normalerweise Schritt für Schritt machbar, ohne dass das darüberliegende Kartenhaus zusammenfällt: Da, wo bisher IDs geändert wurden, wird die Bottleneck-
Ein Problem undseine Lösung | 211
Funktion updatePostingID() aufgerufen. Da in der Funktion die gleiche Logik wie bisher steckt, können andere Codebereiche, die noch nicht überarbeitet sind, weiterhin auf eigene Faust mit den IDs hantieren. Analog ersetzt man die Stellen, an denen Beiträge sortiert werden, durch Aufrufe einer sortArticle()-Funktion.
Wenn man das geschafft und ausgiebig getestet hat, ist ein großer Teil der zu hütenden Flöhe im Sack - jetzt kann man sich daran machen, eine Downtime des Systems vorzunehmen, damit man in Ruhe die Logik der ID-Berechnung verändern kann. Das geschieht dann nur noch zentral in den wenigen Bottleneck-Funktionen.
Am Ende gibt es allerdings noch ein Datenmigrationsproblem: Die alte Art der Nummerierung der Beiträge passt nicht zur neuen, denn die alten lDs bestanden aus Timestamps - die sind zwar nicht fortlaufend, aber doch immer weiter aufsteigende Zahlen. Die neuen IDs sind hingegen Zufallszahlen. Während man Beiträge nach den alten IDs sortieren konnte, ist das mit den neuen nicht mehr möglich. Man muss also die beiden Bedeutungen der bisherigen IDs, nämlich Unique-ID und Sortierhilfe, in zwei Felder (ID und timeLastEdited) im Dateiformat auftrennen. Damit sind das alte und das neue Dateiformat leider nicht mehr kompatibel.
Damit man im Code nicht zwei Formate behandeln muss, wäre es sinnvoll, ein kleines Programm zu schreiben, das die alten Beiträge auf das neue Format umstellt: Es nimmt die alten Beitragsdateien, liest den Wert der alten ID und schreibt ihn in timeLastEdited und in die neue ID. Dieses Programm lässt man einmal durchlaufen, danach kann man die alten Beiträge editieren, ohne dass sich ihre IDjemals wieder ändert - das timeLastEdited-Feld hingegen wird sich bei jedem Editieren eines Beitrags ändern.
Vor dem Refakturieren und dem Datenupdate würde der Datenbestand so aussehen (ohne die XML-Tags):
Eintrag 1236739535 content blafasel
Nach dem Refakturieren und dem Datenupdate würde der Datenbestand so aussehen (wieder ohne die XML-Tags) wie unten.
Alter Eintrag:
Eintrag 1236739535
lastTimeEdited 1236743135
lastEditor null (weil es dieses Feld im alten Format nicht gab) content blafasel
Ein im neuen Format angelegter neuer Eintrag:
Eintrag 23459030940243059283489029435 lastTimeEdited 1486587262 lastEditor Johannes content blafasel