© martin fowler, 1997 refactoring: improving the design of...

82
1 Page 1 © Martin Fowler 9/11/99 Refactoring: Refactoring: Improving the Design Improving the Design of Existing Code of Existing Code © Martin Fowler, 1997 Martin Fowler Martin Fowler fowler fowler@acm acm.org .org http:// http://ourworld ourworld.compuserve compuserve.com/homepages/Martin_Fowler .com/homepages/Martin_Fowler Page 2 © Martin Fowler 9/11/99 What we will cover What we will cover q A simple example of refactoring Blow by blow example of changes Steps for illustrated refactorings q Background of refactoring Where it came from Tools Why and When q Unit testing with JUnit Rhythm of development q Bad Smells and their cures Fowler, Refactoring: Improving the Design of Existing Code, Addison-Wesley, 1999

Upload: others

Post on 03-Jun-2020

6 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

1

Page 1 © Martin Fowler 9/11/99

Refactoring:Refactoring:Improving the DesignImproving the Design

of Existing Codeof Existing Code

© Martin Fowler, 1997

Martin FowlerMartin Fowler

fowlerfowler@@acmacm.org.orghttp://http://ourworldourworld..compuservecompuserve.com/homepages/Martin_Fowler.com/homepages/Martin_Fowler

Page 2 © Martin Fowler 9/11/99

What we will coverWhat we will cover

q A simple example of refactoringâ Blow by blow example of changesâ Steps for illustrated refactorings

q Background of refactoringâ Where it came fromâ Toolsâ Why and When

q Unit testing with JUnitâ Rhythm of development

q Bad Smells and their cures

Fowler, Refactoring: Improving the Design of ExistingCode, Addison-Wesley, 1999

Page 2: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

2

Page 3 © Martin Fowler 9/11/99

What is RefactoringWhat is Refactoring

q Verify no change in external behavior byâ testingâ formal code analysis by tool

è In practice good tests are essential

A series of small steps, each of whichchanges the program’s internalstructure without changing itsexternal behavior

Page 4 © Martin Fowler 9/11/99

Starting Class diagramStarting Class diagram

priceCode: int

Movie

daysRented: int

Rental

statement()

Customer[1

[ 1

Page 3: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

3

Page 5 © Martin Fowler 9/11/99

Class MovieClass Movie

public class Movie {public static final int CHILDRENS = 2;public static final int REGULAR = 0;public static final int NEW_RELEASE = 1;

private String _title;private int _priceCode;

public Movie(String title, int priceCode) {_title = title;_priceCode = priceCode;

}

public int getPriceCode() {return _priceCode;

}

public void setPriceCode(int arg) { _priceCode = arg;

}

public String getTitle () {return _title;

};}

Page 6 © Martin Fowler 9/11/99

Class RentalClass Rental

class Rental { private Movie _movie; private int _daysRented;

public Rental(Movie movie, int daysRented) { _movie = movie; _daysRented = daysRented; } public int getDaysRented() { return _daysRented; } public Movie getMovie() { return _movie; }}

Page 4: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

4

Page 7 © Martin Fowler 9/11/99

Class Customer (almost)Class Customer (almost)

class Customer {

private String _name;

private Vector _rentals = new Vector();

public Customer (String name) {

_name = name;

};

public void addRental(Rental arg) {

_rentals.addElement(arg);

}

public String getName () {

return _name;

};

public String statement() // see next slide

Page 8 © Martin Fowler 9/11/99

Customer.statement() part 1Customer.statement() part 1public String statement() {

double totalAmount = 0;

int frequentRenterPoints = 0;

Enumeration rentals = _rentals.elements();

String result = "Rental Record for " + getName() + "\n";

while (rentals.hasMoreElements()) {

double thisAmount = 0;

Rental each = (Rental) rentals.nextElement();

//determine amounts for each line

switch (each.getMovie().getPriceCode()) {

case Movie.REGULAR:

thisAmount += 2;

if (each.getDaysRented() > 2)

thisAmount += (each.getDaysRented() - 2) * 1.5;

break;

case Movie.NEW_RELEASE:

thisAmount += each.getDaysRented() * 3;

break;

case Movie.CHILDRENS:

thisAmount += 1.5;

if (each.getDaysRented() > 3)

thisAmount += (each.getDaysRented() - 3) * 1.5;

break;

}

continues on next slide

Page 5: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

5

Page 9 © Martin Fowler 9/11/99

Customer.statement() part 2Customer.statement() part 2// add frequent renter points

frequentRenterPoints ++;

// add bonus for a two day new release rental

if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) &&

each.getDaysRented() > 1) frequentRenterPoints ++;

//show figures for this rental

result += "\t" + each.getMovie().getTitle()+ "\t" +

String.valueOf(thisAmount) + "\n";

totalAmount += thisAmount;

}

//add footer lines

result += "Amount owed is " + String.valueOf(totalAmount) + "\n";

result += "You earned " + String.valueOf(frequentRenterPoints) +

" frequent renter points";

return result;

}

Page 10 © Martin Fowler 9/11/99

Interactions for statementInteractions for statement

aCustomer aRental aMovie

getMovie

* [for all rentals]

getPriceCode

getDaysRented

statement

Page 6: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

6

Page 11 © Martin Fowler 9/11/99

Sample OutputSample Output

Rental Record for Dinsdale Pirhana

Monty Python and the Holy Grail 3.5

Ran2

Star Trek 27 6

Star Wars 3.2 3

Wallace and Gromit 6

Amount owed is 20.5

You earned 6 frequent renter points

Page 12 © Martin Fowler 9/11/99

Requirements ChangesRequirements Changes

q Produce an html version of the statementq The movie classifications will soon

changeâ together with the rules for charging and for

frequent renter points

Page 7: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

7

Page 13 © Martin Fowler 9/11/99

Extract MethodExtract Method

void printOwing() {printBanner();

//print detailsSystem.out.println ("name:" + _name);System.out.println ("amount" + getOutstanding());

}

void printOwing() {printBanner();printDetails(getOutstanding());

}

void printDetails (double outstanding) {System.out.println ("name:" + _name);System.out.println ("amount" + outstanding);

}

You have a code fragment that can be grouped togetherTurn the fragment into a method whose name explains the

purpose of the method.

Page 14 © Martin Fowler 9/11/99

Candidate ExtractionCandidate Extractionpublic String statement() {

double totalAmount = 0;

int frequentRenterPoints = 0;

Enumeration rentals = _rentals.elements();

String result = "Rental Record for " + getName() + "\n";

while (rentals.hasMoreElements()) {

double thisAmount = 0;

Rental each = (Rental) rentals.nextElement();

//determine amounts for each line

switch (each.getMovie().getPriceCode()) {

case Movie.REGULAR:

thisAmount += 2;

if (each.getDaysRented() > 2)

thisAmount += (each.getDaysRented() - 2) * 1.5;

break;

case Movie.NEW_RELEASE:

thisAmount += each.getDaysRented() * 3;

break;

case Movie.CHILDRENS:

thisAmount += 1.5;

if (each.getDaysRented() > 3)

thisAmount += (each.getDaysRented() - 3) * 1.5;

break;

}

[snip]

Page 8: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

8

Page 15 © Martin Fowler 9/11/99

Steps for Steps for Extract MethodExtract Methodq Create method named after intention of

codeq Copy extracted codeq Look for local variables and parameters

â turn into parameterâ turn into return valueâ declare within method

q Compileq Replace code fragment with call to new

methodq Compile and test

Page 16 © Martin Fowler 9/11/99

Extracting the Amount Extracting the Amount Calculation Calculation

private int amountFor(Rental each) {

int thisAmount = 0;

switch (each.getMovie().getPriceCode()) {

case Movie.REGULAR:

thisAmount += 2;

if (each.getDaysRented() > 2)

thisAmount += (each.getDaysRented() - 2) * 1.5;

break;

case Movie.NEW_RELEASE:

thisAmount += each.getDaysRented() * 3;

break;

case Movie.CHILDRENS:

thisAmount += 1.5;

if (each.getDaysRented() > 3)

thisAmount += (each.getDaysRented() - 3) * 1.5;

break;

}

return thisAmount;

}

Page 9: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

9

Page 17 © Martin Fowler 9/11/99

Statement() after extractionStatement() after extractionpublic String statement() {

double totalAmount = 0;

int frequentRenterPoints = 0;

Enumeration rentals = _rentals.elements();

String result = "Rental Record for " + getName() + "\n";

while (rentals.hasMoreElements()) {

double thisAmount = 0;

Rental each = (Rental) rentals.nextElement();

thisAmountthisAmount = = amountFor amountFor(each);(each);

// add frequent renter points

frequentRenterPoints ++;

// add bonus for a two day new release rental

if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) &&

each.getDaysRented() > 1) frequentRenterPoints ++;

//show figures for this rental

result += "\t" + each.getMovie().getTitle()+ "\t" +

String.valueOf(thisAmount) + "\n";

totalAmount += thisAmount;

}

//add footer lines

result += "Amount owed is " + String.valueOf(totalAmount) + "\n";

result += "You earned " + String.valueOf(frequentRenterPoints) +

" frequent renter points";

return result;

}

}

Page 18 © Martin Fowler 9/11/99

Extracting the amount Extracting the amount calculation (2)calculation (2)

private double amountFor(Rental each) {

double thisAmount = 0;

switch (each.getMovie().getPriceCode()) {

case Movie.REGULAR:

thisAmount += 2;

if (each.getDaysRented() > 2)

thisAmount += (each.getDaysRented() - 2) * 1.5;

break;

case Movie.NEW_RELEASE:

thisAmount += each.getDaysRented() * 3;

break;

case Movie.CHILDRENS:

thisAmount += 1.5;

if (each.getDaysRented() > 3)

thisAmount += (each.getDaysRented() - 3) * 1.5;

break;

}

return thisAmount;

}

Page 10: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

10

Page 19 © Martin Fowler 9/11/99

Change names of variablesChange names of variables

private double amountFor(Rental aRental) {

double result = 0;

switch (aRental.getMovie().getPriceCode()) {

case Movie.REGULAR:

result += 2;

if (aRental.getDaysRented() > 2)

result += (aRental.getDaysRented() - 2) * 1.5;

break;

case Movie.NEW_RELEASE:

result += aRental.getDaysRented() * 3;

break;

case Movie.CHILDRENS:

result += 1.5;

if (aRental.getDaysRented() > 3)

result += (aRental.getDaysRented() - 3) * 1.5;

break;

}

return result;

}

Is this important?

Is this method in the right place?

Page 20 © Martin Fowler 9/11/99

Move MethodMove Method

aMethod()

Class 1

Class 2

Class 1

aMethod()

Class 2

A method is, or will be, using or used by more features ofanother class than the class it is defined on.

Create a new method with a similar body in the class it usesmost. Either turn the old method into a simple delegation, or

remove it altogether.

Page 11: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

11

Page 21 © Martin Fowler 9/11/99

Steps for Steps for Move methodMove method

q Declare method in target classq Copy and fit codeq Set up a reference from the source object

to the targetq Turn the original method into a

delegating methodâ amountOfamountOf(Rental each) {return each.charge()}(Rental each) {return each.charge()}

â Check for overriding methods

q Compile and testq Find all users of the method

â Adjust them to call method on target

q Remove original methodq Compile and test

Page 22 © Martin Fowler 9/11/99

Moving amount() to RentalMoving amount() to Rentalclass Rental ...

double getCharge() {

double result = 0;

switch (getMovie().getPriceCode()) {

case Movie.REGULAR:

result += 2;

if (getDaysRented() > 2)

result += (getDaysRented() - 2) * 1.5;

break;

case Movie.NEW_RELEASE:

result += getDaysRented() * 3;

break;

case Movie.CHILDRENS:

result += 1.5;

if (getDaysRented() > 3)

result += (getDaysRented() - 3) * 1.5;

break;

}

return result;

}

1 [

statement()

Customer

getCharge()

daysRented: int

Rental

priceCode: int

Movie

Page 12: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

12

Page 23 © Martin Fowler 9/11/99

Altered statementAltered statementclass Customer...

public String statement() {

double totalAmount = 0;

int frequentRenterPoints = 0;

Enumeration rentals = _rentals.elements();

String result = "Rental Record for " + getName() + "\n";

while (rentals.hasMoreElements()) {

double thisAmount = 0;

Rental each = (Rental) rentals.nextElement();

thisAmount = each.each.getChargegetCharge()();

// add frequent renter points

frequentRenterPoints ++;

// add bonus for a two day new release rental

if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) &&

each.getDaysRented() > 1) frequentRenterPoints ++;

//show figures for this rental

result += "\t" + each.getMovie().getTitle()+ "\t" +

String.valueOf(thisAmount) + "\n";

totalAmount += thisAmount;

}

//add footer lines

result += "Amount owed is " + String.valueOf(totalAmount) + "\n";

result += "You earned " + String.valueOf(frequentRenterPoints) +

" frequent renter points";

return result;

}

Page 24 © Martin Fowler 9/11/99

Problems with tempsProblems with tempsclass Customer...

public String statement() {

double totalAmount = 0;

int frequentRenterPoints = 0;

Enumeration rentals = _rentals.elements();

String result = "Rental Record for " + getName() + "\n";

while (rentals.hasMoreElements()) {

double thisAmount = 0;

Rental each = (Rental) rentals.nextElement();

thisAmount = each.each.getChargegetCharge()();

// add frequent renter points

frequentRenterPoints ++;

// add bonus for a two day new release rental

if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) &&

each.getDaysRented() > 1) frequentRenterPoints ++;

//show figures for this rental

result += "\t" + each.getMovie().getTitle()+ "\t" +

String.valueOf(thisAmount) + "\n";

totalAmount += thisAmount;

}

//add footer lines

result += "Amount owed is " + String.valueOf(totalAmount) + "\n";

result += "You earned " + String.valueOf(frequentRenterPoints) +

" frequent renter points";

return result;

}

Page 13: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

13

Page 25 © Martin Fowler 9/11/99

A Word About PerformanceA Word About Performance

q Most of a program’s time is taken in asmall part of the code

q Profile a running program to find these“hot spots”â You won’t be able to find them by eye

q Optimize the hot spots, and measure theimprovement

The best way to optimizeperformance is to first write a wellfactored program, then optimize it.

McConnell Steve, Code Complete: A Practical Handbook ofSoftware Construction, Microsoft Press, 1993

Page 26 © Martin Fowler 9/11/99

Replace Temp with QueryReplace Temp with Query

double basePrice = _quantity * _itemPrice;if (basePrice > 1000)

return basePrice * 0.95;else

return basePrice * 0.98;

if (basePrice() > 1000) return basePrice() * 0.95;

else return basePrice() * 0.98;

...double basePrice() {

return _quantity * _itemPrice;}

You are using a temporary variable to hold the result of anexpression.

Extract the expression into a method. Replace all referencesto the temp with the expression. The new method can then

be used in other methods.

Page 14: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

14

Page 27 © Martin Fowler 9/11/99

Steps for Steps for Replace temp with QueryReplace temp with Query

q Find temp with a single assignmentq Extract Right Hand Side of assignmentq Replace all references of temp with new

methodq Remove declaration and assignment of

tempq Compile and test

Page 28 © Martin Fowler 9/11/99

thisAmount thisAmount removedremoved public String statement() {

double totalAmount = 0;

int frequentRenterPoints = 0;

Enumeration rentals = _rentals.elements();

String result = "Rental Record for " + getName() + "\n";

while (rentals.hasMoreElements()) {

Rental each = (Rental) rentals.nextElement();

// add frequent renter points

frequentRenterPoints ++;

// add bonus for a two day new release rental

if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) &&

each.getDaysRented() > 1) frequentRenterPoints ++;

//show figures for this rental

result += "\t" + each.getMovie().getTitle()+ "\t" +

String.valueOf(each.each.getChargegetCharge()()) + "\n";

totalAmount += each.each.getChargegetCharge()();

}

//add footer lines

result += "Amount owed is " + String.valueOf(totalAmount) + "\n";

result += "You earned " + String.valueOf(frequentRenterPoints) +

" frequent renter points";

return result;

}

}

Page 15: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

15

Page 29 © Martin Fowler 9/11/99

Extract and move Extract and move frequentRenterPointsfrequentRenterPoints()()

class Customer...

public String statement() {

double totalAmount = 0;

int frequentRenterPoints = 0;

Enumeration rentals = _rentals.elements();

String result = "Rental Record for " + getName() + "\n";

while (rentals.hasMoreElements()) {

Rental each = (Rental) rentals.nextElement();

frequentRenterPoints frequentRenterPoints += each. += each.getFrequentRenterPointsgetFrequentRenterPoints();();

//show figures for this rental

result += "\t" + each.getMovie().getTitle()+ "\t" +

String.valueOf(each.getCharge()) + "\n";

totalAmount += each.getCharge();

}

//add footer lines

result += "Amount owed is " + String.valueOf(totalAmount) + "\n";

result += "You earned " + String.valueOf(frequentRenterPoints) +

" frequent renter points";

return result;

}

Page 30 © Martin Fowler 9/11/99

After moving charge and After moving charge and frequent renter pointsfrequent renter points

[1statement()

Customer

getCharge()getFrequentRenterPoints()

daysRented: int

Rental

priceCode: int

Movie

aCustomer aRental aMovie

getCharge

* [for all rentals]

getPriceCode

statement

getFrequentRenterPointsgetPriceCode

Page 16: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

16

Page 31 © Martin Fowler 9/11/99

More temps to killMore temps to kill

class Customer...

public String statement() {

double totalAmount = 0;

int frequentRenterPoints = 0;

Enumeration rentals = _rentals.elements();

String result = "Rental Record for " + getName() + "\n";

while (rentals.hasMoreElements()) {

Rental each = (Rental) rentals.nextElement();

frequentRenterPoints += each.getFrequentRenterPoints();

//show figures for this rental

result += "\t" + each.getMovie().getTitle()+ "\t" +

String.valueOf(each.getCharge()) + "\n";

totalAmount += each.getCharge();

}

//add footer lines

result += "Amount owed is " + String.valueOf(totalAmount) + "\n";

result += "You earned " + String.valueOf(frequentRenterPoints) +

" frequent renter points";

return result;

}

Page 32 © Martin Fowler 9/11/99

The new methodsThe new methodsclass Customer…

private double getTotalCharge() {

double result = 0;

Enumeration rentals = _rentals.elements();

while (rentals.hasMoreElements()) {

Rental each = (Rental) rentals.nextElement();

result += each.getCharge();

}

return result;

}

private int getTotalFrequentRenterPoints(){

int result = 0;

Enumeration rentals = _rentals.elements();

while (rentals.hasMoreElements()) {

Rental each = (Rental) rentals.nextElement();

result += each.getFrequentRenterPoints();

}

return result;

}

Page 17: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

17

Page 33 © Martin Fowler 9/11/99

The temps removedThe temps removed

public String statement() {

Enumeration rentals = _rentals.elements();

String result = "Rental Record for " + getName() + "\n";

while (rentals.hasMoreElements()) {

Rental each = (Rental) rentals.nextElement();

//show figures for this rental

result += "\t" + each.getMovie().getTitle()+ "\t" +

String.valueOf(each.getCharge()) + "\n";

}

//add footer lines

result += "Amount owed is " + String.valueOf(getTotalChargegetTotalCharge()()) + "\n";

result += "You earned " + String.valueOf(getTotalFrequentRenterPointsgetTotalFrequentRenterPoints()()) +

" frequent renter points";

return result;

}

Page 34 © Martin Fowler 9/11/99

After replacing the totalsAfter replacing the totals

[1statement()getTotalCharge()getTotalFrequentRenterPoints()

Customer

getCharge()getFrequentRenterPoints()

daysRented: int

Rental

priceCode: int

Movie

aCustomer aRental aMovie

* [for all rentals] getCharge

getTotalCharge

getPriceCode

statement

* [for all rentals] getFrequentRenterPoints

getPriceCode

getTotalFrequentRenterPoints

Page 18: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

18

Page 35 © Martin Fowler 9/11/99

htmlStatementhtmlStatement()()

public String htmlStatement() {

Enumeration rentals = _rentals.elements();

String result = "<H1>Rentals for <EM>" + getName() + "</EM></H1><P>\n";

while (rentals.hasMoreElements()) {

Rental each = (Rental) rentals.nextElement();

//show figures for each rental

result += each.getMovie().getTitle()+ ": " +

String.valueOf(each.getCharge()) + "<BR>\n";

}

//add footer lines

result += "<P>You owe <EM>" + String.valueOf(getTotalCharge()) +

"</EM><P>\n";

result += "On this rental you earned <EM>" +

String.valueOf(getTotalFrequentRenterPoints()) +

"</EM> frequent renter points<P>";

return result;

}

Page 36 © Martin Fowler 9/11/99

The current The current getChargegetCharge method method

class Rental ...

double getCharge() {

double result = 0;

switch (getMovie().getPriceCode()) {

case Movie.REGULAR:

result += 2;

if (getDaysRented() > 2)

result += (getDaysRented() - 2) * 1.5;

break;

case Movie.NEW_RELEASE:

result += getDaysRented() * 3;

break;

case Movie.CHILDRENS:

result += 1.5;

if (getDaysRented() > 3)

result += (getDaysRented() - 3) * 1.5;

break;

}

return result;

}

Page 19: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

19

Page 37 © Martin Fowler 9/11/99

getChargegetCharge moved to Movie moved to Movieclass Rental...

double getCharge() {

return _movie.getCharge(_daysRented);

}

class Movie …

double getCharge(int daysRented) {

double result = 0;

switch (getPriceCode()) {

case Movie.REGULAR:

result += 2;

if (daysRented > 2)

result += (daysRented - 2) * 1.5;

break;

case Movie.NEW_RELEASE:

result += daysRented * 3;

break;

case Movie.CHILDRENS:

result += 1.5;

if (daysRented > 3)

result += (daysRented - 3) * 1.5;

break;

}

return result;

}

q Do the same with frequentRenterPoints()

Page 38 © Martin Fowler 9/11/99

Consider inheritanceConsider inheritance

How’s this?

getCharge

Movie

getCharge

Regular Movie

getCharge

Childrens Movie

getCharge

New Release Movie

Page 20: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

20

Page 39 © Martin Fowler 9/11/99

Using the State PatternUsing the State Pattern

getCharge

Price

getCharge

Regular Price

getCharge

Childrens Price

getCharge

New Release Price

getCharge

Movie

1

return price.getCharge

Page 40 © Martin Fowler 9/11/99

Replace Type Code with State/StrategyReplace Type Code with State/Strategy

ENGINEER : intSALESMAN : inttype : int

Employee

Employee Type

Engineer Salesman

Employee1

You have a type code which affects the behavior of a classbut you cannot use subclassing.

Replace the type code with a state object.

Page 21: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

21

Page 41 © Martin Fowler 9/11/99

Steps for Steps for Replace Type Code with Replace Type Code with State/StrategyState/Strategy

q Create a new state class for the type codeq Add subclasses of the state object, one

for each type code.q Create an abstract query in the superclass

to return the type code. Override insubclasses to return correct type code

q Compileq Create field in old class for the state

object.q Change the type code query to delegate

to the state object.q Change the type code setting methods to

assign an instance of the subclass.q Compile and test.

Page 42 © Martin Fowler 9/11/99

Price codes on the price hierarchyPrice codes on the price hierarchyabstract class Price {

abstract int getPriceCode();

}

class ChildrensPrice extends Price {

int getPriceCode() {

return Movie.CHILDRENS;

}

}

class NewReleasePrice extends Price {

int getPriceCode() {

return Movie.NEW_RELEASE;

}

}

class RegularPrice extends Price {

int getPriceCode() {

return Movie.REGULAR;

}

}

Page 22: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

22

Page 43 © Martin Fowler 9/11/99

Change accessors on MovieChange accessors on Movie

public int getPriceCode() {

return _price.getPriceCode();

}

public void setPriceCode(int arg) {

switch (arg) {

case REGULAR:

_price = new RegularPrice();

break;

case CHILDRENS:

_price = new ChildrensPrice();

break;

case NEW_RELEASE:

_price = new NewReleasePrice();

break;

default:

throw new IllegalArgumentException("Incorrect Price Code");

}

}

private Price _price;

public int getPriceCode() {

return _priceCode;

}

public setPriceCode (int arg) {

_priceCode = arg;

}

private int _priceCode;

Page 44 © Martin Fowler 9/11/99

Replace Conditional With Replace Conditional With PolymorphsimPolymorphsim

getSpeed

Bird

getSpeed

European

getSpeed

African

getSpeed

Norweigian Blue

double getSpeed() {switch (_type) {

case EUROPEAN:return getBaseSpeed();

case AFRICAN:return getBaseSpeed() - getLoadFactor() * _numberOfCoconuts;

case NORWEIGIAN_BLUE:return (_isNailed) ? 0 : getBaseSpeed(_voltage);

}throw new RuntimeException ("Should be unreachable");

}

You have a conditional that chooses different behaviordepending on the type of an object

Move each leg of the conditional to an overriding methodin a subclass. Make the original method abstract

Page 23: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

23

Page 45 © Martin Fowler 9/11/99

Steps for Steps for Replace Conditional with Replace Conditional with PolymorphismPolymorphism

q Move switch to superclass of inheritancestructure

q Copy one leg of case statement intosubclass

q Compile and testq Repeat for all other legsq Replace case statement with abstract

method

Page 46 © Martin Fowler 9/11/99

Move Move getChargegetCharge to Price to Price

class Movie…

double getCharge(int daysRented) {

return _price.getCharge(daysRented);

}

class Price…

double getCharge(int daysRented) {

double result = 0;

switch (getPriceCode()) {

case Movie.REGULAR:

result += 2;

if (daysRented > 2)

result += (daysRented - 2) * 1.5;

break;

case Movie.NEW_RELEASE:

result += daysRented * 3;

break;

case Movie.CHILDRENS:

result += 1.5;

if (daysRented > 3)

result += (daysRented - 3) * 1.5;

break;

}

return result;

}

Page 24: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

24

Page 47 © Martin Fowler 9/11/99

Override Override getChargegetCharge in the in the subclassessubclasses

Class RegularPrice…

double getCharge(int daysRented){

double result = 2;

if (daysRented > 2)

result += (daysRented - 2) * 1.5;

return result;

}

Class ChildrensPrice

double getCharge(int daysRented){

double result = 1.5;

if (daysRented > 3)

result += (daysRented - 3) * 1.5;

return result;

}

Class NewReleasePrice…

double getCharge(int daysRented){

return daysRented * 3;

}

Class Price…

abstract double getCharge(int daysRented);

q Do each leg one at a timeq then...

Page 48 © Martin Fowler 9/11/99

Similar Statement MethodsSimilar Statement Methods public String statement() {

Enumeration rentals = _rentals.elements();

String result = "Rental Record for " + getName() + "\n";

while (rentals.hasMoreElements()) {

Rental each = (Rental) rentals.nextElement();

result += "\t" + each.getMovie().getTitle()+ "\t" +

String.valueOf(each.getCharge()) + "\n";

}

result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n";

result += "You earned " + String.valueOf(getTotalFrequentRenterPoints()) +

" frequent renter points";

return result;

}

public String htmlStatement() {

Enumeration rentals = _rentals.elements();

String result = "<H1>Rentals for <EM>" + getName() + "</EM></H1><P>\n";

while (rentals.hasMoreElements()) {

Rental each = (Rental) rentals.nextElement();

result += each.getMovie().getTitle()+ ": " +

String.valueOf(each.getCharge()) + "<BR>\n";

}

result += "<P>You owe <EM>" +

String.valueOf(getTotalCharge()) + "</EM><P>\n";

result += "On this rental you earned <EM>" +

String.valueOf(getTotalFrequentRenterPoints()) +

"</EM> frequent renter points<P>";

return result;

}

Page 25: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

25

Page 49 © Martin Fowler 9/11/99

Form Template MethodForm Template Method

getBillableAmountgetBaseAmountgetTaxAmount

Site

Site

getBillableAmount

Residential Site

getBillableAmount

Lifeline Site

double base = _units * _rate;double tax = base * Site.TAX_RATE;return base + tax;

double base = _units * _rate * 0.5;double tax = base * Site.TAX_RATE * 0.2;return base + tax;

return getBaseAmount() + getTaxAmount();

getBaseAmountgetTaxAmount

Residential Site

getBaseAmountgetTaxAmount

LifelineSite

You have two methods in subclasses that carry out similarsteps in the same order, yet the steps are different

Give each step into methods with the same signature, so thatthe original methods become the same. Then you can pull

them up.

Page 50 © Martin Fowler 9/11/99

Steps for Steps for Form Template MethodForm Template Method

q Take two methods with similar overallstructure but varying piecesâ Use subclasses of current class, or create a

strategy and move the methods to thestrategy

q At each point of variation extract methodsfrom each source with the the samesignature but different body.

q Declare signature of extracted method insuperclass and place varying bodies insubclasses

q When all points of variation have beenremoved, move one source method tosuperclass and remove the other.

Page 26: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

26

Page 51 © Martin Fowler 9/11/99

Create a Statement StrategyCreate a Statement Strategyclass Customer …

public String statement() {

return new TextStatement().value(this);

}

class TextStatement {

public String value(Customer aCustomer) {

Enumeration rentals = aCustomer.getRentals();

String result = "Rental Record for " + aCustomer.getName() + "\n";

while (rentals.hasMoreElements()) {

Rental each = (Rental) rentals.nextElement();

result += "\t" + each.getMovie().getTitle()+ "\t" +

String.valueOf(each.getCharge()) + "\n";

}

result += "Amount owed is " +

String.valueOf(aCustomer.getTotalCharge()) + "\n";

result += "You earned " +

String.valueOf(aCustomer.getTotalFrequentRenterPoints()) +

" frequent renter points";

return result;

}

q Do the same with htmlStatement()

Page 52 © Martin Fowler 9/11/99

Extract DifferencesExtract Differencesclass TextStatement...

public String value(Customer aCustomer) {

Enumeration rentals = aCustomer.getRentals();

String result = headerString(aCustomer);

while (rentals.hasMoreElements()) {

Rental each = (Rental) rentals.nextElement();

result += "\t" + each.getMovie().getTitle()+ "\t" +

String.valueOf(each.getCharge()) + "\n";

}

result += "Amount owed is " +

String.valueOf(aCustomer.getTotalCharge()) + "\n";

result += "You earned " +

String.valueOf(aCustomer.getTotalFrequentRenterPoints()) +

" frequent renter points";

return result;

}

String headerString(Customer aCustomer) {

return "Rental Record for " + aCustomer.getName() + "\n";

}

q Do the same with htmlStatementclass HtmlStatement...

String headerString(Customer aCustomer) {

return "<H1>Rentals for <EM>" + aCustomer.getName() + "</EM></H1><P>\n";

}

Page 27: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

27

Page 53 © Martin Fowler 9/11/99

Continue extractingContinue extractingclass TextStatement …

public String value(Customer aCustomer) {

Enumeration rentals = aCustomer.getRentals();

String result = headerString(aCustomer);

while (rentals.hasMoreElements()) {

Rental each = (Rental) rentals.nextElement();

result += eachRentalString(each);

}

result += footerString(aCustomer);

return result;

}

String eachRentalString (Rental aRental) {

return "\t" + aRental.getMovie().getTitle()+ "\t" +

String.valueOf(aRental.getCharge()) + "\n";

}

String footerString (Customer aCustomer) {

return "Amount owed is " +

String.valueOf(aCustomer.getTotalCharge()) + "\n" +

"You earned " +

String.valueOf(aCustomer.getTotalFrequentRenterPoints()) +

" frequent renter points";

}

q Do the same with htmlStatement

Page 54 © Martin Fowler 9/11/99

Pull up the value methodPull up the value method

class Statement...

public String value(Customer aCustomer) {

Enumeration rentals = aCustomer.getRentals();

String result = headerString(aCustomer);

while (rentals.hasMoreElements()) {

Rental each = (Rental) rentals.nextElement();

result += eachRentalString(each);

}

result += footerString(aCustomer);

return result;

}

abstract String headerString(Customer aCustomer);

abstract String eachRentalString (Rental aRental);

abstract String footerString (Customer aCustomer);

Page 28: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

28

Page 55 © Martin Fowler 9/11/99

State of ClassesState of Classes

statement()htmlStatement()

Customer

value(Customer)headerString(Customer)eachRentalString(Rental)footerString(Customer)

Statement

1

headerString(Customer)eachRentalString(Rental)footerString(Customer)

Text Statement

headerString(Customer)eachRentalString(Rental)footerString(Customer)

Html Statement

Page 56 © Martin Fowler 9/11/99

In this exampleIn this example

q We saw a poorly factored programimprovedâ easier to add new services on customerâ easier to add new types of movie

q No debugging during refactoringâ appropriate steps reduce chance of bugsâ small steps make bugs easy to find

q Illustrated several refactoringsâ Extract Methodâ Move Methodâ Replace Temp with Queryâ Replace Type Code with State/Strategyâ Replace Switch with Polymorphismâ Form Template Method

Page 29: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

29

Page 57 © Martin Fowler 9/11/99

Definitions of RefactoringDefinitions of Refactoring

q Loose Usageâ Reorganize a program (or something)

q As a nounâ a change made to the internal structure of

some software to make it easier tounderstand and cheaper to modify, withoutchanging the observable behavior of thatsoftware

q As a verbâ the activity of restructuring software by

applying a series of refactorings withoutchanging the observable behavior of thatsoftware.

Page 58 © Martin Fowler 9/11/99

Where Refactoring Came FromWhere Refactoring Came From

q Ward Cunningham and Kent BeckâSmalltalk style

q Ralph Johnson at University of Illinois atUrbana-Champaign

q Bill Opdyke’s Thesisftp://st.cs.uiuc.edu/pub/papers/refactoring/opdyke-thesis.ps.Z

q John Brant and Don Roberts: TheRefactoring Browser

Page 30: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

30

Page 59 © Martin Fowler 9/11/99

Refactoring ToolsRefactoring Tools

q Based on provable transformationsâ Build parse tree of programsâ Mathematic proof that refactoring does not

change semanticsâ Embed refactoring in tool

q Speeds up refactoringâ Extract method: select code, type in method

name.â No need for tests (unless dynamic reflection)â Big speed improvement

q Not Science Fictionâ Available for Smalltalkhttp://st-www.cs.uiuc.edu/~brant/RefactoringBrowser

Page 60 © Martin Fowler 9/11/99

The Importance of TestsThe Importance of Tests

q Even with a tool, testing is importantâ Not all refactorings can be proven

q Write tests as you write the codeq Make the test self-checking

â return “OK” if good, errors if not

q Run a suite with a single commandq Test with every compile

ç ftp://www.armaties.com/D/home/armaties/ftp/TestingFramework/

ç http://ourworld.compuserve.com/homepages/Martin_Fowler

Page 31: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

31

Page 61 © Martin Fowler 9/11/99

The Two HatsThe Two Hats

Adding Function

q Add new capabilities tothe system

q Adds new testsq Get the test working

Refactoring

q Does not add any newfeatures

q Does not add tests (butmay change some)

q Restructure the code toremove redundancy

Swap frequently between the hats, butonly wear one at a time

Page 62 © Martin Fowler 9/11/99

Why RefactorWhy Refactor

q To improve the software designâ combat’s “bit rot”â makes the program easier to change

q To make the software easier tounderstandâ write for people, not the compilerâ understand unfamiliar code

q To help find bugsâ refactor while debugging to clarify the code

Refactoring helps you program faster!

Page 32: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

32

Page 63 © Martin Fowler 9/11/99

When should you refactor?When should you refactor?

q The Rule of Threeq To add new functionality

â refactor existing code until you understand itâ refactor the design to make it easy to add

q To find bugsâ refactor to understand the code

q For code reviewsâ immediate effect of code reviewâ allows for higher level suggestions

Don’t set aside time for refactoring, include itin your normal activities

Page 64 © Martin Fowler 9/11/99

What do you tell your managerWhat do you tell your manager

q If the manager is really concerned aboutqualityâ then stress the quality aspects

q Otherwise you need to develop as fast aspossibleâ you’re the professional, so you know to do

what makes you go faster

Page 33: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

33

Page 65 © Martin Fowler 9/11/99

Problems with RefactoringProblems with Refactoring

q We don’t know what they are yetq Database Migration

â Insulate persistent database structure fromyour objects

â With OO databases, migrate frequently

q Published Interfacesâ Publish only when you need toâ Don’t publish within a development team

q Without working testsâ Don’t bother

Page 66 © Martin Fowler 9/11/99

Design DecisionsDesign Decisions

q In the momentâ Consider current needsâ Patch code when new needs appear

q Up front designâ Consider current needs and possible future

needsâ Design to minimize change with future needsâ Patch code if unforeseen need appears

q Evolutionary designâ Consider current needs and possible future

needsâ Trade off cost of current flexibility versus cost

of later refactoringâ Refactor as changes appear

Page 34: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

34

Page 67 © Martin Fowler 9/11/99

Extreme ProgrammingExtreme Programming

q Methodology developed by Kent Beckq Designed to adapt to changesq Key Practices

â Iterative Developmentâ Self Testing Codeâ Refactoringâ Pair Programming

q Moves away from up-front design

http://www.armaties.com/extreme.htm

©

Page 68 © Martin Fowler 9/11/99

Team TechniquesTeam Techniques

q Encourage refactoring cultureâ nobody gets things right first timeâ nobody can write clear code without reviewsâ refactoring is forward progress

q Provide sound testing baseâ tests are essential for refactoringâ build system and run tests daily

q Pair Programmingâ two programmers working together can be

quicker than working separatelyâ refactor with the class writer and a class user

Page 35: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

35

Page 69 © Martin Fowler 9/11/99

Creating Your Own RefactoringsCreating Your Own Refactorings

q Consider a change to a programq Should it change the external behavior of

the systemq Break down the change into small steps

â Look for points where you can compile andtest

q Carry out the change, note what you doâ If a problem occurs, consider how to

eliminate it in future

q Carry it out again, follow and refine thenotes

q After two or three times you have aworkable refactoring

Page 70 © Martin Fowler 9/11/99

Self Testing CodeSelf Testing Code

q For each piece of new functionâ Write the testâ Write the production codeâ Run your test suiteâ If it works you’re done

q Developersâ Do this with every small bit of function you

add

q QA or Test Groupâ Do this with each increment

Build and run tests as you build productioncode

Page 36: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

36

Page 71 © Martin Fowler 9/11/99

The JUnit FrameworkThe JUnit Framework

q Simple, but effective framework forcollecting and running unit tests in Java

q Written by Erich Gamma and Kent Beckâ based on Kent’s framework for Smalltalk

q Easily define testsq Easily group tests into suitesq Easily run suites and monitor results

ftp://www.armaties.com/D/home/armaties/ftp/TestingFramework/JUnit/

Page 72 © Martin Fowler 9/11/99

An Example Coding SessionAn Example Coding Session

q Build a Money classâ combines amount and currencyâ provides arithmetic operationsâ use of Quantity pattern

q Build a MoneyTester class

Fowler, Martin. Analysis Patterns: Reusable ObjectModels, Addison Wesley 1997

Page 37: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

37

Page 73 © Martin Fowler 9/11/99

Fitting into the FrameworkFitting into the Framework

Test«interface»

TestSuite TestCase

MoneyTester

test.framework

money

Money

Page 74 © Martin Fowler 9/11/99

The The Junit Junit GUIGUI

q There is also a text console interfaceq Invoke with

â javajava test. test.textuitextui..TestRunner MoneyTesterTestRunner MoneyTester

Page 38: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

38

Page 75 © Martin Fowler 9/11/99

Creating Creating MoneyTesterMoneyTester

import test.framework.*;

public class MoneyTester extends TestCase{

public MoneyTester(String name) {super(name);

}public static Test suite() {

return new TestSuite(MoneyTester.class);}

}

Page 76 © Martin Fowler 9/11/99

The First TestThe First Test

MoneyTesterMoneyTesterpublic void testPrint() {

Money d15 = new Money(15, "USD");assertEquals("15 USD", d15.toString());

}

public class Money {private long _amountInPennies;private String _currencyCode;

public Money(double amount, String currencyCode) {_amountInPennies = Math.round (amount * 100);_currencyCode = currencyCode;

}public String toString() {

return ("" + _amountInPennies + " " + _currencyCode);}

}

Page 39: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

39

Page 77 © Martin Fowler 9/11/99

First Test ResultFirst Test Result

Page 78 © Martin Fowler 9/11/99

Fixing the First TestFixing the First Test

public void testPrint() {Money d15 = new Money(15, "USD");assertEquals("15 USD", d15.toString());

}

public class Money {private long _amountInPennies;private String _currencyCode;

public Money(double amount, String currencyCode) {_amountInPennies = Math.round (amount * 100);_currencyCode = currencyCode;

}public String toString() {

return ("" + getAmountgetAmount()() + " " + _currencyCode);}private doubleprivate double getAmount getAmount() {() {

return (double) _return (double) _amountInPenniesamountInPennies / 100; / 100;}}

}

Page 40: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

40

Page 79 © Martin Fowler 9/11/99

After the fixAfter the fix

Page 80 © Martin Fowler 9/11/99

Changing the TestChanging the Test

public void testPrint() {Money d15 = new Money(15, "USD");assertEquals("15.0.0 USD", d15.toString());

}

q Don’t consider fancy formatting yet

Page 41: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

41

Page 81 © Martin Fowler 9/11/99

Checking RoundingChecking Rounding

public void testRound() {Money dRounded = new Money (1.2350, "USD");assertEquals ("1.24 USD", dRounded.toString());

}

Page 82 © Martin Fowler 9/11/99

Adding AdditionAdding Addition

q Add two monies together in the samecurrency

public void testAddition() {Money d15 = new Money (15, "USD");Money d2_51 = new Money (2.51, "USD");assertEquals (new Money (17.51, "USD"),

d15.plus(d2_51));}

Page 42: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

42

Page 83 © Martin Fowler 9/11/99

Need to add equalsNeed to add equals

public void testEquals() {Money d2_51a = new Money (2.51, "USD");Money d2_51b = new Money (2.51, "USD");assertEquals (d2_51a, d2_51b);

}

q Money is a value, so needs a specialequals (and hash)

Page 84 © Martin Fowler 9/11/99

The Equals MethodThe Equals MethodMoneyMoneypublic boolean equals (Object arg) {

if (! (arg instanceof Money)) return false;Money moneyArg = (Money) arg;return (_amountInPennies == moneyArg._amountInPennies &&

_currencyCode.equals(moneyArg._currencyCode));}

Page 43: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

43

Page 85 © Martin Fowler 9/11/99

Additional TestsAdditional Tests

public void testCloseNumbersNotEqual() {Money d2_51a = new Money (2.515, "USD");Money d2_51b = new Money (2.5149, "USD");assert(! d2_51a.equals(d2_51b));

}public void testDifferentCurrencyNotEqual() {

Money d2_51a = new Money (2.51, "USD");Money d2_51b = new Money (2.51, "DEM");assert(! d2_51a.equals(d2_51b));

}

Page 86 © Martin Fowler 9/11/99

Testing Testing HashCodeHashCode

MoneyTesterMoneyTesterpublic void testHash() {

Money d2_51a = new Money (2.51, "USD");Money d2_51b = new Money (2.51, "USD");assertEquals (d2_51a.hashCode(), d2_51b.hashCode());

}

MoneyMoneypublic int hashCode() {

return _currencyCode.hashCode() ^(int) _amountInPennies;

}

Page 44: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

44

Page 87 © Martin Fowler 9/11/99

The addition methodThe addition methodpublic Money plus (Money arg) {

return new Money (_amountInPennies + arg._amountInPennies,

_currencyCode);}

Page 88 © Martin Fowler 9/11/99

Addition with a marked Addition with a marked constructorconstructor

public Money plus (Money arg) {return new Money (

_amountInPennies + arg._amountInPennies,_currencyCode,false);

}

private Money (long amountInPennies, String currencyCode,boolean privacyMarker)

{_amountInPennies = amountInPennies;_currencyCode = currencyCode;

}

Page 45: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

45

Page 89 © Martin Fowler 9/11/99

Adding different currenciesAdding different currencies

q For this application, we will treat this asan errorâ An alternative is the MoneyBag pattern

public void testAdditionOfDifferentCurrencies() {Money d15 = new Money (15, "USD");Money m2_51 = new Money (2.51, "DEM");try {

d15.plus(m2_51);assert (false);

} catch (IllegalArgumentException e) {}}

Page 90 © Martin Fowler 9/11/99

The new plus methodThe new plus method

public Money plus (Money arg) {if (! _currencyCode.equals(arg._currencyCode))

throw new IllegalArgumentException("Cannot add different currencies");

return new Money (_amountInPennies + arg._amountInPennies,_currencyCode, false);

}

Page 46: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

46

Page 91 © Martin Fowler 9/11/99

Duplication of test setup codeDuplication of test setup code

public void testAdditionOfDifferentCurrencies() {Money d15 = new Money (15, "USD")d15 = new Money (15, "USD");Money m2_51 = new Money (2.51, "DEM");try {

d15.plus(m2_51);assert (false);

} catch (IllegalArgumentException e) {}}public void testAddition() {

Money d15 = new Money (15, "USD")d15 = new Money (15, "USD");Money d2_51 = new Money (2.51, "USD");assertEquals (new Money (17.51, "USD"), d15.plus(d2_51));

}public void testDifferentCurrencyNotEqual() {

Money d2_51a = new Money (2.51, "USD");Money d2_51b = new Money (2.51, "DEM");assert(! d2_51a.equals(d2_51b));

}

Page 92 © Martin Fowler 9/11/99

Create a test fixtureCreate a test fixture

public class MoneyTester extends TestCase{private Money d15;private Money d2_51;private Money m2_51;

public void setUp() {d15 = new Money (15, "USD");d2_51 = new Money (2.51, "USD");m2_51 = new Money (2.51, "DEM");

}

public void testDifferentCurrencyNotEqual() {assert(! d2_51.equals(m2_51));

}

Page 47: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

47

Page 93 © Martin Fowler 9/11/99

Adding SubtractionAdding Subtraction

MoneyTesterMoneyTesterpublic void testSubtraction() {

assertEquals (new Money (12.49, "USD"),d15.minus(d2_51));}

MoneyMoneypublic Money minus (Money arg) {

if (! _currencyCode.equals(arg._currencyCode))throw new IllegalArgumentException ("Cannot add

different currencies");return new Money (_amountInPennies -

arg._amountInPennies, _currencyCode, false);}

Page 94 © Martin Fowler 9/11/99

Duplicate CodeDuplicate Code

public Money minus (Money arg) {if (! _if (! _currencyCodecurrencyCode.equals(.equals(argarg._._currencyCodecurrencyCode))))

throw newthrow new IllegalArgumentException IllegalArgumentException ("Cannot add ("Cannot adddifferent currencies");different currencies");

return new Money (_amountInPennies -arg._amountInPennies, _currencyCode, false);}public Money plus (Money arg) {

if (! _if (! _currencyCodecurrencyCode.equals(.equals(argarg._._currencyCodecurrencyCode))))throw newthrow new IllegalArgumentException IllegalArgumentException ("Cannot add ("Cannot add

different currencies");different currencies");return new Money (_amountInPennies +

arg._amountInPennies, _currencyCode, false);}

q Kill such snakes immediately

Page 48: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

48

Page 95 © Martin Fowler 9/11/99

Extract MethodsExtract Methodspublic Money minus (Money arg) {

assertSameCurrencyassertSameCurrency((argarg););return new Money (_amountInPennies -

arg._amountInPennies, _currencyCode, false);}

public Money plus (Money arg) {assertSameCurrencyassertSameCurrency((argarg););return new Money (_amountInPennies +

arg._amountInPennies, _currencyCode, false);}

public void assertSameCurrency (Money arg) {if (! _currencyCode.equals(arg._currencyCode))

throw new IllegalArgumentException ("Currencies mustbe the same");}

q Make it work, make it right

Page 96 © Martin Fowler 9/11/99

Next Step: Local PrintingNext Step: Local Printing

q Leave other arithmetic and sortoperations to the reader

q Provide a localString method that formatsthe currency in the native locale of thecurrency

q We need a currency classâ refactor the money class to use a currency:

q Define the test, but don’t run it yet

public void xxtestLocalPrinting() {//assertEquals("$15.00", d15.localString());//assertEquals("2,51 DM", m2_51.localString());

}

Page 49: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

49

Page 97 © Martin Fowler 9/11/99

Replace Data Value with ObjectReplace Data Value with Object

customer: String

Order

name: String

CustomerOrder

1

You have a data item that needs additional data or behaviorTurn the data item into an object

Page 98 © Martin Fowler 9/11/99

Replace Data Value with Replace Data Value with ObjectObjectMoneyMoney

public Money(double amount, String currencyCode) {_amountInPennies = Math.round (amount * 100);_currency = new Currency(_currency = new Currency(currencyCodecurrencyCode));

}public boolean equals (Object arg) {

if (! (arg instanceof Money)) return false;Money moneyArg = (Money) arg;return (_amountInPennies == moneyArg._amountInPennies &&

_currency.getCodegetCode()().equals(moneyArg._currency.getCodegetCode()()));}private long _amountInPennies;private Currency _currency;private Currency _currency;

CurrencyCurrencypublic Currency(String code) {

_code = code;}public String getCode() {

return _code;}private String _code;

Page 50: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

50

Page 99 © Martin Fowler 9/11/99

Code in the wrong placeCode in the wrong placeMoneyMoney

public boolean equals (Object arg) {if (! (arg instanceof Money)) return false;Money moneyArg = (Money) arg;return (_amountInPennies == moneyArg._amountInPennies &&

_currency._currency.getCodegetCode().equals(().equals(moneyArgmoneyArg._currency.._currency.getCodegetCode()));()));}

Page 100 © Martin Fowler 9/11/99

Move MethodMove Method

MoneyMoneypublic boolean equals (Object arg) {

if (! (arg instanceof Money)) return false;Money moneyArg = (Money) arg;return (_amountInPennies == moneyArg._amountInPennies &&

_currency.equals(moneyArg._currency));}CurrencyCurrencypublic boolean equals(Object arg) {

if (! (arg instanceof Currency)) return false;Currency currencyArg = (Currency) arg;return (_code.equals(currencyArg._code));

}

Page 51: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

51

Page 101 © Martin Fowler 9/11/99

Currency is a valueCurrency is a value

MoneyMoneypublic Money(double amount, String currencyCode) {

_amountInPennies = Math.round (amount * 100);_currency = new Currency(currencyCode);

}

Money Currency

Page 102 © Martin Fowler 9/11/99

Replace Constructor with Replace Constructor with Factory MethodFactory Method

You want to do more than simple construction when youcreate an object

Replace the constructor with a factory method

Employee (int type) {_type = type;

}

static Employee create(int type) {return new Employee(type);

}

Page 52: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

52

Page 103 © Martin Fowler 9/11/99

Replacing the ConstructorReplacing the Constructor

class Currency...class Currency...public static Currency create (String code) {

return new Currency (code);}private Currency(String code) {

_code = code;}

class Money...class Money...public Money(double amount, String currencyCode) {

_amountInPennies = Math.round (amount * 100);_currency = Currency.create(currencyCode);

}

Page 104 © Martin Fowler 9/11/99

Replace value object Replace value object with reference objectwith reference object

class Currency...class Currency...private String _code;private static Dictionary _instances = new Hashtable();

public static void loadInstances() {_instances.put("USD", new Currency("USD"));_instances.put("GBP", new Currency("GBP"));_instances.put("DEM", new Currency("DEM"));

}

public static Currency create (String code) {Currency result = (Currency) _instances.get(code);if (result == null)

throw new IllegalArgumentException("There is no currency with code: " + code);

return result;}class class MoneyTesterMoneyTester……public void setUp() {

Currency.Currency.loadInstancesloadInstances();();d15 = new Money (15, "USD");d2_51 = new Money (2.51, "USD");m2_51 = new Money (2.51, "DEM");

}

Page 53: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

53

Page 105 © Martin Fowler 9/11/99

Add the locale to currencyAdd the locale to currency

class Currency...private Currency(String code, Locale locale) {

_code = code;_locale = locale;

}public static void loadInstances() {

_instances.put("USD", new Currency("USD", Locale.US));_instances.put("GBP", new Currency("GBP", Locale.UK));_instances.put("DEM", new Currency("DEM",

Locale.GERMANY));}

private Locale _locale;

Page 106 © Martin Fowler 9/11/99

Add methods for printingAdd methods for printing

class Money...class Money...public String localString() {

return _currency.getFormat().format(getAmount());}class Currency...class Currency...public NumberFormat getFormat() {

return NumberFormat.getCurrencyInstance(_locale);}

q Enable the test

Page 54: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

54

Page 107 © Martin Fowler 9/11/99

The Rhythm of DevelopmentThe Rhythm of Development

q Define a testq Refactor to make it easy to add the

functionq Add functionalityq Enable the testq Refactor to remove any bad smellsq Integrate

Page 108 © Martin Fowler 9/11/99

Daily BuildDaily Build

q Build system every dayâ compile, link, and unit tests at 100%â Anyone who breaks build must fix it

immediately

q Developers should check in dailyâ If more than 2 days - raise flagâ break down coding effort for intermediate

buildâ developers do personal build before checking

in

q Assign a build token

Page 55: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

55

Page 109 © Martin Fowler 9/11/99

Code SmellsCode Smells

© Martin Fowler, 1999

Martin FowlerMartin Fowler

fowlerfowler@@acmacm.org.orghttp://http://ourworldourworld..compuservecompuserve.com/homepages/Martin_Fowler.com/homepages/Martin_Fowler

Page 110 © Martin Fowler 9/11/99

Bad Smells in CodeBad Smells in Code

q How do we know when to refactorq No hard and fast rulesq Bad Smells are things to look for

â suggest certain refactorings

“If it stinks, change it.”— Grandma Beck, discussing child raising philosophy

Page 56: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

56

Page 111 © Martin Fowler 9/11/99

Duplicated CodeDuplicated Code

q Same expression in two methods of thesame classâ Use Extract Method

q Same expression in sibling subclassesâ Extract Method and Pull Up Method

q Similar code in sibling subclassesâ Use Form Template Method

q Same code in unrelated classesâ Decide where it should really be and use

Move Method to get it there.â May be a signal for Extract Class

Page 112 © Martin Fowler 9/11/99

Pull Up MethodPull Up Method

Employee

Salesman Engineer

getName

Salesman

getName

Engineer

getName

Employee

You have methods with identical results on subclassesMove them to the superclass

Page 57: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

57

Page 113 © Martin Fowler 9/11/99

Extract ClassExtract Class

getTelephoneNumber

nameofficeAreaCodeofficeNumber

Person

getTelephoneNumber

name

Person

getTelephoneNumber

areaCodenumber

Telephone Number

officeTelephone

1

You have a class that is doing the work that should be doneby two.

Create a new class and move the relevant fields andmethods from the old class into the new class.

Page 114 © Martin Fowler 9/11/99

Long MethodLong Method

q Use Extract Method on logical chunksâ For conditions: Decompose Conditional

q Lots of temps make extraction difficultâ Use Replace Temp with Queryâ For parameters use Introduce Parameter

Object and Preserve Whole Objectâ As a last resort use Replace Method with

Method Object

Page 58: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

58

Page 115 © Martin Fowler 9/11/99

Decompose ConditionalDecompose Conditional

if (date.before (SUMMER_START) || date.after(SUMMER_END)) charge = quantity * _winterRate + _winterServiceCharge;

else charge = quantity * _summerRate;

if (notSummer(date)) charge = winterCharge(quantity);

else charge = summerCharge (quantity);

You have a complicated conditional (if-then-else) statementExtract methods from the condition, then part, and else

parts.

Page 116 © Martin Fowler 9/11/99

Replace Method with Method ObjectReplace Method with Method Object

price()

Order

compute

primaryBasePricesecondaryBasePricetertiaryBasePrice

PriceCalculator

1

return new PriceCalculator(this).compute()

class Order...double price() {

double primaryBasePrice;double secondaryBasePrice;double tertiaryBasePrice;// long computation;...

}

You have a long method that uses local variables in such away that you cannot apply Extract Method

Turn the method into its own object.

Page 59: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

59

Page 117 © Martin Fowler 9/11/99

Preserve Whole ObjectPreserve Whole Object

int low = daysTempRange().getLow();int high = daysTempRange().getHigh();withinPlan = plan.withinRange(low, high);

withinPlan = plan.withinRange(daysTempRange());

You are getting several values from an object and passingthese values as parameters in a method call

Send the whole object instead

Page 118 © Martin Fowler 9/11/99

Introduce Parameter ObjectIntroduce Parameter Object

amountInvoicedIn(start: Date, end: Date)amountReceivedIn(start: Date, end: Date)amountOverdueIn(start: Date, end: Date)

Customer

amountInvoicedIn(DateRange)amountReceivedIn(DateRange)amountOverdueIn(DateRange)

Customer

You have a group of parameters that naturally go togetherReplace them with an object

Page 60: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

60

Page 119 © Martin Fowler 9/11/99

Large ClassLarge Class

q Find a bunch of data and methods that gotogetherâ Use Extract Class or Extract Subclass

q Examine how clients use the classâ separate different kinds of uses with Extract

Interface

q Complex GUI Classesâ Use Extract Class to create domain objects.â Use Duplicate Observed Data where data

needs to be in both places

Page 120 © Martin Fowler 9/11/99

Duplicate Observed DataDuplicate Observed Data

StartField_FocusLostEndField_FocusLostLengthField_FocusLostcalculateLengthcalculateEnd

startField: TextFieldendField: TextFieldlengthField: TextField

Interval Window StartField_FocusLostEndField_FocusLostLengthField_FocusLost

startField: TextFieldendField: TextFieldlengthField: TextField

Interval Window

calculateLengthcalculateEnd

start: Stringend: Stringlength: String

Interval

«interface»Observer

Observable

1

You have domain data available only in a gui control anddomain methods need access.

Copy the data to a domain object. Set up an observer tosynchronize the two pieces of data.

Page 61: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

61

Page 121 © Martin Fowler 9/11/99

Extract InterfaceExtract Interface

getRatehasSpecialSkillgetNamegetDepartment

EmployeegetRatehasSpecialSkill

«interface»Billable

getRatehasSpecialSkillgetNamegetDepartment

Employee

Several clients use the same subset of a class’s interface ortwo classes have part of their interfaces in common

Extract that subset into an interface

Page 122 © Martin Fowler 9/11/99

Extract SubclassExtract Subclass

getTotalPricegetUnitPricegetEmployee

Job Item

getTotalPricegetUnitPrice

Job Item

getUnitPricegetEmployee

Labor Item

A class has features that are only used by some instancesCreate a subclass for that subset of features

Page 62: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

62

Page 123 © Martin Fowler 9/11/99

Long Parameter ListsLong Parameter Lists

q Parameters that seem to go togetherâ Preserve Whole Objectâ Introduce Parameter Object

q The invoked method can find oneparameter itselfâ Use Replace Parameter With Method

Page 124 © Martin Fowler 9/11/99

Replace Parameter With MethodReplace Parameter With Method

int basePrice = _quantity * _itemPrice;discountLevel = getDiscountLevel();double finalPrice = discountedPrice (basePrice, discountLevel);

int basePrice = _quantity * _itemPrice;double finalPrice = discountedPrice (basePrice);

An object invokes a method, then passes the result as aparameter for a method. The receiver could also invoke this

method.Remove the parameter and let the receiver invoke the

method

Page 63: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

63

Page 125 © Martin Fowler 9/11/99

Divergent ChangeDivergent Change

q Change one class in different ways fordifferent reasons

q Usually only apparent during evolution ofa systemâ Use Extract Class to factor out each style of

change

Page 126 © Martin Fowler 9/11/99

Shotgun SurgeryShotgun Surgery

q A Common kind of change affects severalclasses

q Need to bring the changes together tomake change easierâ Move Method and Move Field to bring

common elements togetherâ Inline Class to remove unnecessary

separations

Page 64: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

64

Page 127 © Martin Fowler 9/11/99

Inline ClassInline Class

getTelephoneNumber

nameareaCodenumber

Person

getTelephoneNumber

name

Person

getTelephoneNumber

areaCodenumber

Telephone Number

officeTelephone

1

A class isn’t doing very muchMove all its features into another class and delete it.

Page 128 © Martin Fowler 9/11/99

Feature EnvyFeature Envyq A method uses more features from

another class than it does its own classâ Use Move Method to move it to where it

wants to beâ If only part of a method is jealous use Extract

Method and Move Method

q Many patterns deliberately break this ruleâ To avoid smells of Divergent Change or

Shotgun Surgery

Page 65: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

65

Page 129 © Martin Fowler 9/11/99

Data ClumpsData Clumpsq Data Items that tend to hang around

togetherâ Groups of fields in several classesâ Groups of parameters in several method callsâ eg: startDate and endDate

q Start with classesâ Use Extract Class to group fields into an

object

q Continue with method callsâ Preserve Whole Objectâ Introduce Parameter Object

q A test: if you delete on data item, do theothers make sense?

q Now look for methods on other classesthat have Feature Envy for the newclasses

Page 130 © Martin Fowler 9/11/99

Primitive ObsessionPrimitive Obsessionq Objects blur the line between primitive

data types and recordsq Objects are almost always more useful

â Replace Data Value with Objectâ Replace Type Code with Classâ Replace Type Code with Subclassesâ Replace Type Code with State/Strategyâ Replace Array with Object

q Look for Data Clumps

Page 66: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

66

Page 131 © Martin Fowler 9/11/99

Replace Data Value with ObjectReplace Data Value with Object

customer: String

Order

name: String

CustomerOrder

1

You have a data item that needs additional data or behaviorTurn the data item into an object

Page 132 © Martin Fowler 9/11/99

Change Value to ReferenceChange Value to Reference

name: String

CustomerOrder

1

name: String

CustomerOrder

1[

You have a class with many equal instances that you want toreplace with a single object

Turn the object into a reference object

Page 67: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

67

Page 133 © Martin Fowler 9/11/99

Replace Type Code with Class Replace Type Code with Class

O: intA : intB : intAB : intbloodGroup : int

Person

O: BloodGroupA : BloodGroupB : BloodGroupAB : BloodGroup

BloodGroup

Person

1

A class has a numeric type code that does not affect itsbehavior

Replace the number with a new class

Page 134 © Martin Fowler 9/11/99

Replace Type Code with SubclassesReplace Type Code with Subclasses

ENGINEER : intSALESMAN : inttype : int

EmployeeEmployee

Engineer Salesman

You have an immutable type code which affects thebehavior of a class

Replace the type code with subclasses

Page 68: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

68

Page 135 © Martin Fowler 9/11/99

Replace Array with ObjectReplace Array with Object

String[] row = new String[3];row [0] = "Liverpool";row [1] = "15";

Performance row = new Performance();row.setName("Liverpool");row.setWins("15");

You have an array where certain elements mean differentthings

Replace the array with an object, with a field for eachelement

Page 136 © Martin Fowler 9/11/99

Switch StatementsSwitch Statementsq Usually leads to duplicated conditionals

â particularly when used with a type code

q Set up structure and use polymorphismâ Extract Method to remove the conditionalâ Move Method to put it in the right placeâ Replace Type Code with Subclassesâ Replace Type Code with State/Strategyâ Replace Conditional with Polymorphism

q Conditional behavior on a parameterâ Consider Replace Parameter with Explicit

Methodsâ But try to remove the parameter

q For null testsâ Introduce Null Object

Page 69: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

69

Page 137 © Martin Fowler 9/11/99

Replace Parameter with Replace Parameter with Explicit MethodsExplicit Methods

void setValue (String name, int value) {if (name.equals("height"))

_height = value;if (name.equals("width"))

_width = value;Assert.shouldNeverReachHere();

}

void setHeight(int arg) {_height = arg;

}void setWidth (int arg) {

_width = arg;}

You have a method with a runs different code depending onthe values of an enumerated parameter

Create a separate method for each value of the parameter

Page 138 © Martin Fowler 9/11/99

Introduce Null ObjectIntroduce Null Object

You have a method with a runs different code depending onthe values of an enumerated parameter

Create a separate method for each value of the parameter

if (customer == null) plan = BillingPlan.basic();else plan = customer.getPlan();

getPlan

Customer

getPlan

Null Customer

Page 70: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

70

Page 139 © Martin Fowler 9/11/99

Parallel Inheritance Parallel Inheritance HierarciesHierarcies

q Coupling between separate hierarciesq Often signalled by common prefixes or

suffixesq Make one hierarchy refer to the otherq Move features to the latterq Remove the former

Page 140 © Martin Fowler 9/11/99

Lazy ClassLazy Class

q A class that does not do enoughq Remove it

â Collapse Hierarchyâ Inline Class

Page 71: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

71

Page 141 © Martin Fowler 9/11/99

Collapse HierarchyCollapse Hierarchy

Employee

Salesman

Employee

A superclass and subclass are not very differentMerge them together

Page 142 © Martin Fowler 9/11/99

Speculative GeneralitySpeculative Generality

q Unused features that are there becauseyou are “sure” you’ll need them

q Unused features make the program hardto understand, and are usually wrong

q You can always add them laterq So remove them

â Lazy Abstract Classes: Collapse Hierarchyâ Unnecessary delegation: Inline Classâ Unused parameters: Remove Parameterâ Odd abstract method names: Rename

Method

Page 72: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

72

Page 143 © Martin Fowler 9/11/99

Temporary FieldTemporary Field

q A field that’s only used for a short part ofa class’s life

q If there’s more than one: separate theminto their own classâ Extract Classâ Avoid conditionals with Introduce Null

Object

Page 144 © Martin Fowler 9/11/99

Message ChainMessage Chain

q getObject().getAnotherObject().getYetAnotherObject().getYetAnotherAnotherObject().somethingMeaningful()

q Couples host to a whole data structureq Hide the structure

â Hide Delegateâ But may result in Middle Men

q See what the final code is doingâ Use Extract Method on the code that uses itâ Use Move Method to move it down the chain

Page 73: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

73

Page 145 © Martin Fowler 9/11/99

Hide DelegateHide Delegate

getDepartment

Person

getManager

Department getManager

Person

Department

Client Class Client Class

A client is calling a delegate class of an objectCreate methods on the server to hide the delegate

Page 146 © Martin Fowler 9/11/99

Middle ManMiddle Man

q Chasing around a lot of empty delegationq Talk to the real object

â Remove Middle Manâ But beware of Message Chainsâ If several methods use the same delegation:

Inline Methodâ Replace Delegation with Inheritance

Page 74: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

74

Page 147 © Martin Fowler 9/11/99

Replace Delegation with InheritanceReplace Delegation with Inheritance

getName

Person

Employee

getName

Employee 1

getName

Person

return person.getName()

You’re using delegation and are often writing many simpledelegations for whole interface

Make the delegating class a subclass of the delegate

Page 148 © Martin Fowler 9/11/99

Inappropriate IntimacyInappropriate Intimacy

q Classes should not know too much abouteach other

q Break up classes to reduce needed linksâ Use Move Method and Move Field to

separate piecesâ Change Bidirectional Association to

Unidirectionalâ Extract Class to combine common interestsâ Hide Delegate to let another class mediate.

q Inheritance often increases couplingâ Replace Inheritance with Delegation

Page 75: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

75

Page 149 © Martin Fowler 9/11/99

ChangeChange Bidirectional Bidirectional Association Association to Unidirectionalto Unidirectional

You have a two way association but one class no longerneeds features from the other.

Drop the unneeded end of the association

Order Customer1[

Order Customer1[

Page 150 © Martin Fowler 9/11/99

Replace Inheritance with DelegationReplace Inheritance with Delegation

isEmpty

Vector

Stack

isEmpty

Stack 1

isEmpty

Vector

return _vector.isEmpty()

A subclasses only uses part of a superclasses interface, ordoes not want to inherit its data.

Create a field for the superclass, adjust methods to delegateto the superclass, remove the subclassing

Page 76: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

76

Page 151 © Martin Fowler 9/11/99

Alternative classes with Alternative classes with different interfacesdifferent interfaces

q Try to ensure classes use differentimplementations with the same interfaceâ Rename Method to get names the sameâ Move Method if one class does not do

enoughâ Extract Superclass to factor out

commonalitiesâ Extract Interface if you can’t superclass

Page 152 © Martin Fowler 9/11/99

Extract Extract SuperclassSuperclass

getAnnualCostgetNamegetId

Employee

getTotalAnnualCostgetNamegetHeadCount

Department

getAnnualCostgetName

Party

getAnnualCostgetHeadCount

Department

getAnnualCostgetId

Employee

You have two classes with similar featuresCreate a superclass and move the common features to the

superclass

Page 77: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

77

Page 153 © Martin Fowler 9/11/99

Incomplete Library ClassIncomplete Library Class

q Cannot change library classesq So usual tactics don’t work

â Introduce Foreign Methodâ Introduce Local Extension

Page 154 © Martin Fowler 9/11/99

Introduce Foreign Method Introduce Foreign Method

Date newStart = new Date (previousEnd.getYear(), previousEnd.getMonth(), previousEnd.getDate() + 1);

Date newStart = nextDay(previousEnd);

private static Date nextDay(Date arg) {return new Date (arg.getYear(),arg.getMonth(), arg.getDate() + 1);

}

A server class you are using needs an additional method,but you can’t modify the class.

Create a method in the client class with an instance of theserver class as its first argument

Page 78: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

78

Page 155 © Martin Fowler 9/11/99

Introduce Local ExtensionIntroduce Local Extension

nextDay(Date) : Date

Client Class

nextDay() : Date

MfDate

Date

A server class you are using needs several additionalmethods, but you can’t modify the class.

Create a new class which contains these extra methods.Make this extension class a subclass or a wrapper of the

original

Page 156 © Martin Fowler 9/11/99

Data ClassData Class

q A class that is just getters and settersq May have public data

â Encapsulate Fieldâ Encapsulate Collectionâ Remove Setting Method

q Look for methods that use the accessorsâ Use Extract Method and Move Method to

move behavior into the data classâ Look to Hide Method on the accessors

Page 79: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

79

Page 157 © Martin Fowler 9/11/99

Encapsulate Field Encapsulate Field

public String _name

private String _name;public String getName() {return _name;}public void setName(String arg) {_name = arg;}

There is a public fieldMake it private and provide accessors

Page 158 © Martin Fowler 9/11/99

Encapsulate CollectionEncapsulate Collection

getCourses():SetsetCourses(:Set)

Person

getCourses():Unmodifiable SetaddCourse(:Course)removeCourse(:Course)

Person

A method returns a collectionMake it return a read only view and provide add/remove

methods

Page 80: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

80

Page 159 © Martin Fowler 9/11/99

Remove Setting MethodRemove Setting Method

setImmutableValue

Employee Employee

A field should be set at creation time and never alteredRemove any Setting Method for that field

Page 160 © Martin Fowler 9/11/99

Hide MethodHide Method

+ aMethod

Employee

- aMethod

Employee

A Method is not used by any other classMake the Method private

Page 81: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

81

Page 161 © Martin Fowler 9/11/99

Refused BequestRefused Bequestq Only using some of the features of the

parentq A sign of an incorrect hierarchyq Create a new sibling class

â Push Down Methodâ Push Down Field

q Doesn’t want to support parent interfaceâ Replace Inheritance with Delegation

Page 162 © Martin Fowler 9/11/99

CommentsComments

q Not a bad smell: but is a deodorantq Look for the smell that the comment is

trying to maskq Remove the smell, see if you still need

the comment

Page 82: © Martin Fowler, 1997 Refactoring: Improving the Design of ...jaoo.org/jaoo1999/schedule/MartinFowlerRefractoring.pdf · 6 Page 11 © Martin Fowler 9/11/99 Sample Output Rental Record

82

Page 163 © Martin Fowler 9/11/99

Final ThoughtsFinal Thoughts

q The one benefit of objects is that theymake it easier to change.

q Refactoring allows you to improve thedesign after the code is written

q Up front design is still important, but notso critical

q Refactoring is an immature subject: notmuch written and very few tools

Make it run,make it right,make it fast

Kent Beck