Seminar Softwareentwicklung (Programmierstil), WS 2002
Johannes Kepler Universität Linz, System Software Group
Betreuer: Prof. Hanspeter Mössenböck
Autor: Klemens Stelzmüller, klemens.stelzmueller@gmx.at
Diese Seminararbeit bietet eine Einführung in das Thema Refactoring. Im ersten Teil werden die nötigen Begriffe definiert, Gründe für den Einsatz des Refactorings aufgezeigt, die Metapher der „Bad Smells“ erklärt und dargestellt, wie man beim Refaktorisieren vorgeht. Der zweite Teil zeigt anhand eines kleinen Beispiels einige ausgewählte Bad Smells und den Vorgang des Refactorings. Martin Fowlers Katalog von Refaktorisierungen wird im dritten Teil vorgestellt. Am Ende dieser Seminararbeit finden sich jene Probleme, die Refactoring in der Praxis aufwirft.
“Refactoring
has become a central part of my development process, and of the process I teach
through my seminars and consulting work. It’s a design technique that is a
great complement to the up-front design techniques advocated by the UML and
various methods.
Its great
strength is that it works on existing software. […] Refactoring provides a way
to manipulate and improve the code base without getting trapped in bugs.”
Mit diesen Worten beschreibt Martin Fowler [Fowl98], der Autor des Standardwerks zum Thema Refactoring [Fowl99, Fowl00], den Wert dieser Methode: Refactoring stellt ein strukturiertes Vorgehen zur Verfügung, mit dem das Design vorhandener Software schrittweise verbessert werden kann. Denn durch das Altern von Software, das Änderungen und Erweiterungen mit sich bringt, zerfällt mit der Zeit auch ihr Design, der Code wird unstrukturierter und schwerer verständlich, Fehler schleichen sich ein, und Erweiterungen werden immer komplizierter. Kurzum: Restrukturierung vorhandener Software ist regelmäßig notwendig.
Martin Fowler unterscheidet zwischen dem Substantiv „Refactoring“ und der Tätigkeit des „Refactoring“:
Refaktorisierung (Substantiv) [Fowl00]: „Eine Änderung an der internen Struktur einer Software, um sie leichter verständlich zu machen und einfacher zu verändern, ohne ihr beobachtbares Verhalten zu ändern.“
Refaktorisieren (Verb) [Fowl00]: „Eine Software umstrukturieren, ohne ihr beobachtbares Verhalten zu ändern, indem man eine Reihe von Refaktorisierungen anwendet.“
Aus der zweiten Definition ist ersichtlich, dass das Refaktorisieren eine iterative Tätigkeit ist. Während Sie einige Zeit refaktorisieren, wenden Sie vielleicht ein Duzend verschiedene Refaktorisierungen an, um das Design des bearbeiteten Codes zum Besseren zu verändern. Wichtig dabei ist, dass am beobachtbaren Verhalten keine Änderungen bemerkbar sind. Refactoring fügt weder neue Funktionen zu einer Software hinzu, noch werden nicht gebrauchte Funktionen entfernt. Es wird lediglich die innere Struktur aufgeräumt.
In diesem Zusammenhang ist auch Kent Becks Metapher der zwei Hüte zu nennen: Während der Software-Entwicklung teilt man sich die Zeit zwischen zwei Aktivitäten auf – Funktionalität hinzufügen und refaktorisieren. Solange neues Verhalten hinzugefügt wird, darf vorhandener Code nicht verändert werden. Wird die Software refaktorisiert, so darf keine neue Funktionalität eingebaut werden.
Refactoring ist leider kein Allheilmittel für Probleme in der Software-Entwicklung. Es ist aber ein nützliches Instrument, bestehenden Code „jung“ zu halten und ihn vor lästigen „Altersbeschwerden“ zu schützen. Martin Fowler identifiziert vier wichtige Gründe, warum Code regelmäßig refaktorisiert werden soll [Fowl00]:
1. Verbesserung und Pflege des Designs:
Das Design bestehender Software leidet mit der Zeit, wie eingangs bereits erwähnt wurde, an Zerfallserscheinungen. Refactoring hilft dabei, Unordnungen zu entwirren und aufzuräumen; die Dinge werden an den richtigen Platz gebracht; die Menge des Codes kann meist reduziert werden. Die Software und ihr Design können also in Form gehalten werden.
2. Software wird verständlicher:
Allein durch ein klares Design wird einem Leser des Quelltextes die Funktion bestehender Software leichter erkennbar. Zusätzlich dazu wird durch das ständige Aufräumen und Umformulieren kleiner Portionen der Code so formuliert, dass nicht nur der Compiler, sondern auch wir Menschen schnell verstehen, was in bestimmten Abschnitten geschehen soll.
3. Fehler werden gefunden:
Durch das intensive Auseinandersetzen mit dem bestehenden Code vergrößert man auch das Verständnis dessen, was geschieht. Somit wird auch schnell klar, an welchen Stellen nicht das passiert, was eigentlich erledigt werden soll.
4. Hilfe, um schneller zu programmieren:
„Ich bin fest davon überzeugt, dass ein gutes Design entscheidend für schnelle Softwareentwicklung ist,“ stellt Fowler in [Fowl00] fest. Refactoring hilft dabei, den Verfall des Designs zu stoppen, sie verständlicher zu machen und Fehler zu finden und zu korrigieren – also hilft es im Grunde auch dabei, schneller zu entwickeln.
„If it stinks, change it.“ Kent Beck, bekannt als Erfinder des Extreme Programming, zitierte so seine Großmutter, die über das Wicklen von Kleinkindern sprach: Wenn es zu stinken anfängt, muss es gewickelt werden.
Mit der Metapher schlechter Gerüche behelfen sich Kent Beck und Martin Fowler, jene Stellen im Code zu beschreiben, die refaktorisiert werden sollten. Überall dort, wo sich Gestank breit macht, schreit Software nach einer Verjüngungskur. Doch sie bleiben nicht bei „vager Ästhetik“ – ein ganzer Katalog übler Gerüche hilft dabei, Kandidaten für eine Restrukturierung zu finden.
Hier seien nun ein paar dieser „Bad Smells“ exemplarisch angeführt, die auch in späteren Kapiteln immer wieder erwähnt werden. Die möglichen Abhilfen, die ebenfalls weiter hinten benutzt werden, sind ebenfalls aufgeführt. Die restlichen üblen Gerüche möchte ich nur ohne deren Korrekturmöglichkeiten auflisten – interessierte Leser können ja in [Fowl99, Fowl00] nachschlagen.
Die gleiche Codestruktur an mehr als einer Stelle verbreitet den größten Gestank für Martin Fowler. Ein einfacher Fall liegt vor, wenn exakt der selbe Ausdruck an mehreren Stellen in einer Klasse vorkommt. Dann braucht nur dieser Ausdruck in eine neue Methode extrahiert und diese benutzt werden.
Ist die Struktur des Ausdrucks nur ähnlich, so kann eventuell eine abstrakte Methode in einer Oberklasse eingefügt werden, die dann in den Unterklassen mit dem angepassten Verhalten implementiert wird. So kann weit oben in der Vererbungshierarchie die Struktur eines Algorithmus bereits implementiert werden, ohne über detailliertes Wissen über die Einzelschritte verfügen zu müssen.
Mögliche Refaktorisierungen: Methode extrahieren, Template Methode bilden.
Lange Methoden neigen dazu, viele verschiedene Aufgaben zu erledigen. Solche Methoden machen nicht nur das, was ihr Name andeutet. Ein Leser dieser Methode vermutet anfangs nicht die gesamte enthaltene Funktionalität im Rumpf.
In diesem Fall sollte die lange Methode zerlegt werden in mehrere kleine Methoden, die alle entsprechend ihrer Aufgabe benannt werden sollen. So wird Wiederverwendung ermöglicht und die eigentliche Methode durch ihre Verkürzung leichter verständlich gemacht.
Mögliche Refaktorisierungen: Methode extrahieren, Temporäre Variable durch Abfrage ersetzen.
Eine Methode einer Klasse oder bestimmte Abschnitte dieser Methode interessieren sich mehr für Daten einer anderen Klasse als ihrer eigener. Die neidigen Teile können in eine eigene Methode extrahiert und anschließend in jene Klasse verschoben werden, wo die Daten liegen, für die sich die neue Methode interessiert.
Auf keinen Fall sollte aber geschehen, dass Dinge, die gemeinsam geändert werden müssen, auseinander gerissen werden. Das ist zum Beispiel der Fall, wenn Entwurfsmuster wie Strategie und Besucher verwendet werden. In solchen Fällen ist der Neid Teil des guten Designs und sondert daher keine üblen Gerüche ab.
Mögliche Refaktorisierungen: Methode extrahieren & Methode verschieben.
Eine temporäre Variable wird dazu benutzt, das Ergebnis eines Ausdrucks zu speichern bzw. kumulierte Daten im Laufe einer Schleife beispielsweise zu sammeln. Dieser Ausdruck bzw. diese Teile der Schleife sollte in eine Methode extrahiert und dann anstatt der temporären Variable eingesetzt werden.
Gestank verbreiten sie deshalb, weil sie das Schreiben langer Methoden fördern und durch ihre Lokalität innerhalb einer Methode eine Wiederverwendung unmöglich machen.
Mögliche Refaktorisierungen: Methode extrahieren & Temporäre Variable durch Abfrage ersetzen, Temporäre Variable zerlegen.
Alternative Klassen mit verschiedenen Schnittstellen |
Ausgeschlagenes Erbe |
Datenklassen |
Datenklumpen |
Divergierende Änderungen |
Duplizierter Code |
Faule Klasse |
Große Klasse |
Kommentare |
Lange Methode |
Lange Parameterliste |
Nachrichtenketten |
Neid |
Neigung zu elementaren Typen |
Parallele Vererbungshierarchien |
Schrotkugeln herausoperieren |
Spekulative Allgemeinheit |
Switch-Befehle |
Temporäre Felder |
Unangebrachte Intimität |
Unvollständige Bibliotheksklasse |
Vermittler |
Refactoring erfordert ein schrittweises, iteratives Vorgehen. Dort, wo der Code den größten Gestank absondert, dort, wo Fehler vermutet werden, und dort, wo das Hinzufügen neuer Funktionalität Probleme bereitet, soll Schritt für Schritt die Struktur verbessert werden, um den Gestank zu lindern, den Fehler zu lokalisieren und zu korrigieren und um das Hinzufügen neuer Funktionalität zu ermöglichen.
Dabei wird folgendermaßen vorgegangen: Zuerst wird der Code laut Anleitung der gewählten Refaktorisierung geändert. Diese Änderung wird sofort getestet, um eine Änderung des äußeren Verhaltens auszuschließen. Hat diese Änderung das Verhalten nicht beeinflusst und ist auch die gewünschte Verbesserung am Design eingetreten, kann mit der nächsten Runde fortgefahren werden. Wurde ein Fehler entdeckt oder stimmt das Ergebnis der Design-Änderung nicht mit den Erwartungen überein, so geht man einfach einen Schritt zurück und beginnt von neuem – eventuell auch mit einer anderen Refaktorisierung.
Beim Refactoring spielt also das Testen nach jedem Schritt eine wichtige Rolle. Nur wenn man über automatisierte Regressions- bzw. Komponententests verfügt, kann man nach jedem Schritt das Beibehalten des beobachtbaren Verhaltens prüfen. Martin Fowler rät in [Fowl99, Fowl00] zum Einsatz eines Test-Frameworks wie der xUnit-Familie [OMentor, SForge, Newk02].
Um ein Gefühl für Refactoring zu entwickeln, möchte ich nun ein kleines Beispiel durchgehen, dass ich aus [Fowl00] entnommen und nach C# übersetzt habe. Anhand dieses Beispiels werde ich die oben genannten Bad Smells aufzeigen und darstellen, wie man mit ausgewählten Refaktorisierungen schrittweise zu einer besseren Lösung für das gegebene Problem kommt.
Unser kleines Programm ermöglicht es einem Videoverleih, Rechnungen für seine Kunden zu drucken. Dabei werden die ausgeliehenen Filme nach drei Kategorien (Regulär, Neuerscheinung, Kinderfilm) unterschieden. Der Preis gestaltet sich je nach Kategorie und Dauer der Ausleihe. Zusätzlich dazu werden dem Kunden Bonuspunkte gut geschrieben, die sich ebenfalls aus Kategorie des Films und Dauer der Ausleihe berechnen. Das Klassendiagramm veranschaulicht den Zusammenhang der drei Klassen Customer, Rental und Movie.
Problematisch an diesem Programm ist das Property Statement der Klasse Customer. Bei genauerer Betrachtung zeigt sich, dass die Methode alle nötigen Daten für die Rechnung selbst berechnet. Möchte man nun dieses Beispiel um eine Rechnung im HTML-Format erweitern, so kann man die Berechnung dieser Daten nicht wiederverwenden. Daher werden wir durch einige Runden Refactoring dafür sorgen, dass diese Erweiterung problemlos eingebaut werden kann.
Bevor wir jedoch irgend eine Änderung am vorhandenen Code durchführen, müssen wir sicherstellen, dass wir über eine genügend große Menge von Testfällen verfügen. Nur mit automatisierten Tests können wir sicherstellen, dass keine Änderung am beobachtbaren Verhalten der Software eintritt.
Um den Geruch des langen Properties zu lindern und um den Gestank des duplizierten Codes zu verhindern, der duch dein Einbau einer HTML-Rechnung entstünde, möchten wir die Berechnung der Ausleihe-Beträge in einer Methode zur Verfügung stellen, sodass sie auch von HTMLStatement verwendet werden kann.
Daher wenden wir Methode extrahieren auf das Property Statement an, um den Switch-Block in eine neue Methode AmountFor zu extrahieren. Bei dieser Extraktion muss darauf geachtet werden, dass die Abhängigkeiten, die der extrahierte Block mit dem Rest des Methodenrumpfes aufweist, erhalten bleiben. AmountFor erhält daher einen Eingangsparameter und liefert das Ergebnis der Berechnung als Funktionsergebnis.
In Statement müssen wir sodann die Benutzung von AmountFor einbauen. Das Klassendiagramm zeigt die neue Struktur, deren gleiches Verhalten wir sofort mit einem Testlauf kontrollieren.
Unsere neue Methode sondert einen neuen Geruch ab: Neid. AmountFor benutzt ausschließlich Daten der Klasse Rental. Daher verwenden wir Methode verschieben.
Als erstes kopieren wir dazu den Code nach Rental und geben ihm den sprechenderen Namen Charge. Da Charge keinen Parameter mehr braucht, können wir daraus sogar ein Property machen, das uns die Gebühr dieser Ausleihe ermittelt. Im nun leeren Rumpf von AmountFor richten wir eine Delegation auf dieses neue Property ein, um das Gleichbleiben des Verhaltens zu testen.
Erst danach entfernen wir AmountFor aus Customer und können die Benutzung von Charge in Statement einbauen. Dadurch wird auch die temporäre Variable thisAmount sinnlos. Mit Temporäre Variable durch Abfrage ersetzen können wir sie problemlos eliminieren. Auch nach dieser Änderung testen wir unser neues Design.
Natürlich wird auch die Berechnung der Bonuspunkte in der HTML-Rechnung Wiederverwendung finden wollen. Daher verfahren wir mit ihr analog wie mit der Berechnung der Ausleihe-Gebühr: Wir wenden Methode extrahieren an, um den Block mit den Berechnungen zu extrahieren. Da dieser Block so kurz ist, wenden wir sogleich Methode verschieben an und fügen den extrahierten Code gleich mit dem Namen FrequentRenterPoints in Rental ein. Denn auch die Berechnung der Bonuspunkte schaut Neid erfüllt auf diese Klasse.
In Statement müssen wir anschließend den extrahierten Block durch die Benutzung von FrequentRenterPoints ersetzen. Auch diese Evolutionsstufe des Designs will getestet werden.
Wenn wir Statement nun genauer „beschnüffeln“, so finden wir noch einige nicht gerade duftende Stellen: Die temporären Variablen totalAmount und frequentRenterPoints, die zum Sammlen der Daten in der Schleife eingesetzt werden, könnten wir noch mittels Temporäre Variable durch Abfrage ersetzen eliminieren.
Um totalAmount zu eliminieren muss bloß die Schleife aus Statement kopiert werden und darin ein lokaler Zähler erhöht werden. Ein sprechender Name für dieses Property, das wir in Customer ablegen können, ist TotalCharge, da es ja die gesamte Gebühr für alle Ausleihungen eines Kunden berechnet.
Bei der Variable frequentRenterPoints können wir analog vorgehen: Einfach die Schleife kopieren, darin einen lokalen Zähler erhöhen und das ganze in einem Property namens TotalFrequentRenterPoints ablegen.
In Statement können wir nun die beiden Variablen eliminieren und die beiden neuen Properties benützen. Ein kurzer Testlauf gibt uns die Sicherheit, dass wir auch jetzt keine Änderung am beobachtbaren Verhalten verursacht haben.
Wenn wir uns Statement nun anschauen, dann finden wir darin nur noch Code, der mit der Erstellung der Rechnung befasst ist. Alle Berechnungen haben wir, ohne deren Ergebnisse zu ändern, erfolgreich restrukturiert.
Nun ist es an der Zeit, den Refactoring-Hut abzunehmen und ihn gegen den anderen der beiden Hüte einzutauschen. Wir können nun die neue Funktionalität hinzufügen – und das ganz problemlos. HTMLStatement ist ebenso einfach gehalten wie Statement. Natürlich sollten wir dafür auch gleich einige Testfälle bauen, um spätere Änderungen unseres jetzigen Designs auf Beibehaltung des beobachtbaren Verhaltens hin untersuchen zu können. Das überlasse ich aber Ihnen!
Unser anfängliches Ziel, eine Rechnung im HTML-Format zu erstellen, haben wir erreicht. Daher können wir guten Gewissens stoppen und mit dem erreichten Design zufrieden sein. Gute Nasen werden aber noch an manchen Stellen üble Gerüche und oder gar Gestank wahrnehmen. In diesem Punkt muss man nun abwägen, ob man genug Zeit hat, die noch ausstehenden Refaktorisierungen gleich vorzunehmen, oder ob man sie auf später vertagt.
Ist uns morgen etwas im Wege, das heute als gut und billig empfunden wird, so können wir uns an Martin Fowler halten [Fowl00]: „Refaktorisieren gibt mir die Freiheit, meine Meinung zu ändern.“ Refactoring unterstützt uns dabei, Meinungen und Entscheidungen auf Basis neu erworbenen Wissens zu überdenken und die dafür notwendigen Maßnahmen mit geringem Aufwand umzusetzen.
Sie haben nun sicher ein Gefühl dafür entwickelt, wie man Refactoring anwendet. Doch das kleine Beispiel von vorhin hat nicht viel mit echten Softwaresystemen zu tun. Dort sind die Lösungen nicht so offensichtlich und auch das Vorgehen nicht so einfach. Außerdem werden Sie Gerüche wahrnehmen, die Sie zwar als Bad Smell identifizieren können, für die Sie aber nicht im Handumdrehen die geeignete Refaktorisierung erkennen können.
Darum hat Martin Fowler einen Katalog aller ihm bekannten Refaktorisierungen aufgestellt [Fowl99, Fowl00]. Auf [FowlWeb] findet sich sogar ein ständig gewarteter Katalog, dem ständig neue Refaktorisierungen hinzugefügt werden, und dessen Gliederung im Bedarfsfalle an die neuen Anforderungen angepasst wird. Darin beschreibt er neben dem Namen der Refaktorisierung, in welchen Fällen sie angewendet wird, und erläutert in ausführlichen Beispielen, wie man sie schrittweise einsetzt, um Fehler zu vermeiden.
Ich möchte hier nur einen groben Überblick über die in [Fowl99, Folw00] genannten Refaktorisierungen geben. Interessierte Leser lade ich ein, direkt im Web bei [FowlWeb] nachzulesen!
Refaktorisierungen zum Thema „Methoden zusammenstellen“ helfen, Code ordentlich zu strukturieren. Dazu gehören folgende Refaktorisierungen, von denen wir im obigen Beispiel bereits einige verwendet haben:
Methode extrahieren |
Methode integrieren |
Temporäre
Variable integrieren |
Temporäre Variable durch Abfrage ersetzen |
Erklärende Variable einführen |
Temporäre Variable zerlegen |
Zuweisung zu Parametern entfernen |
Methode durch Methodenobjekt ersetzen |
Algorithmus ersetzen |
|
Eigenschaften zwischen Objekten zu verschieben hilft dabei, Code an jene Stellen zu rücken, wo seine Verantwortlichkeiten liegen. Einen Vertreter dieser Refaktorisierungen haben wir ebenfalls bereits kennen gelernt:
Methode verschieben |
Feld verschieben |
Klasse extrahieren |
Klasse integrieren |
Delegation verbergen |
Vermittler entfernen |
Fremde Methode einführen |
Lokale Erweiterung einführen |
Der Umgang mit Daten ist ein heikles Thema. Wie streng soll ich Information Hiding betreiben? Wie sehr soll ich meine Daten kapseln? Fragen, auf die man heute so, morgen eventuell anders antworten mag. Folgende Refaktorisierungen helfen, Ihre Meinung leichter zu ändern:
Eigenes Feld kapseln |
Wert durch Objekt ersetzen |
Wert durch Referenz ersetzen |
Referenz durch Wert ersetzen |
Array durch Objekt ersetzen |
Beobachtete Werte duplizieren |
Gerichtete Assoziation durch bidirektionale ersetzen |
Bidirektionale Assoziation durch gerichtete ersetzen |
Magische Zahl durch symbolische Konstanten ersetzen |
Feld kapseln |
Collection kapseln |
Satz durch Datenklasse ersetzen |
Typenschlüssel durch Klasse ersetzen |
Typenschlüssel durch Unterklasse ersetzen |
Typenschlüssel durch Zustand/Strategie ersetzen |
Unterklasse durch Feld ersetzen |
Bool’sche Ausdrücke, die Bedingungen realisieren, haben für uns Menschen oft ihre Tücken. Je länger und je mehr Operatoren, desto unverständlicher werden sie für uns. Folgende Refaktorisierungen helfen dabei, die Probleme zu lindern:
Bedingung zerlegen |
Bedinge Ausdrücke konsolidieren |
Redundante Bedingungsteile konsolidieren |
Steuerungsvariable entfernen |
Geschachtelte Bedingungen durch Wächterbedingungen ersetzen |
Bedingten Ausdruck durch Polymorphismus ersetzen |
Null-Objekt einführen |
Zusicherung einführen |
Schnittstellen richtig entwerfen ist auch für geübte Framework-Entwickler schwer. Ein ganzer Satz Refaktorisierungen hilft dabei, passable Ansätze in gute Schnittstellen zu verwandeln:
Methode umbenennen |
Parameter ergänzen |
Parameter entfernen |
Abfrage von Veränderung trennen |
Methode parametrisieren |
Parameter durch eplizite Methode ersetzen |
Ganzes Objekt übergeben |
Parameter durch Methode ersetzen |
Parameterobjekt einführen |
Setter entfernen |
Methode verbergen |
Konstruktor durch Fabrikmethode ersetzen |
Downcast kapseln |
Fehlercode durch Ausnahme ersetzen |
Ausnahme durch Bedingung ersetzen |
|
Vererbung und Generalisierung von Anfang an richtig einzusetzen – und das mit ständig fließenden Anforderungen – ist nahezu unmöglich. Auch dafür gibt es Hilfestellung in Fowlers Katalog:
Feld nach oben verschieben |
Methode nach oben verschieben |
Konstruktorrumpf nach oben verschieben |
Methode nach unten verschieben |
Feld nach unten verschieben |
Unterklasse extrahieren |
Oberklasse extrahieren |
Schnittstelle extrahieren |
Hierarchie abflachen |
Template-Methode bilden |
Vererbung durch Delegation ersetzen |
Delegation durch Vererbung ersetzen |
Die bisher genannten Refaktorisierungen gleichen kleinen Zügen im gesamten „Spiel“ des Refactoring. Die großen Refaktorisierungen helfen dabei, das Design im großen, über mehrere Züge hinweg zu verbessern. Sie gleichen also einer Strategie, mit der die kleinen Züge hintereinander gespielt werden, um das Ziel zu erreichen:
Vererbungsstruktur entzerren |
Prozedurale Entwürfe in Objekte überführen |
Anwendung von der Präsentation trennen |
Hierarchie extrahieren |
Refactoring bringt viele Vorteile – sowohl kleine Verbesserungen, deren Nutzen sofort ersichtlich ist, als auch große Umstrukturierungen, deren Aufwand sich erst spät, dafür aber umso mehr amortisieren. Doch dieses Vorgehensmodell kann nicht immer angewendet und eingesetzt werden; es gibt auch Probleme und Grenzen, die ich hier aufzeigen möchte.
Refactoring ist kein Allheilmittel, wie eingangs bereits erwähnt wurde. Es gibt Fälle, wo es nur schwer bzw. gar nicht eingesetzt werden kann: Bei Informationssystemen, die eng mit Datenbanken gekoppelt sind, wird diese enge Verknüpfung Probleme bereiten. Änderungen am Code erfordern unter Umständen Änderungen am Datenmodell, wofür dann auch Migrationspfade ersonnen werden müssen. Refaktorisierung von bereits öffentlichen Schnittstellen ist ein weiteres Problem. Diese können nicht mehr geändert werden; daher werden oft einfach neue Schnittstellen eingeführt und ein Update-Vorgehen von den alten zu den neuen Schnittstellen vorgeschlagen (Beispiel: Collection-Klassen von Java zu Java 2).
Auch wenn Refactoring prinzipiell möglich ist, bereit die Auswahl zwischen verschiedenen, alternativen Refaktorisierungen Probleme. Ohne Erfahrung kann man sich schnell in eine Sackgasse refaktorisieren. Auch Abhängigkeiten über weite Teile großer Softwaresysteme machen Refactoring schwer; globale Auswirkungen einer Änderung sind oft lokal nicht absehbar und bereiten erst im Nachhinein Probleme.
Bedenken und Einwände kommen sicherlich auch seitens der Kunden bzw. der Projekt-Manager, wenn Refactoring erstmals als Teil des Entwicklungs-Prozesses eingeführt werden soll. Es wird ja nach neuer Funktionalität bezahlt und nicht nach der „inneren Ästhetik“ der Software. Der Nutzen, den ständige Wartung des Designs bringt, wird dabei oft übersehen.
Aber auch Softwareentwickler wehren sich gegen Refactoring, das anfangs als zusätzliche und manchmal auch unnütze Arbeit empfunden wird. Es ist nicht leicht, den späteren Nutzen einer größeren Refactoring-Sitzung abzuschätzen, schon gar nicht, wenn man noch keine Erfahrung damit gesammelt hat bzw. erst eine Test-Sammlung aufbauen muss.
Schließen möchte ich mit einem Zitat Martin Fowlers aus [Fowl00]: „Zusammengefasst ist es eine ganz andere Sache, Software-Profis vom praktischen Wert des Refaktorisierens zu überzeugen als einen Promotionsausschuss davon, dass Forschungen über das Refaktorisieren einen Doktortitel wert sind. Ich brauchte einige Zeit nach meinem Abschluss, um diese Unterschiede völlig zu verstehen.“
[Fowl98] |
Martin Fowler: After the Program Runs – Refactoring: Doing Design. http://www.martinfowler.com/articles/refactoring.pdf,
1998 |
[Fowl99] |
Martin Fowler: Refactoring – Improving the design of existing code.
Addison Wesley, 1999 |
[Fowl00] |
Martin Fowler: Refactoring –
Wie Sie das Design vorhandener Software verbessern. Addison Wesley, 2000 |
[Wake03] |
William C. Wake: Refactoring Workbook (Draft). http://xp123.com/rwb/RWB-draft4.doc,
2003 |
[FowlWeb] |
Martin Fowler: Refactoring Home Page. http://www.refactoring.com |
[OMentor] |
JUnit, Testing Resources for Extreme Programming. http://www.junit.org/index.htm,
2001, 2002 |
[SForge] |
|
[Newk02] |
James W. Newkirk, Alexei A. Vorontsov: NUnit. http://nunit.org/default.htm, 2002 |
[Stel03] |
Klemens
Stelzmüller: Blockbuster, Teile der Beispiel-Sourcen aus [Fowl99, Fowl00]
übersetzt nach C#, 2003 |