Refactoring 2
Refactoring 2
A test for a Data Clump: Delete one value and see if the others
still make sense.
Few cases that affect a single method; Cases are stable; try:
Replace Parameter with Explicit Methods – if the switch value is a
method parameter.
Introduce Null Object – If there is a conditional case comparing with
null.
Replace Type Code with Polymorphism
Replace Type Code with Class
Replace conditional with polymorphism
Replace conditional with polymorphism
Replace type code with state/strategy
State versus Strategy
The difference simply lies in that they solve different
problems:
The State pattern deals with what (state or type) an
object is (in) -- it encapsulates state-dependent behavior,
whereas
the Strategy pattern deals with how an object performs a
certain task -- it encapsulates an algorithm.
State
Problem
Solution (Replace Type Code with Class)
Problem
Solution(Replace type code with state)
Problem
Solution(Replace type code with strategy)
Replace subclasses with fields
You have subclasses that vary only in methods that return constant data.
Parallel Inheritance Hierarchies
If a class has features that are only used in test cases, remove them (and
the test case)..
Think TDD!
Pairs of classes that know too much about each other’s private
details
1. Encapsulate Field.
2. Encapsulate Collection – for collection fields.
3. Remove Setting Method – for final fields.
4. Move Method – from client classes to the data class.
Extract Method – if can’t move whole methods.
5. Hide Method – on getters and setters.
Encapsulate Field
Refused Bequest
Comments
Comments are sometimes used to hide bad code
“…comments often are used as a deodorant” (!)
Extract Method.
Rename Method.
Introduce Assertion.
Bad Smells in Code
Alternative Classes with Different Interfaces
Symptom:Two or more methods do the same thing
but have different signature for what they do
Incomplete Library Class
A framework class doesn’t do everything you need
87
The Catalog
A few of the more common ones, include:
Extract Method
Replace Temp with Query
Move Method
Replace Conditional with Polymorphism
Introduce Null Object
88
Extract Method
You have a code fragment that can be grouped together
Turn the fragment into a method whose name explains
the purpose of the fragment
91
Extract Method, continued
void printOwing(double amount) {
printBanner()
//print details
System.out.println(“name: ” + _name);
System.out.println(“amount: ” + amount);
}
94
Replace Temp with Query
double basePrice = _quantity * _itemPrice
96
Replace Conditional with Polymorphism
You have a conditional that chooses different behavior
depending on the type of an object
Move each “leg” of the conditional to an overriding
method in a subclass. Make the original method abstract
97
Replace Conditional with Polymorphism
double getSpeed() {
switch (_type) {
case EUROPEAN:
return getBaseSpeed();
case AFRICAN:
return getBaseSpeed() - getLoadFactor() *
_numberOfCoconuts;
case NORWEGIAN_BLUE:
return (_isNailed) ? 0 : getBaseSpeed(_voltage);
}
throw new RuntimeException(“Unreachable”)
}
98
Replace Conditional with Polymorphism
Bird
getSpeed()
99
Introduce Null Object
Repeated checks for a null value
Replace the null value with a null object
if (customer == null) {
… Null Customer
getName()
100
Introduce Null Object
if (customer.isNull())
name = “occupant”;
else
name = customer.getName();
customer.getName();
The conditional goes away entirely!!
101
When should you refactor?
The Rule of Three
Three strikes and you refactor
refers to duplication of code
Refactor when you add functionality
do it before you add the new function to make it easier to add
the function
or do it after to clean up the code after the function is added
Refactor when you need to fix a bug
Refactor as you do a code review
102
Managing Refactoring!
Manager’s point-of-view
If my programmers spend time “cleaning up the code” then
that’s less time implementing required functionality (and my
schedule is slipping as it is!)
To address this concern
Refactoring needs to be systematic, incremental, and safe.
103
Principles, continued
When you systematically apply refactoring, you wear two
hats:
adding function
functionality is added to the system without spending any time
cleaning the code
refactoring
no functionality is added, but the code is cleaned up, made easier to
understand and modify, and sometimes is reduced in size
104
Problems with Refactoring
Databases
Business applications are often tightly coupled to
underlying databases
code is easy to change; databases are not
Changing Interfaces
Some refactorings require that interfaces be changed
if you own all the calling code, no problem
if not, the interface is “published” and can’t change
Design Changes that are difficult to refactor
Better to redo than to refactor
Software Engineers should have “courage”!
105
Tool Support
JAVA
Eclipse
IntelliJ IDEA
Borland JBuilder
NetBeans
RefactorIT plug-in for Eclipse, Borland JBuilder, NetBeans,
Oracle JDeveloper
.NET
IntelliJ Resharper for Visual Studio .NET
Recommended Reading
Martin Fowler’s refactoring catalog
Refactoring Example
108
Initial Class Diagram
Movie Rental Customer
1 * * 1
priceCode: int daysRented: int
statement()
109
public class Movie {
public static final int CHILDREN = 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 String getTitle() { return _title; }
public void setPriceCode(int priceCode) {
_priceCode = priceCode;
}
}
110
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; }
}
Movie Rental Customer
1 * * 1
priceCode: int daysRented: int
statement()
111
class Customer {
private String _name;
private Vector _rentals = new Vector();
public Customer(String name) { _name = name; }
public String getName() { return _name; }
public void addRental (Rental arg) {
_rentals.addElement(arg);
}
public String statement();
}
112
statement()
Initial Class Diagram
Movie Rental Customer
1 * * 1
priceCode: int daysRented: int
statement()
113
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();
114
// 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_RELAESE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) *1.5;
break;
}
115
// add frequent renter points
frequentRenterPoints++;
// add bonus for 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;
}
116 // end while
// add footer lines
117
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 line
switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) *1.5;
break;
}
119
How to start?
120
switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) *1.5;
break;
}
Extract method
121
switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) *1.5;
break;
}
127
look at the
private double amountFor(Rental each)
double thisAmount = 0; variable names
switch (each.getMovie().getPriceCode()) { and use more
case Movie.REGULAR: suitable ones
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) *1.5;
break;
}
return thisAmount;
}
128
each and
private double amountFor(Rental each) thisAmount
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_RELAESE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) *1.5;
break;
}
return thisAmount;
}
129
each aRental
thisAmount result
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_RELAESE:
result += aRental.getDaysRented() * 3;
break;
case Movie.CHILDREN:
result += 1.5;
if (aRental.getDaysRented() > 3)
result += (aRental.getDaysRented() - 3) *1.5;
break;
}
return result;
130
}
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);
134
private 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_RELAESE:
result += getDaysRented() * 3;
break;
case Movie.CHILDREN:
result += 1.5;
if (getDaysRented() > 3)
result += (getDaysRented() - 3) *1.5;
break;
}
return result;
}
135
private double amountFor(Rental aRental)
return aRental.getCharge();
}
• initially replace the body with the call
• remove this method later on and call directly
while (rentals.hasMoreElements()) {
double thisAmount = 0;
Rental each = (Rental) rentals.nextElement();
//thisAmount = amountFor(each);
thisAmount = each.getCharge();
137
frequent renter points
// add frequent renter points
frequentRenterPoints++;
// add bonus for 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”;
138
totalAmount += thisAmount;
} // end while
frequent renter points
139
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 line
switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) *1.5;
break;
}
while (rentals.hasMoreElements()) {
double thisAmount = 0;
Rental each = (Rental) rentals.nextElement();
// determine amounts for each line
thisAmount = each.getCharge();
frequentRenterPoints += each.getFrequentRenterPoints();
Movie
Customer continues to get smaller,
priceCode: int
Rental continues to get larger; but
Rental now has operations that
change it from being a “data holder”
to a useful object.
142
Replace Temp with Query
Removing temp variable is good thing because they often
cause the need for parameters where none are required
and can also cause problems in long methods
143
remove temp variables
statement() has two temp variables
totalAmount and frequentRentalPoints
Both of these values are going to be needed by statement()
and htmlStatement()
let’s replace them with query methods
little more difficult because they were calculated within a loop;
we have to move the loop to the query methods
a) replace totalAmount with getTotalCharge()
b) replace frequentRentalPoints with getTotalFrequentPoints()
test after each point
144
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 line
thisAmount = each.getCharge();
frequentRenterPoints += each.getFrequentRenterPoints();
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
147
New Class Diagram
Rental Customer
* 1
daysRented: int
getCharge() statement()
getFrequentRenterPoints() getTotalCharge()
getTotalFrequentRentorPoints()
1*
Movie
• Customer class is now bigger; but has
priceCode: int two methods that can be shared with
the existing statement() and planned
htmlStatement() method.
• Now we have three loops instead of
one; performance can be a concern but
we should wait until a profiler tells us
so.
148
add htmlStatement()
We are now ready to add the htmlStatement() function.
149
New Requirements
It is now anticipated that the store is going to have more
than three initial types of movies
as a result of these new classifications, renter points and
charges will vary with each new movie type.
as a result, we should probably move the getCharge() and
getFrequentRenterPoints() methods to the Movie class.
150
move methods
• move getCharge to Movie
getCharge needs to know the number of days the movie
was rented; since this is information that Rental has, it
needs to be passed as a parameter.
• move getFrequentRenterPoints() to Movie
151
New Class Diagram
Rental Customer
* 1
daysRented: int
getCharge() statement()
getFrequentRenterPoints() getTotalCharge()
getTotalFrequentRentorPoints()
1*
Movie
priceCode: int
getCharge(days: int)
getFrequentRenterPoints(days: int) Movie has new methods; finally
making the transition from data
holder to object.
These methods will allow us to
handle new types of movies
152 easily
How to handle new movies
Movie
getCharge()
But movies can change type – a New Release Movie can later
become regular movie
A movie has a particular state: its charge (and its renter points)
depend upon that state; so we can use the state pattern to handle
153 new types of movies (for now, at least)
Replace Type Code with State/Strategy
154
move method
Now we need to move the method getCharge to the
newly created Price class
It is a very simple move, we just need to remember to change
Movie to delegate its getCharge operation to Price
155
Replace Conditional with Polymorphism
Now we move each branch of the switch statement into
the appropriate subclass
After you have done this, change Price’s getCharge to an
abstract method
156
How to handle new movies
1
Movie Price
getCharge() getCharge()
157
Handle renter points
Now we repeat for frequent renter points
We move the method over to price and use polymorphism to
handle the logic
we leave a default implementation in Price and have newRelease
override the implementation since it is the only class that returns that
value.
158
We’re done!
We have added new functionality, changed “data holders”
to “objects” and made it easy to add new types of movies
with special charges and frequent rental points.
159
Item
Library Blob or god class Title
ISBN
Author
Publisher
Library_Main_Control Cost
Quantity
Fine_Amount …….
Check_Out_Item(Item)
Person Check_In_Item(Item)
Name Add_Item(Item)
User_Id Delete_Item(Item)
Items_Out Print_Catalog(Catalog) Catalog
Fines Sort_Catalogs(Catalog)
….. Search_Catalog(topic) TopicInventory
Edit_Item(Item) …..
Find_Item(Item)
List_Catalogs()
Issue_Library_Card(Person)
Calculate_Fine(Person)
…….
Check_Out_Item(Item)
Check_In_Item(Item)
Add_Item(Item)
Delete_Item(Item)
Print_Catalog(Catalog) Catalog
Sort_Catalogs(Catalog)
Search_Catalog(topic) Topic_Inventory
…..
Edit_Item(Item)
Find_Item(Item)
Person
Name List_Catalogs()
User_Id Issue_Library_Card(Person)
Items_Out Calculate_Fine(Person)
Fines …….
…..
Item
Look for “Natural Homes”…. Title
\ ISBN
Author
Publisher
Cost
Quantity
Library_Main_Control …….
Fine_Amount
Check_Out_Item()
SelectItemMenu(item,menuchoice) Check_In_Item()
Print_Catalog(Catalog) Add_Item()
Sort_Catalogs(Catalog) Delete_Item()
Search_Catalog(topic) Edit_Item()
Find_Item()
Catalog
List_Catalogs()
Person Issue_Library_Card(Person) Topic_Inventory
Name Calculate_Fine(Person) …..
User_Id …….
Items_Out
Fines
…..
Item
Title
ISBN
Author
Publisher
Cost
Quantity
…….
Library_Main_Control Check_Out_Item()
Check_In_Item()
Fine_Amount
Fine_Amount Add_Item()
Delete_Item()
SelectItemMenu(item,menuchoice) Edit_Item()
Find_Item()
SelectCatalogMenu(Catalog,
menuchoice)
Catalog
Topic_Inventory
Person
….
Name
Issue_Library_Card(Person) Print_Catalog()
User_Id
Calculate_Fine(Person) Sort_Catalogs()
Items_Out Calculate_Fine(Person)
……. Search_Catalog(topic)
Fines
List_Catalogs()
…..
Item
Title
ISBN
Author
Publisher
Cost
Quantity
…….
Fine Library_Main_Control
Check_Out_Item()
Fine_Amount Check_In_Item()
Add_Item()
Calculate_Fine(Pers SelectItemMenu(item,menuchoice) Delete_Item()
on) Edit_Item()
SelectCatalogMenu(Catalog, Find_Item()
menuchoice)
Find _fine(Person)
Catalog
Topic_Inventory
Person
….
Name
Print_Catalog()
User_Id
Sort_Catalog()
Items_Out
Issue_Library_Card(Person) Search_Catalog()
Fines
……… List_Catalogs()
…..
Item
Redefine Relationships Title
ISBN
Author
Publisher
Cost
Quantity
…….
Fine Library_Main_Control Check_Out_Item()
Check_In_Item()
Fine_Amount Add_Item()
Delete_Item()
Calculate_Fine(Pers Edit_Item()
on) SelectItemMenu(item,menuchoice) Find_Item()
SelectCatalogMenu(Catalog,
menuchoice)
Find _fine(Person) Catalog
Person Topic_Inventory
Name ….
User_Id Print_Catalog()
Items_Out Issue_Library_Card(Person) Sort_Catalog()
Fines ……… Search_Catalog(topic)
….. List_Catalogs()
SelectItemMenu(item,
menuchoice)
How do you make refactoring safe?
First, use refactoring “patterns”
Fowler’s book assigns “names” to refactorings in the same way that
the GoF’s book assigned names to patterns
170