A (Very) Simple Example
Consolidate duplicate conditional
fragments
if (isSpecialDeal()) {
total = price * 0.95;
send ();
if (isSpecialDeal())
total = price * 0.95;
}
else
total = price * 0.98;
else {
total = price * 0.98;
send ();
send ();
}
Software Engineering II
Lecture 42
Fakhar Lodhi
Recap
A (Very) Simple Example
Better Still
if (isSpecialDeal())
total = price * 0.95;
else
total = price * 0.98;
send ();
if (isSpecialDeal())
factor = 0.95;
else
factor = 0.98;
total = price * factor;
send ();
Refactoring: Where to
Start?
• How do you identify code that
needs to be refactored?
– “Bad Smells” in Code
Bad Smells in Code
• Duplicated Code
– bad because if you modify one
instance of duplicated code but not
the others, you (may) have
introduced a bug!
• Long Method
– long methods are more difficult to
understand; performance concerns
with respect to lots of short methods
Bad Smells in Code
• Large Class
• Long Parameter List
• Divergent Change
• Shotgun Surgery
• Feature Envy
• Data Clumps
• Primitive Obsession
Bad Smells in Code
• Switch Statements
• Parallel Inheritance Hierarchies
• Lazy Class
• Speculative Generality
• Temporary Field
• Message Chains
• Middle Man
Bad Smells in Code
• Inappropriate Intimacy
• Alternative Classes with Different
Interfaces
• Incomplete Library Class
• Data Class
• Refused Bequest
• Comments (!)
Refactoring Example
• A simple program for a video store
– Movie
– Rental
– Customer
• Program is told which movies a
customer rented and for how long
and then, it calculates:
– The charges
– Frequent renter points
• Customer object can also print a
Initial Class Diagram
Movie
priceCode: int
1
Rental
*
daysRented: int
*
1
Customer
statement()
• statement() works by looping through all
rentals
• For each rental, it retrieves its movie and
the number of days it was rented; it also
retrieves the price code of the movie.
• It then calculates the price for each movie
rental and the number of frequent renter
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;
}
}
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; }
}
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();
}
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;
// 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;
}
// add footer lines
result += “Amount owed is ” +
String.valueOf(totalAmount) + “\n”;
result += “You earned ” +
String.valueOf(frequentRenterPoints) + “\n”;
return result;
}
How to start?
• We want to decompose statement() into smaller
pieces which are easier to manage and move
around.
• We’ll start with “Extract Method”
– target the switch statement first
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
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;
}
so each is a variables
parametercan
and
is returned
to
• non-modifiable
bevalue
passed
as parameter
to
each does
not change
but thisAmount does
thisAmount
new method
(if required)
• modified variables require more care; since there is only
one (i.e. thisAmount), we can make it the return value of
the new method.
Watch
for
local
variables
Extract method
‘each’ and ‘thisAmount’
variable ‘each’ is passed as a
parameter
private double amountFor(Rental each)
double thisAmount = 0;
Switch statement is
switch (each.getMovie().getPriceCode())
{
extracted
and put in a new
case Movie.REGULAR:
method “amountFor()”
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;
value of ‘thisAmount’ is
}
returned
return thisAmount;
}
Be careful!
• Pitfalls
– be careful about return types; in the original
statement(), thisAmount is a double, but it would
be easy to make a mistake of having the new
method return an int. This will cause rounding-off
error.
• Always remember to test after each change.
look at the
private double amountFor(Rental each)
variable names
double thisAmount = 0;
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;
}
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;
}
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;
}
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);
// the switch statement has been replaced by this
// call to amountFor
… the rest continues as before
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;
}
amountFor()should
uses information
from
the
amountFor()
be part of the
Rental
return result;
Rental
class,
but customer
it does notclass,
use so it
class
and
not the
}
information
from to
thethe
Customer
class where
should
be moved
Rental class
it is currently located.
Move method
• Methods should be located close to the data they
operate.
– we can get rid of the parameters this way.
– let’s also rename the method to getCharge() so as
to clarify what it is doing.
– as a result, back in customer, we must delete the
old method and change the call from
amountFor(each) to each.getCharge()
• compile and test
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();
Call to amountFor has been replaced
and getCharge() is called directly
thisAmount = each.getCharge();
… the rest continues as before
New Class Diagram
Movie
priceCode: int
1
Rental
*
daysRented: int
getCharge()
*
1
Customer
statement()
• No major changes; however, Customer is now a
smaller class and an operation has been moved to
the class that has the data it needs to do the job
© Copyright 2026 Paperzz