Lecture 25: Refactoring, Part 1
Credit where Credit is Due
- Some of the material for this lecture is taken from “Refactoring: Improving the Design of Existing Code” by Martin Fowler; as such some material is copyright © Addison Wesley, 1999
Review: What is Refactoring?
- Refactoring is the process of changing a software system such that
- the external behavior of the system does not change
- but the internal structure of the system is improved
- This is sometimes called
Improving the design after it has been written
- Key Point: Applying a refactoring to a system does not change its behavior!
- If it does, then something has gone wrong
Refactoring Considered Harmful?
- From the standpoint of a manager, refactoring can appear to be dangerous!
- If my developers spend their time
cleaning up the code
then that's less time implementing required functionality
- …and my schedule is slipping as it is!
- To address these concerns, refactoring needs to be
- systematic
- incremental
- safe
Getting It Right the Second Time
- The idea behind refactoring is to acknowledge that it will be difficult to get the design of a system right the first time
- Even if you did, your requirements may change, obsoleting your current design!
- The primary purpose of refactoring is to make software easier to understand and maintain
- contrast this with performance optimization
- functionality remains the same
- only internal structure is changed
- however while the system is now faster, the code is often HARDER to understand
- Benefits
- Often code size is reduced after a refactoring
- Confusing structures are transformed into simpler structures
The Refactoring Process
- When you systematically apply refactoring, you wear two hats
- add functionality
- refactoring
- Don't try to clean the code when doing the former
- Don't try to add features when doing the latter
Making It Safe
- How do you make refactoring safe?
- First, use refactoring patterns
- Like the ones discussed in Fowler's book
- Second, test constantly!
- Each time you finish a refactoring, you run your test suite to confirm that your system's functionality has stayed the same
Why adopt refactoring?
- Refactoring improves the design of your system
- Refactoring makes your software easier to understand
- because structure is improved
- duplicated code is removed
- etc.
- Refactoring helps you find bugs
- because it promotes a deep understanding of the code
- Refactoring helps you program faster
- because a good design enables progress
When should you refactor?
- The Rule of Three
- Refers to duplicated code
- If you see code being duplicated three times, refactor to eliminate two of the copies
- Refactor before or after adding functionality
- Before: clean up code to make it easier to add the new feature
- After: address any “code rot” that may have slipped in while the feature was being added
- Refactor when you fix a bug
- Refactor during a code review
Problems with Refactoring
- Databases
- Business applications are often tightly coupled to underlying databases
- code is easy to change; databases are not
- Changing Public Interfaces
- Some refactorings change the public interface of a module
- If you own all the calling code, no problem
- Otherwise, the interface is “published” and can't change
- Radically Changing the Design of a System
- Refactorings perform incremental changes to a system's design
- Radical change requires a rewrite of the software
Bad Smells
- Question: How do you identify code that needs to be refactored?
- Answer: Sniff out the “bad smells” of your code!
- A very valuable chapter in Fowler's book
- It presents examples of bad smells and what to do about them
Examples
- Duplicated Code
- Bad because its easy to lose track of multiple copies of the same code
- Long Method
- Bad because long methods are harder to understand than short methods
- Large Class
- Bad because its easy for a large class to lose cohesion
- Long Parameter List
- hard to understand, can become inconsistent across methods
- Divergent Change
- Related to Cohesion: one type of change requires changing one subset of methods; another type of change requires changing another subset
- Shotgun Surgery
- a change requires lots of little changes in a lot of different classes
- Feature Envy
- A method requires lots of information from some other class
- Data Clumps
- attributes that clump together (are used together) but are not part of the same class
- Switch Statements
- Switch statements are often duplicated in code; they can typically be replaced by use of polymorphism (let OO do your selection for you!)
- Message Chains
- a client asks an object for another object and then asks that object for another object etc. Bad because client depends on the structure of the navigation
- Middle Man
- If a class is delegating more than half of its responsibilities to another class, do you really need it?
Example Refactorings
- The refactoring book has 72 refactoring patterns!
-
- We will cover:
- Extract Method
- Replace Temp with Query
- Move Method
- Replace Conditional with Polymorphism
- Introduce Null Object
Extract Method
- You have a code fragment that appears in multiple places within the code
- Turn the fragment into a method whose name explains its purpose
- Delete all remaining instances of the fragment and replace with a call to the new method
Example (I)
void printBill(Customer c, double amount) {
printBanner();
String name = c.getName();
//print details
System.out.println("name: " + name);
System.out.println("amount: " + amount);
}
Example (II)
void printBill(Customer c, double amount) {
printBanner();
printDetails(c.getName(), amount);
}
void printDetails(String name, double amount) {
System.out.println("name: " + name);
System.out.println("amount: " + amount);
}
Note: comment deleted, temp variable removed
Replace Temp with Query
- You are using a temporary variable to hold the result of a common calculation
- This calculation is common and multiple copies of it exist in the code
- Extract the expression into a method
- Replace all references to the temp variable with a call to the new method
Example (I)
…
double basePrice = _quantity * _itemPrice;
if (basePrice > 1000) {
return basePrice * 0.95;
} else {
return basePrice * 0.98;
}
…
Example (II)
double basePrice() {
return _quantity * _itemPrice;
}
…
if (basePrice() > 1000) {
return basePrice() * 0.95;
} else {
return basePrice() * 0.98;
}
…
Note: a lot of developers dislike this refactoring. Why?
Move Method
- A method is using more features (attributes and methods) of another class than the class in which it is defined
- Create a new method with a similar body in the class it uses most.
- Turn the old method into a simple delegation, or remove it altogether.
- Update all places in the code that called the old method to call it in its new location
Example (I)
- Two classes: Account and AccountType
- One Method: overdraftCharge()
- Method is currently in Account, but customer has asked for a new type of account that uses a different rule for calculating its overdraft charge
- As a result, we want to move overdraftCharge() from Account to AccountType
Example (II): Original Code
class Account {
…
double overdraftCharge() {
if (_type.isPremium()) {
double result = 10;
if (_daysOverdrawn > 7 ) {
result += (_daysOverdrawn - 7) * 0.85;
}
return result;
} else {
return _daysOverdrawn * 1.75;
}
}
double bankCharge() {
double result = 4.5;
if (_daysOverdrawn > 0) {
result += overdraftCharge();
return result;
}
}
private AccountType _type;
private int _daysOverdrawn;
}
Example (III): Move method to AccountType
class AccountType {
…
double overdraftCharge(int daysOverdrawn) {
if (isPremium()) {
double result = 10;
if (daysOverdrawn > 7 ) {
result += (daysOverdrawn - 7) * 0.85;
}
return result;
} else {
return daysOverdrawn * 1.75;
}
}
…
}
Note: daysOverdrawn lives in Account, so it becomes a parameter;
isPremium used to be accessed via a variable of type AccountType;
now the method lives in that class and can access the method directly
Example (III): Clean up Account Class
class Account {
…
double overdraftCharge() {
return _type.overdraftCharge(_daysOverdrawn);
}
…
}
Note: here the old method delegates to the new method in AccountType
Example (IV): Clean up Account Class, Take Two
class Account {
…
double bankCharge() {
double result = 4.5;
if (_daysOverdrawn > 0) {
result += _type.overdraftCharge(_daysOverdrawn);
return result;
}
…
}
Note: here the old method is deleted;
bankCharge() is then updated to access overdraftCharge() in its new location
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
Example (I)
…
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”);
}
…
Example (II)

This refactoring eliminates the need for the conditional by creating the above class structure
Example (III)
public abstract class Bird {
…
double getSpeed();
…
}
class europeanBird extends Bird {
…
double getSpeed() {
return getBaseSpeed();
}
…
}
class africanBird extends Bird {
…
double getSpeed() {
return getBaseSpeed() - getLoadFactor() * _numberOfCoconuts;
}
…
}
class norwegianBlueBird extends Bird {
…
double getSpeed() {
return (_isNailed) ? 0 : getBaseSpeed(_voltage);
}
…
}
Example (IV)
Note: by splitting the branches of the conditional into the appropriate subclasses, the conditional goes away. And now, polymorphism can be used to get the correct speed, when needed:
void printSpeed(Bird[] birds) {
for (int i=0; i < birds.length; i++) {
System.out.println("" + birds[i].getSpeed());
}
}
Example (I)
Create an extension of the relevant class that represents the “null value” for that class. Make that class a singleton and initialize it with appropriate values

The NullCustomer class would be implemented as a singleton and would contain code that looks like this:
public class NullCustomer extends Customer {
…
public void getName() {
return "Will U. Leavemealone";
}
…
}
Example (II)
The findCustomer() method shown previously would be modified to return an instance of NullCustomer whenever it previously would have returned null
public Customer findCustomer(Map params) {
Customer result;
…
if (result == null) {
result = NullCustomer.instance();
}
return result;
}
Summary
- Refactoring is a useful technique for teaching developers:
- how to improve the structure of their code
- without impacting its existing functionality
- It provides a set of guidelines for how to identify code that needs refactoring
- Refactoring patterns exist for developers to learn that allows refactoring to be applied in a systematic, incremental, and safe fashion
- We will look at an extended refactoring example in the next lecture
Coming Up Next
- Lecture 26: Refactoring, Part 2
- Lecture 27: Test Driven Design