9.2 Refactoring
Refactoring bezeichnet die Verbesserung der Codequalität eines Programms unter Beibehaltung seiner bestehenden Funktionalität. Diese Verbesserung sollte am besten kontinuierlich erfolgen. Refactorings sollen also die Verständlichkeit und die Wartbarkeit des Programmcodes erhöhen, wobei das Verhalten des Programms (nach außen) unverändert bleibt. Ralph Johnson und William Opdyke prägten den Begriff ursprünglich, den Martin Fowler später durch sein Buch [Martin Fowler: Refactoring: Improving the Design of Existing Code, Bonn: Addison-Wesley 2000.] und die Liste der Refactorings [http://www.tutego.de/java/refactoring/catalog/] einem größeren Entwicklerkreis bekannt gemacht hat.
Diese Liste beschreibt verschiedene Verfahren, um einen Sourcecode zu modifizieren, ohne dessen Verhalten zu verändern. Obwohl jedes Refactoring atomar ist und sich deshalb nicht in kleinere Refactorings zerlegen lässt, ist die Komplexität der einzelnen Refactorings sehr unterschiedlich. Sie reicht von einfachen Operationen wie Benenne Methode um bis zu sehr komplexen wie Ersetze Fallunterscheidungen durch Subtyping.
9.2.1 Refactorings in Xcode

Viele Refactorings lassen sich recht gut automatisiert umsetzen, und so verfügt Xcode über sechs automatische Refactorings, die Sie über das Kontextmenü Refactor im Editor aufrufen können (siehe Abbildung 9.25).
Spezielle Refactorings für Objective-C
Das siebte Refactoring haben Sie bereits am Ende von Kapitel 2, »Grundlagen«, kennengelernt. Es stellt ein Projekt vom manuellen auf automatisches Referenzenzählen um. Da es sehr Objective-C-spezifisch ist, taucht es allerdings nicht in Martin Fowlers Liste auf. Sie finden es in Xcode unter Edit • Refactor • Convert to Objective-C ARC...
Xcode 4.5 stellt außerdem noch ein weiteres Refactoring speziell für Objective-C bereit: Über das Menu Edit • Refactor • Convert to Modern Objective-C Syntax... können Sie Xcode veranlassen, Ihren Code syntaktisch aufzupolieren. Dieses Refactoring ersetzt beispielsweise Convenience-Konstruktor-Aufrufe durch Boxing-Ausdrücke und Elementzugriffe in Arrays und Dictionarys durch indizierte Zugriffe mit eckigen Klammern.
Über Rename... können Sie Klassen, Methoden oder auch beliebige Variablen umbenennen. Dabei ändert Xcode jedoch nicht nur den Text, auf dem Sie das Refactoring aufgerufen haben, sondern auch alle anderen Stellen, an denen der Bezeichner auftritt. Allerdings erkennt Xcode dabei keine Bezeichner in Selektoren oder Zeichenketten.
Wenn Sie beispielsweise in dem Code aus Listing 9.1 über Rename... den Namen der Klasse von User in Person ändern möchten, dann zeigt Ihnen Xcode den Dialog aus Abbildung 9.26. Über das Häkchen bei Rename related files können Sie mit der Klasse auch deren Dateien umbenennen lassen – sodass beispielsweise User.h in Person.h umbenannt wird.
@implementation User
@synthesize firstName;
@synthesize lastName;
- (id)newUser {
Class theClass = NSClassFromString(@"User");
id theUser = [[theClass alloc] init];
return theUser;
}
- (NSString *)fullName {
return [NSString stringWithFormat:@"%@ %@",
self.firstName, self.lastName];
}
- (NSString *)description {
return [self fullName];
}
- (void)logFullName {
SEL theSelector = @selector(fullName);
NSLog(@"user = %@", [self performSelector:theSelector]);
}
@end
Listing 9.1 Beispielcode für Refactoring
Abbildung 9.25 Ausführen von Refactorings im Editor
Durch Drücken des Preview-Buttons gelangen Sie zu einem Dialog, mit dem Sie die Änderungen des Refactorings überprüfen können. Dies funktioniert analog zu dem Dialog, mit dem Sie einen Snapshot mit dem aktuellen Projektstand vergleichen (siehe Abschnitt 9.1.9), und erst durch Betätigen des Save-Buttons führen Sie das Refactoring aus.
Abbildung 9.26 Umbenennen einer Klasse
Bei dieser Umbenennung erkennt Xcode den Bezeichner User im Interface- und Im-plementation-Block sowie an allen Stellen, an denen Sie den Namen direkt verwenden (z. B. [[User alloc] init] oder [User class]). Xcode passt den Klassennamen in einer Zeichenkette – wie in der Methode newUser – allerdings nicht an, sodass dort auch nach dem Refactoring NSClassFromString(@"User") und nicht NSClassFromString(@"Person") steht.
Analoges gilt für das Umbenennen von Methoden. Wenn Sie über das Refactoring die Methode fullName in name umtaufen, passt Xcode den Aufruf in der Methode description an, jedoch nicht die Selektorerzeugung in der Methode logFullName.
Refactoring und Snapshots
Ein Refactoring kann sehr viele Dateien auf einmal verändern. Durch den unvorsichtigen Einsatz des Refactorings können Sie sehr schnell Ihren Code unbrauchbar machen. Sie sollten deswegen nach jedem Refactoring überprüfen, ob Sie auch das gewünschte Ergebnis erhalten haben. Wenn Sie außerdem automatische Snapshots einschalten, speichert Xcode den aktuellen Projektstand, bevor es den Code verändert.
Durch das Umbenennen von Variablen, Methoden und Klassen können Sie Ihren Code verständlicher machen, indem Sie sprechende Namen für Ihre Bezeichner wählen. Die anderen Refactorings, die Xcode zur Verfügung stellt, verändern hingegen die Struktur, was Ihnen beim Aufräumen Ihres Codes helfen kann.
9.2.2 Methoden auslagern

Neben der Liste der Refactorings enthält Fowlers Buch auch eine Liste von Code- Smells – also von übel riechendem oder genauer gesagt stinkendem Code. Das sind Merkmale oder Eigenschaften des Programmcodes, die ein Refactoring nahelegen und manchmal sogar danach schreien. Zwei typische Stinker sind lange Methoden und doppelter Code.
Je mehr Anweisungen eine Methode enthält, umso schwieriger ist es, ihre Funktionsweise zu verstehen. Dabei gibt es indes keine feste Obergrenze, wie viele Zeilen eine Methode haben darf. Je kürzer Ihre Methoden sind, umso besser ist es. Doppelter Code tritt häufig zusammen mit langen Methoden auf. Der Programmcode enthält dann gleiche oder sehr ähnliche Codefragmente mehrmals. Das Refactoring Methode auslagern, das Sie unter Extract... im Kontextmenü finden, ist in vielen Fällen eine gute Möglichkeit zum Entlüften.
Die Methode drawClockHands in der Klasse ClockView.m des Beispielprojekts AlarmClock enthält dreimal die identischen Zeilen:
CGContextMoveToPoint(theContext, theCenter.x, theCenter.y);
CGContextAddLineToPoint(theContext, thePoint.x, thePoint.y);
CGContextStrokePath(theContext);
Listing 9.2 Doppelter Code in drawClockHands
Selektieren Sie diese drei Zeilen, und rufen Sie über das Kontextmenü Refactor • Extract... auf. Xcode öffnet daraufhin das Fenster aus Abbildung 9.27. Xcode schlägt Ihnen als Methodensignatur extracted_method:theContext:thePoint: vor. Neben dem nichtssagenden Namen ist auch die Parameterreihenfolge optimierungsbedürftig. Ändern Sie also den Text im Eingabefeld wie folgt:
- (void)drawLineFromPoint:(CGPoint)inCenter
toPoint:(CGPoint)inPoint withContext:(CGContextRef)inContext
Listing 9.3 Die neue Methodensignatur
Abbildung 9.27 Extrahieren einer Methode
Drücken Sie den Preview-Button. Xcode zeigt Ihnen den bekannten Dialog an, mit dem Sie das Ergebnis des Refactorings vor dessen Ausführung begutachten können. Durch dieses Refactoring fügt Xcode die neue Methode vor der Methode drawClockHands ein.
- (void)drawLineFromPoint:(CGPoint)inCenter
toPoint:(CGPoint)inPoint
withContext:(CGContextRef)inContext {
CGContextMoveToPoint(inContext, inCenter.x, inCenter.y);
CGContextAddLineToPoint(inContext, inPoint.x, inPoint.y);
CGContextStrokePath(inContext);
}
Listing 9.4 Diese neue Methode erhalten Sie durch das Refactoring.
Sie können jetzt auch die beiden anderen Stellen in drawClockHands durch einen Aufruf der neuen Methode ersetzen. In Listing 9.4 finden Sie einen Ausschnitt aus der überarbeiteten Methode, der alle Änderungen enthält.
CGContextSetRGBStrokeColor(theContext, 0.25, 0.25, 0.25, 1.0);
CGContextSetLineWidth(theContext, 7.0);
CGContextSetLineCap(theContext, kCGLineCapButt);
[self drawLineFromPoint:theCenter toPoint:thePoint
withContext:theContext];
thePoint = [self pointWithRadius:theRadius * 0.9
angle:theMinute];
CGContextSetLineWidth(theContext, 5.0);
[self drawLineFromPoint:theCenter toPoint:thePoint
withContext:theContext];
thePoint = [self pointWithRadius:theRadius * 0.95
angle:theSecond];
CGContextSetLineWidth(theContext, 3.0);
CGContextSetRGBStrokeColor(theContext, 1.0, 0.0, 0.0, 1.0);
[self drawLineFromPoint:theCenter toPoint:thePoint
withContext:theContext];
Listing 9.5 Eliminierung des doppelten Codes
Refactoring und kein Ende
Die Methode drawClockHands ist jedoch auch nach diesem Refactoring relativ lang. Als nächsten Schritt böte es sich an, die Berechnung des Mittelpunkts in die neue Methode zu verschieben, wobei dann auch der erste Parameter wegfallen könnte. Außerdem können Sie auch den Grafikkontext innerhalb dieser Methode bestimmen, sodass auch dieser Parameter aus der Signatur entfällt. .
Stattdessen ließe sich das Setzen der Linienbreite und die Berechnung des Zielpunktes in die ausgelagerte Methode verschieben. Die ausgelagerte Methode würde dadurch nur unwesentlich wachsen, während drawClockHands dafür erheblich schrumpfte. Natürlich sollten Sie den Namen dieser geänderten Methode auch anpassen. Er könnte beispielsweise drawLineFromCenterWithRadius:angle:lineWidth: lauten.
Für einige Refactorings in diesem Kasten (z. B. Methodenparameter entfernen bzw. hinzufügen) gibt es leider keine Unterstützung in Xcode. Hier müssen Sie also alles selber machen.
Den entsprechend überarbeiteten Code finden Sie auf der DVD unter Code/Apps/RefactoredAlarmClock oder im Github-Repository zum Buch im Unterverzeichnis:
https://github.com/cocoaneheads/iPhone/tree/Auflage_2/Apps/RefactoredAlarmClock
9.2.3 Oberklassen erzeugen und Methoden verschieben

Wie Sie im vorigen Abschnitt gesehen haben, können Sie durch die Auslagerung von Methoden nicht nur Ihren Code vereinfachen, sondern auch dessen Wiederverwendungsgrad erhöhen. Der überarbeitete Code ruft die ausgelagerte Methode schließlich von drei unterschiedlichen Stellen aus auf. Das klappt zwar innerhalb einer Klasse, aber wie lässt sich doppelter Code eliminieren, wenn er sich auf unterschiedliche Klassen verteilt?
Wenn die Klassen eine gemeinsame Oberklasse haben, die Sie verändern können, dann könnten Sie in dieser Oberklasse eine Methode mit dem entsprechenden Code implementieren und die Codeduplikate durch den Aufruf der neuen Methode ersetzen. Abbildung 9.28 stellt dieses Vorgehen schematisch dar. In der linken Klassenhierarchie existieren mehrere Stellen mit dem gleichen oder einem ähnlichen Code – in der Abbildung als Code D bezeichnet. Auf der rechten Seite ist dieser Code in die Methode codeD der gemeinsamen Oberklasse A ausgelagert worden, und die Methoden der Unterklassen rufen jetzt diese Methode auf.
Abbildung 9.28 Auslagerung von doppeltem Code in eine Oberklasse
In vielen Fällen haben Ihre Klassen indes keine gemeinsame, veränderbare Oberklasse. Wenn die Klassen wenigstens eine gemeinsame Oberklasse – also eine Systemklasse – haben, dann können Sie eine neue Klasse anlegen, die Sie als gemeinsame Oberklasse verwenden. Diese Situation tritt häufig bei Viewcontrollern auf. Beispielsweise hat das Beispielprojekt Games die Klassen PuzzleViewController und MemoryViewController, die beide auch gemeinsame Funktionalitäten brauchen. Diese Gemeinsamkeiten beherbergt die gemeinsame Oberklasse GameViewController.
Xcode unterstützt diese Umbauarbeiten mit zwei Refactorings. Über Create Superclass... können Sie eine neue Oberklasse zu einer bestehenden Klasse anlegen, und über Move Up... verschieben Sie eine Methode in deren Oberklasse. Eine Erweiterung des Weckers soll diese beiden Refactorings in der Praxis veranschaulichen. Angenommen, Sie möchten den Wecker um eine alternative Anzeige – beispielsweise ein Digitaldisplay – erweitern. Das Programm braucht dazu eine neue Viewklasse für die Digitalanzeige.
Natürlich können Sie jetzt wieder bei null anfangen, eine neue Klasse DigitalClockView anlegen und die benötigten Methoden implementieren. Diese neue Klasse braucht jedoch auch einige Eigenschaften, die die Klasse ClockView bereits besitzt. Sie muss die Uhrzeit sowie den Kalender speichern und sollte auch die Animation starten und stoppen können.
Um die Oberklasse anzulegen, selektieren Sie den Klassennamen ClockView in der entsprechenden Headerdatei und rufen über das Kontextmenü Refactor • Create Superclass... auf. Es erscheint der Dialog aus Abbildung 9.29. Den Namen der neuen Oberklasse geben Sie in das Textfeld ein. Über die Radiobuttons können Sie auswählen, ob Xcode die Deklaration und die Implementierung in eigenen Dateien oder in den Dateien ClockView.h beziehungsweise ClockView.m unterbringen soll. Nach dem Refactoring enthält das Projekt die beiden neuen Dateien AbstractClockView.h und AbstractClockView.m.
Abbildung 9.29 Anlegen einer neuen Oberklasse
Import-Anweisungen
Xcode hat noch einige Probleme mit den #import-Anweisungen. Nach dem Refactoring finden Sie in der Headerdatei AbstractClockView.h die Anweisung #import "UIView.h". Das ist allerdings nicht richtig, und Sie müssen diese Anweisung durch #import <UIKit/UIKit.h> ersetzen; Xcode macht Sie jedoch auf diesen Fehler mit einer Warnung aufmerksam.
In älteren Xcode-Versionen erhalten Sie im Preview-Dialog noch eine weitere Warnung, dass Sie den Import für die Datei AbstractClockView.h überprüfen sollen. Dieser Import ist jedoch korrekt.
In diese neue Oberklasse können Sie nun die gemeinsam genutzten Propertys und Methoden schieben. Leider kann Xcode nur Methoden, jedoch keine Propertys per Refactoring verschieben. Sie müssen also die Property-Deklarationen manuell aus der Klassendeklaration ClockView in die Oberklasse verschieben. Die anonyme Kategorie ClockView() verschieben Sie in die Datei AbstractClockView.m und benennen sie in AbstractClockView() um. Die @synthesize-Anweisungen verschieben Sie ebenfalls dorthin. Damit haben Sie die manuellen Vorbereitungsschritte abgeschlossen und können nun das Refactoring von Xcode verwenden.
Selektieren Sie die Methode startAnimation in ClockView.h, und wählen Sie Refactor • Move Up... über das Kontextmenü aus. Es erscheint der Dialog aus Abbildung 9.30. Nach diesem Refactoring befindet sich die Methode startAnimation in der Klasse AbstractClockView. Verfahren Sie genauso mit der Methode stopAnimation.
Abbildung 9.30 Verschieben einer Methode
Xcode zeigt Ihnen in der Datei ClockView.m einige Fehler an. Sie rühren daher, dass die Property calendar nur in der Klasse AbstractClockView schreibbar ist. Sie können diese Fehler jedoch leicht dadurch beheben, dass Sie die drei Methoden initWithFrame:, dealloc und awakeFromNib ebenfalls in die Oberklasse verschieben. Nach dem Verschieben dieser drei Methoden sollte sich Ihr Programm wieder übersetzen und ausführen lassen. Diese neue Oberklasse kann nun als Basis für weitere Anzeigeklassen dienen.
9.2.4 Attribute kapseln und verschieben
Xcode bietet auch zwei Refactorings für Attribute an, die allerdings nicht auf Propertys anwendbar sind. In diesem Buch wurde weitestgehend auf die Verwendung von Attributdeklarationen verzichtet, da sie einerseits Implementationsdetails der Klasse verraten, wenn sie sich im Header befinden, und andererseits durch synthetisierte Propertys obsolet geworden sind.
Sofern Sie doch Attribute verwenden sollten, können Sie über ein Refactoring automatisch die Accessoren dafür erzeugen lassen. Dies ersetzt außerdem jeden Attributzugriff im Programm durch den entsprechenden Accessoraufruf. Sie rufen das Refactoring auf, indem Sie auf einem Attribut im Kontextmenü den Punkt Refactor • Encapsulate... aufrufen. Xcode zeigt daraufhin das in Abbildung 9.31 dargestellte Fenster an, in dem Sie den Namen für den Getter und den Setter festlegen können. Falls Sie Code mit Attributen haben, können Sie dieses Refactoring dazu nutzen, die Deklarationen aus den Headerdateien zu entfernen.
In der Voransicht können Sie sehen, dass Xcode die Deklarationen für die Accessoren in die Headerdatei einfügen wird und die Definitionen ans Ende der Implementierung der Klasse. Außerdem ersetzt die IDE alle Attributzugriffe (beispielsweise ClockView *theView = clockView; oder clockView = theView;) durch die entsprechenden Accessoraufrufe (also theView = [self clockView]; beziehungsweise [self setClockView:theView];).
Abbildung 9.31 Kapseln eines Attributs
Das letzte Refactoring in diesem Abschnitt erlaubt das Verschieben von Attributen aus einer Klasse in deren Unterklassen. Sie rufen es über den Punkt Refactor • Move Down... im Kontextmenü auf. Es kopiert die Attributdeklaration aus der Oberklasse in alle Unterklassen und löscht sie aus der Oberklasse. Die Auswirkungen dieses Refactorings sind also sehr begrenzt, und auch die Situationen, in denen Sie es einsetzen können, sind eher selten.
Ihr Kommentar
Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.











Jetzt bestellen





