Beruflich Dokumente
Kultur Dokumente
ContinuousIntegration PDF
ContinuousIntegration PDF
Release 1
STE
17.03.2014
Inhaltsverzeichnis
Einleitung 1.1 Versionsverwaltung . . . . 1.2 Unit Testing . . . . . . . . 1.3 Metriken . . . . . . . . . . 1.4 Refactoring . . . . . . . . . 1.5 Automatische Builds . . . . 1.6 Continuous Integration (CI)
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
1 1 1 2 2 2 2 3 3 4 7 8 15 15 19 19 21 22 29 29 30 45 45 45 49
Runde 1 2.1 Infrastruktur . . . . . . . . . . . . . . . . 2.2 Das Programm . . . . . . . . . . . . . . . 2.3 Entwickeln unter einer Versionsverwaltung 2.4 Erstellen von Unit-Tests . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
Runde 2 3.1 Automatische Builds . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Runde 3 4.1 CI Server Jenkins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2 Jobs anlegen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3 Jobs Kongurieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Runde 4 5.1 Refactoring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Aufteilen umfangreicher Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Runde 5 6.1 OOP-Refactoring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Methoden herausnehmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Suche
ii
KAPITEL 1
Einleitung
Dieses Tutorial soll einen Einstieg in die Aspekte Versionsverwaltung Unit Testing Metriken Refactoring Automatisierte Build Continuous Integration bieten. Es sind mittlerweile Grundelemente einer jeden (agilen) Softwareentwicklung und fllen den Begriff mit Inhalt.
1.1 Versionsverwaltung
Nicht nur in Teams und verteilten Standorten ist eine Verwaltung des Quellcodes von auerordentlicher Bedeutung. Das Nachvollziehen von nderungen, das eventuell notwendige Rckgngigmachen von nderungen gehrt heute zu jedem ordentlichen Programmierer dazu. In unserem Beispiel werden wir mit Hilfe der zentralen Softwareverwaltung Subversion die grundlegenden Prinzipien kennenlernen. Dazu gehrt z.B. auch die Absprache, an welchen Quellcode-Dateien ein Entwickler gerade arbeitet das Vermeiden von Konikten
1.3 Metriken
Metriken messen bestimmte Eigenschaften von Quellcode, z.B. Lines of Code, Zyklomatische Komplexitt, Vererbungstiefe, Kopplungsgrad von Objekten,... Mit diesen Werten sind anschlieend bestimmte Aussagen ber die Qualitt des Codes mglich. Schlechter Code kann anschlieend refaktorisiert werden.
1.4 Refactoring
Bestehender Quellcode ist hug noch nicht optimal. Dies uert sich z.B. in zu langen Methoden Lagerung des Quellcodes am falschen Ort Nicht-Beachten von allgemein anerkannten Programmierprinzipien schlechten Metriken Refactoring beschreibt Vorgehensweisen, wie man solche Probleme erkennt und behebt. Weiterhin muss natrlich sichergestellt sein, dass nach einem Refactoring der Quellcode immer noch die gleichen Testergebnisse zurckliefert.
Kapitel 1. Einleitung
KAPITEL 2
Runde 1
2.1 Infrastruktur
Der Aufbau der verwendeten Software in unserem Tutorial ist wie folgt: Visual Studio Zur Arbeit mit dem Subversion-Server muss in VStudio ein SVN-Plugin (ANKH-SVN) installiert werden. Sie nden das entsprechende Programm im Order CI auf dem tauschlaufwerk. Subversion-Server Der entsprechende Quellcode ist pro Gruppe in einem SVN-Repository gespeichert. Jede Gruppe hat ihr entsprechendes Login und hat RW-Zugriff auf das Repository. Nachdem Sie das obige Plugin installiert haben, erstellen Sie in Visual Studio ein neues Projekt aus den Subversion-Server heraus und speichern es auf dem lokalen Rechner.
Kapitel 2. Runde 1
//Movie.cs using System; namespace Before { public enum PriceCodes { Regular, NewRelease, Childrens } public class Movie { private string m_Title; private PriceCodes m_PriceCode; public Movie(string title, PriceCodes priceCode) { m_Title = title; m_PriceCode = priceCode; } public PriceCodes PriceCode { get {return m_PriceCode;} set {m_PriceCode = value;} } public string Title { get {return m_Title;} } } } //Rental.cs using System; namespace Fowler { public class Rental { private Movie m_Movie; private int m_DaysRented; public Rental(Movie movie, int daysRented) { m_Movie = movie; m_DaysRented = daysRented; } public int DaysRented { get {return m_DaysRented;} } public Movie Movie { get {return m_Movie;} } } }
//Customer.cs using System; using System.Collections; namespace Fowler { public class Customer { private string m_Name; private ArrayList m_Rentals = new ArrayList(); public Customer(string name) { m_Name = name; } public string Name { get {return m_Name;} } public void AddRental(Rental arg) { m_Rentals.Add(arg); } //public string Statement() { //See the next page } } }
Die Statement-Methode in der Klasse Customer fhrt nun alle Berechnungen durch. Folgendes Sequenzdiagramm zeigt den prinzipiellen Ablauf:
public string Statement() { double totalAmount = 0; int frequentRenterPoints = 0; IEnumerator rentals = m_Rentals.GetEnumerator(); string result = "Rental record for " + m_Name + "\n"; while ( rentals.MoveNext() ) {
Kapitel 2. Runde 1
double thisAmount = 0; Rental each = (Rental)rentals.Current; switch(each.Movie.PriceCode) { case PriceCodes.Regular: thisAmount += 2; if (each.DaysRented > 2) { thisAmount += (each.DaysRented - 2) * 1.5; } break; case PriceCodes.NewRelease: thisAmount += each.DaysRented *3; break; case PriceCodes.Childrens: thisAmount += 1.5; if (each.DaysRented > 3) { thisAmount = (each.DaysRented - 3) * 1.5; } break; } frequentRenterPoints++; if ((each.Movie.PriceCode == PriceCodes.NewRelease) && (each.DaysRented > 1)) { frequentRenterPoints ++; } result += "\t" + each.Movie.Title + "\t" + thisAmount.ToString() + "\n"; totalAmount += thisAmount; } result += "Amount owed is " + totalAmount.ToString() + "\n"; result += "You earned " + frequentRenterPoints.ToString() + " frequent renter points."; return result; }
In unserem Beispiel wollen wir in den bestehenden Code einige Unit-Tests einbauen.
Kapitel 2. Runde 1
Zum Testen der korrekten Funktionalitt sollen einige Demo-Objekte von Filmen, Kunden und Ausleihvorgngen 2.4. Erstellen von Unit-Tests 9
erzeugt werden. Anschlieend wird in einem ersten Test berprft, ob die richtigen Daten zurckgeliefert werden.
using System; using NUnit.Framework; namespace StartingPoint { /// <summary> /// Unit tests for StartingPoint project. /// </summary> [TestFixture] public class UnitTests { /* Fields */ // Movies Movie m_Cinderella; Movie m_StarWars; Movie m_Gladiator; // Rentals Rental m_Rental1; Rental m_Rental2; Rental m_Rental3; // Customers Customer m_MickeyMouse; Customer m_DonaldDuck; Customer m_MinnieMouse; /* Methods */ [SetUp] public void Init() { // Create movies m_Cinderella = new Movie("Cinderella", PriceCodes.Childrens); m_StarWars = new Movie("Star Wars", PriceCodes.Regular); m_Gladiator = new Movie("Gladiator", PriceCodes.NewRelease); // Create m_Rental1 m_Rental2 m_Rental3 rentals = new Rental(m_Cinderella, 5); = new Rental(m_StarWars, 5); = new Rental(m_Gladiator, 5);
// Create customers m_MickeyMouse = new Customer("Mickey Mouse"); m_DonaldDuck = new Customer("Donald Duck"); m_MinnieMouse = new Customer("Minnie Mouse"); } [Test] public void TestMovie() { // Test title property Assert.AreEqual("Cinderella", m_Cinderella.Title); Assert.AreEqual("Star Wars", m_StarWars.Title); Assert.AreEqual("Gladiator", m_Gladiator.Title);
10
Kapitel 2. Runde 1
Der entscheidende Test wird aber das berprfen der Statement()-Methode sein. Bemerkung: Aufgabe berlegen Sie, wie man die Statement-Methode testen knnte.
[Test] public void TestCustomer() { // Test Name property Assert.AreEqual("Mickey Mouse", m_MickeyMouse.Name); Assert.AreEqual("Donald Duck", m_DonaldDuck.Name); Assert.AreEqual("Minnie Mouse", m_MinnieMouse.Name); // Test AddRental() method - set up for test m_MickeyMouse.AddRental(m_Rental1); m_MickeyMouse.AddRental(m_Rental2); m_MickeyMouse.AddRental(m_Rental3); /* At this point, the structure of the program begins getting in the * way of testing. Rentals are imbedded in the Customer object, but * there is no property to access them. They can only be accessed * internally, by the Statement() method, which imbeds them in the * text string passed as its return value. So, to get these amounts, * we will have to parse that value. */ // Test the Statement() method string theResult = m_MickeyMouse.Statement(); // Parse the result char[] delimiters = "\n\t".ToCharArray(); string[] results = theResult.Split(delimiters); /* The results[] array will have the following elements: [0] = junk * [1] = junk * [2] = title #1 * [3] = price #1 * [4] = junk * [5] = title #2 * [6] = price #2 * [7] = junk * [8] = title #3 * [9] = price #3 * [10] = "Amount owed is x" * [11] = "You earned x frequent renter points." * * We will test the title and price elements, and the total * and frequent renter points items. If these tests pass, then * we know that AddRentals() is adding rentals to a Customer * object properly, and that the Statement() method is * generating a statement in the expected format. */
11
// Test the title and price items Assert.AreEqual("Cinderella", results[2]); Assert.AreEqual(3, Convert.ToDouble(results[3])); Assert.AreEqual("Star Wars", results[5]); Assert.AreEqual(6.5, Convert.ToDouble(results[6])); Assert.AreEqual("Gladiator", results[8]); Assert.AreEqual(15, Convert.ToDouble(results[9])); }
12
Kapitel 2. Runde 1
13
Der SVN-Client analysiert die nderungen und trgt sie im zentralen Repository ein.
14
Kapitel 2. Runde 1
KAPITEL 3
Runde 2
3.1.1 NANT
NANT ist ein OpenSource.Projekt und lehnt sich in seiner Arbeitsweise an das aus der Java-Welt bekannte ANT an. Gesteuert wird NANT mit Hilfe eines Kongurationsles default.build, in dem alle Aufgaben deniert werden, die NANT ausfhren soll. Die Aufgaben (task) werden dabei mit Hilfe von targets organisiert.
<?xml version="1.0" encoding="utf-8" ?> <project name="Startingpoint"> <target name="build"> <exec program="msbuild.exe" basedir="c:\Windows\Microsoft.NET\Framework64\v4.0.30319\" commandline="Pfad zur Solution\startingpoint.sln"/> </target>
</project>
Der Aufruf von nant build fhrt dann zum Ausfhren des Buildvorganges.
15
3.1.2 MSBUILD
Die Arbeit mit msbuild gestaltet sich noch einfacher. Der Buildprozess kann dadurch gestartet werden, dass man dem Konsolenprogramm msbuild.exe den Pfad zur Solution bergibt.
c:\Windows\Microsoft.NET\Framework64\v4.0.30319\msbuild.exe Pfad_zur_Solution.sln
3.1.3 NUnit-Integration
Grundstzlich kann NUnit sowohl in NANT als auch in MSBUILD integriert werden. In unserem Beispiel werden wir die Integration mit Hilfe des NANT-Tools zeigen. Eine Kongurationsdatei von NANT kann verschiedene Tasks besitzen, die auch selbstndig voneinander ausgerufen werden knnen.
<?xml version="1.0" encoding="utf-8" ?> <project name="Startingpoint"> <target name="build"> <exec program="msbuild.exe" basedir="c:\Windows\Microsoft.NET\Framework64\v4.0.30319\" commandline="C:\temp\fowler\startpoint\startingpoint.sln"/> </target> <target name="test"> <exec program="c:\Program Files (x86)\NUnit 2.6.3\bin\nunit-console.exe" commandline="C:\temp\fowler\startpoint\bin\Debug\StartingPoint.exe /xml=C:\temp\fowler\ </target> </project>
Der Parameter /xml gibt den Dateinamen an, in der NUnit seine Ausgaben schreiben soll. Diese Datei wird von uns spter noch ausgewertet werden. Der Aufruf des Test-Targets erfolgt dann wie folgt.
16
Kapitel 3. Runde 2
Microsoft Windows [Version 6.3.9600] (c) 2013 Microsoft Corporation. Alle Rechte vorbehalten. C:\temp\fowler\StartPoint>c:\temp\nant\bin\NAnt.exe test NAnt 0.92 (Build 0.92.4543.0; release; 09.06.2012) Copyright (C) 2001-2012 Gerry Shaw http://nant.sourceforge.net Buildfile: file:///C:/temp/fowler/StartPoint/default.build Target framework: Microsoft .NET Framework 4.0 Target(s) specified: test
test: [exec] [exec] [exec] [exec] [exec] [exec] [exec] [exec] [exec] [exec] [exec] [exec] [exec] [exec] [exec] [exec] NUnit-Console version 2.6.3.13283 Copyright (C) 2002-2012 Charlie Poole. Copyright (C) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov. Copyright (C) 2000-2002 Philip Craig. All Rights Reserved. Runtime Environment OS Version: Microsoft Windows NT 6.2.9200.0 CLR Version: 2.0.50727.8000 ( Net 3.5 ) ProcessModel: Default DomainUsage: Single Execution Runtime: net-3.5 ... Tests run: 3, Errors: 0, Failures: 0, Inconclusive: 0, Time: 0,21134 9783145239 seconds Not run: 0, Invalid: 0, Ignored: 0, Skipped: 0
3.1.4 Metrics-Integration
Installation In VStudio 2012 fehlte das Konsolenprogramm metrics.exe. Es muss in das Verzeichnis c:Program Files (x86)Microsoft Visual Studio 11.0Team ToolsStatic Analysis ToolsFxCop kopiert werden Der Aufruf erfolgt mit metrics /f:metrics.exe /o:C:tempMetricsresult.xml
17
18
Kapitel 3. Runde 2
KAPITEL 4
Runde 3
Jenkins ist ber Plugins exibel kongurierbar; in unserem Falle werden wir folgende Plugins installieren:
19
NUnit: Zum Rendern der NUnit-Ausgabe MSBUILD: Zum Builden von Solution-Files NANT: Zum Anstoen weiterer Funktionalitten VStudio Code Metrics: Zum Erzeugen einfacher Metriken per metrics.exe Diese Plugins mssen global installiert und konguriert werden.
20
Kapitel 4. Runde 3
Weitere Kongurationen werden dann innerhalb eines Projekte vorgenommen. Weiterhin mssen noch Benutzer mit entsprechenden Zugriffsberechtigungen auf jeweilige Projekte angelegt werden.
21
22
Kapitel 4. Runde 3
Buildverfahren denieren dabei die Aktionen, die ausgefhrt werden mssen. Post-Build-Aktionen denieren dabei, was mit den Ergebnissen der Build-Verfahren geschehen soll. In unserem Beispiel werden die Ergebnisse der NANT und Metrics-Ausgaben (XMl-Dateien) ber einen Publisher grasch aufbereitet und in die bersichtsseite des jeweiligen Buildvorgangs dann integriert. Voraussetzung dafr war natrlich das Installieren der jeweiligen PublisherPlugins in Jenkins zuvor.
Falls alles klappt, sieht anschlieend die Startseite eines Projektes wie folgt aus.
23
Nach dem Klicken auf einen jeweiligen Buildvorgang werden Details dargestellt.
4.3.1 Aufgaben:
Erstellen API-Dokumentation Das Programm doxygen (http://www.doxygen.org) erstellt aus dem Quellcode eines Programms eine APIDokumentation. Der Ablauf der lokalen Erstellung ist wie folgt: Denieren eines Kongurationsles mit Hilfe eines GUI-Tools (doxywizard)
24
Kapitel 4. Runde 3
25
Beachte In Jenkins muss zur Ausgabe der HTML-Publisher-Plugin instaliert werden Aufgabe Erstellen sie eine lokal ablauffhige Lsung. Passen Sie dann die entsprechende Konguration an und implementieren Sie diese auf dem CI Server Jenkins. Doxygen ist auf dem CI Server installiert. Zum Ablauf der Erstellung muss natrlich ein neues Target innerhalb des NANT-Buildles erstellt werden.
Erstellen Installer Innosetup hnlich wie bei Doxygen kann man mit Hilfe des Programms Innnosetup einen Installer erstellen lassen. Der Ablauf ist prinzipiell der gleich wie bei doxygen. Mit Hilfe eines Wizards wird ein Kongurationsle erstellt, welches dann ber ein Konsolenprogramm aufgerufen wird. Dieser Aufruf muss dann in gleicher Weise auf dem CI Server abgebildet werden. Das Konsolenprogramm fr den Aufruf des installers heit iscc.exe. Dies erwartet als Kommandozeilenparameter die bergabe eines Kongurationsles. Die Vorgehensweise auf der CI-Server-Seite ist die gleiche wie beim Erstellen der Doku.
26
Kapitel 4. Runde 3
Aufgabe Erstellen sie eine lokal ablauffhige Lsung. Passen Sie dann die entsprechende Konguration an und implementieren Sie diese auf dem CI Server Jenkins. Innosetup (iscc.exe) ist auf dem CI Server installiert. Zum Ablauf der Erstellung muss natrlich ein neues Target innerhalb des NANT-Buildles erstellt werden. Darstellen von Artefakten Hug haben Build-Prozesse auch Ergebnisse in Dateiform. Bei einem Installer wird z.B. am Ende eine Datei setup.exe erzeugt, die man gerne innerhalb des Ci-Servers dann anzeigen wrde. Jenkis kennt fr diese Aufgabe die sog. Artefact-Plugins, die solche Dateien dann in der Projektoberche anklickbar machen. In unserem Falle wurde das Artifact Deployer Plugin verwendet, um die Datei setup.exe zu verlinken. 4.3. Jobs Kongurieren 27
Aufgabe Bilden sie die Ausgabe der Datei setup.exe mit Hilfe des Plugins ab.
28
Kapitel 4. Runde 3
KAPITEL 5
Runde 4
5.1 Refactoring
Zitat Refactoring (auch Refaktorierung oder Restrukturierung) bezeichnet in der Software-Entwicklung die manuelle oder automatisierte Strukturverbesserung von Quelltexten unter Beibehaltung des beobachtbaren Programmverhaltens. Dabei sollen die Lesbarkeit, Verstndlichkeit, Wartbarkeit und Erweiterbarkeit verbessert werden, mit dem Ziel, den jeweiligen Aufwand fr Fehleranalyse und funktionale Erweiterungen deutlich zu senken. Refactoring ist ein zentraler Bestandteil der Agilen Softwareentwicklung. Dort wird meist von kontinuierlichem Refactoring [1] oder kompromisslosem Refactoring [2] gesprochen. Refactoring ist in der agilen Softwareentwicklung wie Kodieren oder Modultesten ein integraler Bestandteil des Softwareentwicklungsprozesses und nicht auf bestimmte Zeiten bzw. Phasen beschrnkt. Quelle: http://de.wikipedia.org/wiki/Refactoring Refactoring wird hauptschlich auf unschne Stellen im Code (siehe Code-Smell) angewandt. Diese sind z.B. mit Hilfe von schlechten Metrikwerten als solche zu erkennen. Dabei wird der Quelltext eines Computerprogramms umgestaltet, wobei die tatschliche Programmfunktion unverndert bleiben soll. Die Umgestaltung des Quelltextes erfolgt meist nach folgenden Gesichtspunkten: Lesbarkeit bersichtlichkeit Verstndlichkeit Erweiterbarkeit Vermeidung von Redundanz Testbarkeit Folgendes sind besonders hug eingesetzte Refactorings: nderung eines Symbolnamens. Verschieben eines Symbols in ein anderes Modul, z. B. eine Methode in eine andere Klasse. Aufteilung eines Moduls (z. B. Paket, Klasse, Methode) in mehrere kleinere Module oder Zusammenlegung kleinerer Module zu einem greren. Im weitesten Sinne auch die Umformatierung eines Quelltextes, z. B. mit einem Beautier.
29
Bei genderten Geschftsprozessen bei Darstellung mittels der Unied Modeling Language UML kann mittels Refactoring der Programmcode gendert werden. Dadurch wird eine robuste und stabile Systemarchitektur geschaffen, da unbersichtliche nderungen nicht im Code initiiert werden mssen. Im weiteren Verlauf wollen wir nun den Quelltext hinsichtlich eines mglichen Refactorings untersuchen.
30
Kapitel 5. Runde 4
31
In unserem Beispiel wurde die switch-Anweisung in eine eigene Methode ausgelagert. Wenn korrekte Datentypen bei der bergabe von Parametern deniert werden, darf diese Codenderung anschlieend keine negativen Auswirkungen auf diese Tests haben.
Die Codemetriken fr diese Methode ndern sich ebenfalls in die positive Richtung.
32
Kapitel 5. Runde 4
5.2.2 Umbenennen
Regel Renaming Nachdem die Tests keine Fehler liefern, kann die neue Version noch zum Umbenennen von einigen Variablen genommen werden. Moderne IDEs untersttzen den Entwickler dabei weitgehend und verstndliche Namen erhhen die Nachvollziehbarkeit des Quellcodes.
Ein Neukompilieren sowie das Ausfhren der Tests besttigt, dass es keine Fehler gegeben hat.
33
public double GetCharge() { double result = 0; // Determine amounts for each line switch (m_Movie.PriceCode) { case PriceCodes.Regular: result += 2; if (DaysRented > 2) { result += (DaysRented - 2) * 1.5; } break; case PriceCodes.NewRelease: result += DaysRented * 3; break; case PriceCodes.Childrens: result += 1.5; if (DaysRented > 3) { result += (DaysRented - 3) * 1.5; } break; } return result; }
In der Klasse Customer ist der Quellcode der amountFor-Methode durch folgende Vereinfachung anzupassen.
private double amountFor(Rental aRental) { return aRental.GetCharge(); }
Falls es immer noch fehlerfrei luft, kann die amountFor-Methode vernachlssigt werden. Die Umleitung auf die Rental-Klasse kann wieder direkt in der Statement()-Methode erfolgen.
Die vorhandenen UnitTests sollten noch immer funktionieren und auch die CodeMetrik sollte keine signikanten nderungen erfahren haben.
34
Kapitel 5. Runde 4
Das abschlieende Klassendiagramm hat sich vom Ausgangspunkt wie folgt gendert.
UnitTests Durch die neue ffentliche Methode in Rental mssten fr getcharge() neue UnitTests geschrieben werden. Sie knnen diese unter UnitTest3.cs in ihr Projekt integrieren.
35
Regel Replace Temp with Query Temporre Variablen sind ein weiteres Indiz fr Optimierungen. Sie berleben hug im Produktivcode und nach einiger Zeit ist man sich deren Notwendigkeit unsicher. In unserem beispiel kann die Deklaration und Benutzung von thisAmount in der while-Schleife durch each.getCharge() ersetzt werden.
while ( rentals.MoveNext() ) { Rental each = (Rental)rentals.Current; // Determine amounts for each line // Add frequent renter points frequentRenterPoints++; // Add bonus for a two-day new-release rental if ((each.Movie.PriceCode == PriceCodes.NewRelease) && (each.DaysRented > 1)) { frequentRenterPoints ++; } // Show figures for this rental result += "\t" + each.Movie.Title + "\t" + each.GetCharge().ToString() + "\n"; totalAmount += each.GetCharge(); }
Die Optimierung hinsichtlich der Lesbarkeit wird in diesem Beispiel natrlich durch eine doppelte Berechnung hinterfragbar. Dieser Nachteil wre aber durch eine nderung in der Klasse Rental ausgleichbar. Darauf wird spter noch eingegangen
36
Kapitel 5. Runde 4
Bei der Implementierung der Methode ist zu beachten, dass die frequentrenterPoints-Variable evtl. zweimal erhht wird. Dies ist in der Methode entsprechend zu implementieren.
class Customer { //.... while ( rentals.MoveNext() ) { Rental each = (Rental)rentals.Current; frequentRenterPoints += each.GetFrequentRenterPoints(); // Show figures for this rental
class Rental { .... public int GetFrequentRenterPoints() { if (Movie.PriceCode == PriceCodes.NewRelease && DaysRented > 1) return 2; else return 1; }
37
Rental _RentalSuperman1 = new Rental(new Movie("Superman", PriceCodes.Childrens),1); Assert.AreEqual(1, _RentalSuperman1.GetFrequentRenterPoints()); Rental _RentalSuperman3 = new Rental(new Movie("Superman", PriceCodes.Childrens),3); Assert.AreEqual(1, _RentalSuperman3.GetFrequentRenterPoints()); } [Test] public void TestNewPublicMethod_RentalGetFrequentRenterPointsRegularPrice() { Rental _RentalSuperman1 = new Rental(new Movie("Superman", PriceCodes.Regular),1); Assert.AreEqual(1, _RentalSuperman1.GetFrequentRenterPoints()); Rental _RentalSuperman3 = new Rental(new Movie("Superman", PriceCodes.Regular),3); Assert.AreEqual(1, _RentalSuperman3.GetFrequentRenterPoints()); }
38
Kapitel 5. Runde 4
5.2.6 Code-Verbesserungen
Innerhalb der Statement-Methode kann eine weitere Verbesserung dadurch erreich werden, dass man die Java-Like enumerator - while-Schleife durch ein foreach-Konstrukt ersetzt.
39
Da totalAmount innerhalb einer Schleife errechnet wurde, muss diese Schleifenstruktur innerhalb der Methode getTotalCharge() nachgebildet werden.
40
Kapitel 5. Runde 4
private double GetTotalCharge() { double result = 0; foreach ( Rental each in m_Rentals ) { result += each.GetCharge(); } return result; }
Die gleiche Vorgehensweise ist fr frequentrenterPoints nachzuholen. Aufgabe Erstellen Sie eine Methode GetTotalFrequentRenterPoints und fhren Sie die Refaktorisierung durch.
Lsung
public string Statement() { string result = "Rental record for " + m_Name + "\n"; foreach ( Rental each in m_Rentals ) { // Show figures for this rental result += "\t" + each.Movie.title + "\t" + each.GetCharge().ToString() + "\n"; } // Add footer lines result += "Amount owed is " + GetTotalCharge().ToString() + "\n"; result += "You earned " + GetTotalFrequentRenterPoints().ToString() + " frequent renter points."; return result; } private int GetTotalFrequentRenterPoints() { int result = 0; foreach ( Rental each in m_Rentals ) { result += each.GetFrequentRenterPoints(); } return result; }
41
It is worth stopping to think a bit about the last refactoring. Most refactorings reduce the amount of code, but this one increases it. Thats because Java 1.1 requires a lot of statements to set up a summing loop. Even a simple summing loop with one line of code per element needs six lines of support around it. Its an idiom that is obvious to any programmer but is a lot of lines all the same. The other concern with this refactoring lies in performance. The old code executed the while loop once, the new code executes it three times. A while loop that takes a long time might impair performance. Many programmers would not do this refactoring simply for this reason. But note the words if and might. Until I prole I cannot tell how much time is needed for the loop to calculate or whether the loop is called often enough for it to affect the overall performance of the system. Dont worry about this while refactoring. When you optimize you will have to worry about it, but you will then be in a much better position to do something about it, and you will have more options to optimize effectively. These queries are now available for any code written in the customer class. They can easily be added to the interface of the class should other parts of the system need this information. Without queries like these, other methods have to deal with knowing about the rentals and building the loops. In a complex system, that will lead to much more code to write and maintain.
5.2.8 HTML-Statement
Nach all den Refaktoriesierungen ist es nun einfach, eine HTML-Statement-Methode zu schreiben, da Sie viele interne Implementierungen wiederverwenden kann.
public string HTMLStatement() { string result = "<H1>Rental record for <EM>" + m_Name + "</EM></H1><P>\n"; foreach (Rental each in m_Rentals) { result += each.Movie.title + ": " + each.GetCharge().ToString() + "<BR>\n"; } // Add footer lines result += "<P>Amount owed is <EM>" + GetTotalCharge().ToString() + "</EM><P>\n"; result += "You earned <EM>" + GetTotalFrequentRenterPoints().ToString() + "</EM> frequent renter points.<P>"; return result; }
42
Kapitel 5. Runde 4
+ "You earned <EM>4</EM> frequent renter points.<P>" , _LiverLips.HTMLStatement()); _LiverLips.AddRental(new Rental(new Movie("The Good, The Bad, And The Ugly" , PriceCodes.Childrens), 10)); Assert.AreEqual ("<H1>Rental record for <EM>Liver Lips</EM></H1><P>\nEnforcer: 12<BR>\nApollo 13: " + "9<BR>\nPulp Fiction: 5<BR>\nThe Good, The Bad, And The Ugly: 12<BR>\n<P>" + "Amount owed is <EM>38</EM><P>\nYou earned <EM>5</EM> frequent renter points.<P>" , _LiverLips.HTMLStatement()); }
43
44
Kapitel 5. Runde 4
KAPITEL 6
Runde 5
6.1 OOP-Refactoring
Nachdem die ersten Optimierungen eher im Bereich der strukturierten Programmierung gelegen sind, kann man auch Optimierungen durch Vernderungen objektorientierter Elemente bewirken
45
public class Movie { ... public double GetCharge(int daysRented) { double result = 0; // Determine amounts for each line switch(this.PriceCode) { case PriceCodes.Regular: result += 2; if (daysRented > 2) { result += (daysRented - 2) * 1.5; } break; case PriceCodes.NewRelease: result += daysRented *3; break; case PriceCodes.Childrens: result += 1.5; if (daysRented > 3) { result += (daysRented - 3) * 1.5; } break; } return result; } }
Die Methode bentigt allerdings die Informationen ber die Mietdauer(daysRented). Dies ist beim Aufruf aus Movie heraus zu bergeben.
public class Rental { ... public double GetCharge() { return Movie.GetCharge(DaysRented); } }
Lsung
classe Movie { .... public int GetFrequentRenterPoints() { if (this.PriceCode == PriceCodes.NewRelease && DaysRented > 1) return 2; else return 1; } }
46
Kapitel 6. Runde 5
6.2.1 Polymorphie
Die Movie-Objekte errechnen die Gebhren in Abhngigkeit von ihrem PriceCode. Dies wre eigentlich ein idealer Ansatz fr Vererbung.
Diskussion Diskutieren Sie die oben gettigte Aussage Eine Lsung, die die oben mglichen Nachteile ausschliet, wre die Anwendung des Strategy- bzw. State-Pattern. State-Pattern Erstellen Sie ein Klassendiagramm, welches das State-Pattern auf die Movie-Klasse im Zusammenhang mit der Preisermittlung anwendet.
47
48
Kapitel 6. Runde 5
KAPITEL 7
Suche
search
49