refactoring: improving the design of existing code © martin fowler, 1997 [email protected] martin...

39
Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 [email protected] www.martinfowler.com www.thoughtworks.com Martin Fowler

Upload: beatrice-lynch

Post on 16-Dec-2015

256 views

Category:

Documents


6 download

TRANSCRIPT

Page 1: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Refactoring: Improving the Design of Existing

Code

© Martin Fowler, 1997

[email protected]

www.martinfowler.comwww.thoughtworks.com

Martin Fowler

Page 2: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

What is Refactoring

A series of small steps, each of which changes the program’s internal structure without changing its external behavior

A series of small steps, each of which changes the program’s internal structure without changing its external behavior

Page 3: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Sample Output

Rental Record for Dinsdale PirhanaMonty Python and the Holy Grail3.5Ran 2Star Trek 276

Star Wars 3.23

Wallace and Gromit6

Amount owed is 20.5You earned 6 frequent renter points

Page 4: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Starting Class diagram

priceC ode: in t

M ovie

daysR ented: int

R ental

statem ent()

C ustom er1

1

Page 5: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Class Moviepublic 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: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Class 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 7: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

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: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Customer.statement() part 1

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();

//determine amounts for each lineswitch (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 9: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Customer.statement() part 2

// add frequent renter pointsfrequentRenterPoints ++;// add bonus for a two day new release

rentalif ((each.getMovie().getPriceCode() ==

Movie.NEW_RELEASE) && each.getDaysRented() > 1)

frequentRenterPoints ++;

//show figures for this rentalresult += "\t" +

each.getMovie().getTitle()+ "\t" + String.valueOf(thisAmount) + "\n";totalAmount += thisAmount;

}//add footer linesresult += "Amount owed is " + String.valueOf(totalAmount) + "\n";result += "You earned " + String.valueOf(frequentRenterPoints) +

" frequent renter points";return result;

}

Page 10: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Interactions for statement

aC ustom er aR ental aM ovie

getM ovie

* [for all rentals ]

getPriceC ode

getD aysR ented

statem ent

Page 11: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Requirements Changes

» Produce an html version of the statement» The movie classifications will soon change

– together with the rules for charging and for frequent renter points

Page 12: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Extract Method

voi d printOwi ng() {printBanner();

// print detail sSystem.out.pri ntln ("name: " + _name);System.out.pri ntln ("amount" + getOutstanding());

}

voi d printOwi ng() {printBanner();printDetai ls(getOutstanding());

}

voi d printDetai ls (double outstandi ng) {System.out.pri ntln ("name: " + _name);System.out.pri ntln ("amount" + outstandi ng);

}

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

purpose of the method.

Page 13: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Candidate 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 lineswitch (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]12

Page 14: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Steps for Extract Method

» Create method named after intention of code» Copy extracted code» Look for local variables and parameters

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

» Compile» Replace code fragment with call to new

method» Compile and test

Page 15: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Extracting the Amount 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 16: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Statement() after extraction

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 = amountFor(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 17: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Extracting the amount 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 18: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Change names of variablesprivate 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 19: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Move Method

aMethod()

Class 1

Class 2

Class 1

aMethod()

Class 2

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

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

remove it altogether.

Page 20: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Steps for Move method» Declare method in target class» Copy and fit code» Set up a reference from the source object to the

target» Turn the original method into a delegating method

– amountOf(Rental each) {return each.charge()}– Check for overriding methods

» Compile and test» Find all users of the method

– Adjust them to call method on target» Remove original method» Compile and test

Page 21: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Moving 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

statem ent()

C ustom er

getC harge()

daysR ented: int

R ental

priceC ode: int

M ovie

Page 22: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Altered 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.getCharge(); // 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 23: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Problems 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.getCharge(); // 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: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Replace Temp with Query

double basePri ce = _quanti ty * _itemPrice;if (basePrice > 1000)

return basePri ce * 0.95;el se

return basePri ce * 0.98;

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

el se return basePri ce() * 0.98;

.. .doubl e basePrice() {

return _quanti ty * _itemPrice;}

You are using a temporary variable to hold the result of an expression.Extract the expression into a method. Replace all references to the

temp with the expression. The new method can then be used in other methods.

Page 25: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Steps for Replace temp with Query

» Find temp with a single assignment» Extract Right Hand Side of assignment» Replace all references of temp with new

method» Remove declaration and assignment of temp» Compile and test

Page 26: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

thisAmount removed 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.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 27: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Extract and move frequentRenterPoints()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 28: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

After moving charge and frequent renter points

1statem ent()

C ustom er

getC harge()getF requentR enterPoints()

daysR ented: int

R ental

priceC ode: int

M ovie

aC ustom er aR ental aM ovie

getC harge

* [for all rentals ]

getPriceC ode

statem ent

getF requentR enterPointsgetPriceC ode

Page 29: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

More temps to killclass 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 30: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

The 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 31: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

The 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 rentalresult += "\t" + each.getMovie().getTitle()+ "\t" +

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

//add footer linesresult += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n";result += "You earned " + String.valueOf(getTotalFrequentRenterPoints())

+ " frequent renter points";

return result; }

Page 32: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

After replacing the totals

1statem ent()getTotalC harge()getTotalF requentR enterPoints()

C ustom er

getC harge()getF requentR enterPoints()

daysR ented: int

R ental

priceC ode: int

M ovie

aC ustom er aR ental aM ovie

* [for all rentals ] getC harge

getTotalC harge

getPriceC ode

statem ent

* [for all rentals ] getF requentR enterPoints

getPriceC ode

getTotalF requentR enterPoints

Page 33: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

htmlStatement()

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 rentalresult += each.getMovie().getTitle()+ ": " +

String.valueOf(each.getCharge()) + "<BR>\n";}//add footer linesresult += "<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 34: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

The current getCharge 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 35: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

getCharge 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;}

Do the same with frequentRenterPoints()

Page 36: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Consider inheritance

How’s this?

getC harge

Movie

getC harge

R egular M ovie

getC harge

C hildrens M ovie

getC harge

N ew R elease M ovie

Page 37: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

The Two Hats

Adding Function

» Add new capabilities to the system

» Adds new tests» Get the test working

Refactoring

Does not add any new features

Does not add tests (but may change some)

Restructure the code to remove redundancy

Swap frequently between the hats, but only wear one at a time

Page 38: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

Why Refactor» To improve the software design

– combat’s “bit rot”– makes the program easier to change

» To make the software easier to understand– write for people, not the compiler– understand unfamiliar code

» To help find bugs– refactor while debugging to clarify the code

Refactoring helps you program faster!

Page 39: Refactoring: Improving the Design of Existing Code © Martin Fowler, 1997 fowler@acm.org   Martin Fowler

When should you refactor?

» To add new functionality– refactor existing code until you understand it– refactor the design to make it easy to add

» To find bugs– refactor to understand the code

» For code reviews– immediate effect of code review– allows for higher level suggestions

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