10.2Refactoring
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 [Anm.: Fowler, Martin: Refactoring: Improving the Design of Existing Code, Bonn: Addison-Wesley 2000.] und die Liste der Refactorings [Anm.: http://www.tutego.de/java/refactoring/catalog] einem größeren Entwicklerkreis bekannt machte.
Diese Liste beschreibt verschiedene Verfahren, einen Sourcecode zu modifizieren, ohne sein 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.
10.2.1Refactorings 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.
Zu den wahrscheinlich am häufigsten verwendeten Refactorings gehört das Umbenennen
von Bezeichnern. Xcode stellt für das Umbenennen von lokalen Variablen oder Parametern
den Menüpunkt Editor • Edit All in Scope zur Verfügung, den Sie auch über die Tastenkombination +
+
aufrufen können.
Abbildung 10.31 zeigt diese Funktion bei der Ausführung: Um einen Bezeichner damit umzubenennen,
setzen Sie den Textcursor darauf und rufen Editor • Edit All in Scope auf. Xcode stellt dann um den Bezeichner einen gestrichelten Rand dar ; außerdem hebt die IDE alle Fundstellen
des Bezeichners in seinem Gültigkeitsbereich hervor. Wenn Sie nun den umrandeten
Bezeichner ändern, ändert Xcode auch automatisch die Texte an den anderen Fundstellen
(siehe Abbildung 10.31).
Spezielle Refactorings für Objective-C
Das siebte Refactoring haben Sie bereits am Ende von Kapitel 2, »Die Reise nach iOS«, kennengelernt. Es stellt ein Projekt vom manuellen auf das automatische Referenzzä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 stellt außerdem ein Refactoring speziell für Objective-C bereit: Über das Menu Edit • Refactor • Convert to Modern Objective-C Syntax... veranlassen Sie Xcode, Ihren Code syntaktisch aufzupolieren. Dieses Refactoring ersetzt beispielsweise Convenience-Konstruktoraufrufe für NSNumber oder NSArray durch Boxing-Ausdrücke und Elementzugriffe in Arrays und Dictionarys durch indizierte Zugriffe mit eckigen Klammern.
Abbildung 10.31 Umbenennen über »Edit All in Scope«
Über das Menü Edit • Refactor • Rename... können Sie Klassen, Methoden und andere Bezeichner auch über mehrere Dateien hinweg umbenennen. Dabei ändert Xcode alle Stellen, an denen der Bezeichner auftritt. Allerdings erkennt Xcode dabei keine Bezeichner in @selector-Ausdrücken oder in Zeichenketten.
Wenn Sie beispielsweise in dem Code aus Listing 10.4 über das Rename-Refactoring den Namen der Klasse von User in Person ändern möchten, dann zeigt Ihnen Xcode den Dialog aus Abbildung 10.32. Über das Häkchen bei Rename related files können Sie mit der Klasse auch deren Dateien umbenennen lassen – so dass beispielsweise User.h in Person.h umbenannt wird.
@implementation User
- (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 10.4 Beispielcode für Refactoring
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, und erst durch Betätigen des Save-Buttons führen Sie das Refactoring aus.
Abbildung 10.32 Umbenennen einer Klasse
Bei dieser Umbenennung erkennt Xcode den Bezeichner User im Interface- und Implementation-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 beispielsweise in der Methode newUser, allerdings nicht an, so dass 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.
10.2.2Methoden auslagern

Neben der Liste der Refactorings enthält Fowlers Buch 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.
Projektinformation
Das Wecker-Projekt dient als Basis, um die verschiedenen Refactorings zu erläutern. Den überarbeiteten Code finden Sie auf der DVD unter Code/Apps/iOS7/RefactoredAlarmClock oder im Github-Repository zum Buch im Unterverzeichnis: https://github.com/cocoaneheads/iPhone/tree/Auflage_2/Apps/iOS7/RefactoredAlarmClock.
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 Code-Fragmente 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 gleichen Zeilen:
CGContextSetLineCap(theContext, kCGLineCapButt);
CGContextMoveToPoint(theContext, theCenter.x, theCenter.y);
CGContextAddLineToPoint(theContext, thePoint.x, thePoint.y);
CGContextStrokePath(theContext);
// Minutenzeiger zeichnen
thePoint = [self pointWithRadius:theRadius * 0.9
angle:theMinute];
CGContextSetLineWidth(theContext, theRadius / 40.0);
CGContextMoveToPoint(theContext, theCenter.x, theCenter.y);
CGContextAddLineToPoint(theContext, thePoint.x, thePoint.y);
CGContextStrokePath(theContext);
// Sekundenzeiger zeichnen
thePoint = [self pointWithRadius:theRadius * 0.95
angle:theSecond];
CGContextSetLineWidth(theContext, theRadius / 80.0);
CGContextSetRGBStrokeColor(theContext, 1.0, 0.0, 0.0, 1.0);
CGContextMoveToPoint(theContext, theCenter.x, theCenter.y);
CGContextAddLineToPoint(theContext, thePoint.x, thePoint.y);
CGContextStrokePath(theContext);
Listing 10.5 Doppelter Code in drawClockHands
Selektieren Sie einen dieser Drei-Zeilen-Blöcke, und rufen Sie über das Kontextmenü Refactor • Extract... auf. Xcode öffnet daraufhin das Fenster aus Abbildung 10.33. 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 10.6 Die neue Methodensignatur
Sie lagern bei dem Refactoring also nicht nur den Code aus, sondern ändern auch die vorgeschlagene Parameterreihenfolge (Grafikkontext und zweiter Punkt) und auch die Parameternamen (z. B. von theContext in inContext).
Abbildung 10.33 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. 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 10.7 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 10.7 finden Sie einen Ausschnitt aus der überarbeiteten Methode, der alle Änderungen enthält.
CGContextSetLineCap(theContext, kCGLineCapButt);
[self drawLineFromPoint:theCenter toPoint:thePoint
withContext:theContext];
// Minutenzeiger zeichnen
thePoint = [self pointWithRadius:theRadius * 0.9
angle:theMinute];
CGContextSetLineWidth(theContext, theRadius / 40.0);
[self drawLineFromPoint:theCenter toPoint:thePoint
withContext:theContext];
// Sekundenzeiger zeichnen
thePoint = [self pointWithRadius:theRadius * 0.95
angle:theSecond];
CGContextSetLineWidth(theContext, theRadius / 80.0);
CGContextSetRGBStrokeColor(theContext, 1.0, 0.0, 0.0, 1.0);
[self drawLineFromPoint:theCenter toPoint:thePoint
withContext:theContext];
Listing 10.8 Eliminierung des doppelten Codes
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 den Grafikkontext innerhalb dieser Methode bestimmen, so dass auch dieser Parameter aus der Signatur entfällt. Für einige Refactorings, die Sie dafür benötigen (z. B. Methodenparameter entfernen bzw. hinzufügen), gibt es leider keine Unterstützung in Xcode. Hier müssen Sie also alles selbst machen.
Stattdessen ließen 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 drawClockHandWithRadius:angle:lineWidth: lauten (siehe Listing 10.9).
- (void)drawClockHandWithRadius:(CGFloat)inRadius
angle:(CGFloat)inAngle lineWidth:(CGFloat)inLineWidth {
CGContextRef theContext = UIGraphicsGetCurrentContext();
CGPoint theCenter = [self midPoint];
CGPoint thePoint = [self pointWithRadius:inRadius * 0.7
angle:inAngle];
CGContextSetLineWidth(theContext, inLineWidth);
CGContextMoveToPoint(theContext,
theCenter.x, theCenter.y);
CGContextAddLineToPoint(theContext,
thePoint.x, thePoint.y);
CGContextStrokePath(theContext);
}
Listing 10.9 Überarbeitete Methode zum Zeichnen eines Zeigers
Die Verwendung der neuen Methode verkürzt die Methode drawClockHands erheblich und macht sie auch noch übersichtlicher. Die neue Methode stellt zudem eine sinnvolle Operation dar: das Zeichnen eines Zeigers.
- (void)drawClockHands {
CGContextRef theContext = UIGraphicsGetCurrentContext();
CGFloat theRadius = CGRectGetWidth(self.bounds) / 2.0;
NSDateComponents *theComponents = [self.calendar
components:NSHourCalendarUnit | NSMinuteCalendarUnit |
NSSecondCalendarUnit fromDate:self.time];
CGFloat theSecond = theComponents.second * M_PI / 30.0;
CGFloat theMinute = theComponents.minute * M_PI / 30.0;
CGFloat theHour = (theComponents.hour +
theComponents.minute / 60.0) * M_PI / 6.0;
CGContextSetRGBStrokeColor(theContext,
0.25, 0.25, 0.25, 1.0);
CGContextSetLineCap(theContext, kCGLineCapButt);
[self drawClockHandWithRadius:theRadius * 0.7
angle:theHour lineWidth:theRadius / 20.0];
[self drawClockHandWithRadius:theRadius * 0.9
angle:theMinute lineWidth:theRadius / 40.0];
CGContextSetRGBStrokeColor(theContext,
1.0, 0.0, 0.0, 1.0);
[self drawClockHandWithRadius:theRadius * 0.95
angle:theSecond lineWidth:theRadius / 80.0];
}
Listing 10.10 Überarbeitete Methode zum Zeichnen aller Zeiger
10.2.3Oberklassen erzeugen und Methoden verschieben
Wie Sie im vorigen Abschnitt gesehen haben, vereinfachen Sie durch die Auslagerung von Methoden nicht nur Ihren Code, sondern erhöhen auch dessen Wiederverwendungsgrad. 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 Code-Duplikate durch den Aufruf der neuen Methode ersetzen. Abbildung 10.34 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.
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 Edit • Refactor • Create Superclass... legen Sie eine neue Oberklasse zu einer bestehenden Klasse an, und über Edit • Refactor • 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.
Abbildung 10.34 Auslagerung von doppeltem Code in eine Oberklasse
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 10.35. Den Namen der neuen Oberklasse geben Sie in das Textfeld ein. Über die Radiobuttons wählen Sie aus, ob Xcode die Deklaration und die Implementierung in eigenen Dateien oder in den Dateien ClockView.h beziehungsweise ClockView.m unterbringen soll. Wenn Sie die erste Option wählen, enthält das Projekt nach dem Refactoring die beiden neuen Dateien AbstractClockView.h und AbstractClockView.m.
Abbildung 10.35 Anlegen einer neuen Oberklasse
Importanweisungen
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 diese neue Oberklasse schieben Sie nun die gemeinsam genutzten Propertys und Methoden. Als Erstes selektieren Sie den Bezeichner time in der entsprechenden Property-Deklaration in der Datei ClockView.h und rufen den Punkt Refactor • Move Up... im Kontextmenü auf, worauf Xcode den Dialog aus Abbildung 10.36 zeigt. Wenn Sie die Option Move related methods aktivieren, verschiebt Xcode auch alle Methoden, die nach dem Verschieben der Property nur Eigenschaften der Klasse AbstractAlarmClock verwenden. Dazu gehört hier nur die Methode updateTime:, die ja nur die Property time und die Methode setNeedsDisplay verwendet. Die Methode initWithFrame: verschiebt Xcode hingegen nicht, da diese Methode auf die Property calendar zugreift, die ja bislang noch zur Klasse ClockView gehört.
Abbildung 10.36 Verschieben einer Property
Wenn Sie nun analog mit der Property calendar verfahren wollen, stellen Sie fest, dass Xcode mit der Property auch die Methoden initWithFrame:, awakeFromNib und drawClockHands verschieben möchte. Das ist zwar für die ersten beiden Methoden sinnvoll, jedoch nicht für drawClockHands. Bei dieser Verschiebung sollten Sie also besser die Option Move related methods deaktivieren. Sie können die beiden Methoden auch gesondert über das Kontextmenü Refactor • Move Up... verschieben.
Kommentare verschieben
Wenn Sie Methoden über das Move-up-Refactoring verschieben, verschiebt Xcode auch die eventuell darüber stehenden Kommentare. Das ist nicht nur bei Dokumentationskommentaren sehr praktisch. Bei Propertys funktioniert das leider nicht, und die Kommentare gehen verloren. Wenn Sie die Kommentare erhalten wollen, müssen Sie sie vor der Ausführung des Refactorings also sichern.
Unterschiedliche Implementierungen für die Zeitanzeige können ebenfalls die Methoden startAnimation und stopAnimation wiederverwenden, weswegen Sie diese beiden Methoden ebenfalls in die Klasse AbstractClockView verschieben sollten. Allerdings greifen deren Implementierungen auf die private Property timer zu, und Sie erhalten die Fehlermeldung Property is no longer visible (siehe Abbildung 10.37).
Abbildung 10.37 Fehler beim Verschieben einer Methode
Ignorieren Sie einfach diesen Fehler, und verschieben Sie nach dem Verschieben beider Methoden auch die anonyme Kategorie ClockView() in die Klasse AbstractClockView, und benennen Sie sie dort in AbstractClockView() um.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.