Introduction To Refactoring
Introduction To Refactoring
Refactoring
• Refactoring is:
– restructuring (rearranging) code in a series of small, semantics-
preserving transformations (i.e. the code keeps working) in order to
make the code easier to maintain and modify
• Refactoring is not just arbitrary restructuring
– Code must still work
– Small steps only so the semantics are preserved (i.e. not a major re-
write)
– Unit tests to prove the code still works
– Code is
• More loosely coupled
• More cohesive modules
• More comprehensible
• There are numerous well-known refactoring techniques
– You should be at least somewhat familiar with these before inventing
your own
– Refactoring “catalog”
When to refactor
• You should refactor:
– Any time that you see a better way to do things
• “Better” means making the code easier to understand and to
modify in the future
– You can do so without breaking the code
• Unit tests are essential for this
• You should not refactor:
– Stable code that won’t need to change
– Someone else’s code
• Unless the other person agrees to it or it belongs to you
• Not an issue in Agile Programming since code is communal
The refactoring environment
• Traditional software engineering is modeled after traditional
engineering practices (= design first, then code)
• Assumptions:
– The desired end product can be determined in advance
– Workers of a given type (plumbers, electricians, etc.) are interchangeable
• Agile software engineering is based on different assumptions:
– Requirements (and therefore design) change as users become acquainted
with the software
– Programmers are professionals with varying skills and knowledge
– Programmers are in the best position for making design decisions
• Refactoring is fundamental to agile programming
– Refactoring is sometimes necessary in a traditional process, when the
design is found to be flawed
Where did refactoring come
from?
• Ward Cunningham and Kent Beck influential
people in Smalltalk
• Kent Beck – responsible for Extreme
Programming
• Ralph Johnson a professor at U of Illinois and
part of “Gang of Four”
• Bill Opdyke – Ralph’s Doctoral Student
• Martin Fowler - http://www.refactoring.com/
– Refactoring : Improving The Design Of Existing
Code
Back to refactoring
• When should you refactor?
– Any time you find that you can improve the design of
existing code
– You detect a “bad smell” (an indication that something is
wrong) in the code
• When can you refactor?
– You should be in a supportive environment (agile
programming team, or doing your own work)
– You are familiar with common refactorings
– Refactoring tools also help
– You should have an adequate set of unit tests
Refactoring Process
• Make a small change
– a single refactoring
• Run all the tests to ensure everything still
works
• If everything works, move on to the next
refactoring
• If not, fix the problem, or undo the change,
so you still have a working system
Code Smells
• If it stinks, change it
– Code that can make the design harder to change
• Examples:
– Duplicate code
– Long methods
– Big classes
– Big switch statements
– Long navigations (e.g., a.b().c().d())
– Lots of checking for null objects
– Data clumps (e.g., a Contact class that has fields for address,
phone, email etc.) - similar to non-normalized tables in relational
design
– Data classes (classes that have mainly fields/properties and little
or no methods)
– Un-encapsulated fields (public member variables)
Example 1: switch statements
• switch statements are very rare in properly
designed object-oriented code
– Therefore, a switch statement is a simple and easily
detected “bad smell”
– Of course, not all uses of switch are bad
– A switch statement should not be used to distinguish
between various kinds of object
• There are several well-defined refactorings for
this case
– The simplest is the creation of subclasses
Example 1, continued
• class Animal {
final int MAMMAL = 0, BIRD = 1, REPTILE = 2;
int myKind; // set in constructor
...
String getSkin() {
switch (myKind) {
case MAMMAL: return "hair";
case BIRD: return "feathers";
case REPTILE: return "scales";
default: return “skin";
}
}
}
Example 1, improved
class Animal {
String getSkin() { return “skin"; }
}
class Mammal extends Animal {
String getSkin() { return "hair"; }
}
class Bird extends Animal {
String getSkin() { return "feathers"; }
}
class Reptile extends Animal {
String getSkin() { return "scales"; }
}
How is this an improvement?
• Adding a new animal type, such as Amphibian,
does not require revising and recompiling existing
code
• Mammals, birds, and reptiles are likely to differ in
other ways, and we’ve already separated them out
(so we won’t need more switch statements)
• We’ve gotten rid of the flags we needed to tell one
kind of animal from another
• We’re now using Objects the way they were
meant to be used
Example 2: Encapsulate Field
• Un-encapsulated data is a no-no in OO application
design. Use property get and set procedures to provide
public access to private (encapsulated) member
variables.
public class Course
{
private List students;
public class Course
public List getStudents()
{
{
public List students;
return students;
}
}
public void setStudents(List s)
{
students = s;
int classSize = course.students.size(); }
}
– Also:
• Rename Method
• Change Method Parameters
3. Extract Class
• Break one class into two, e.g. Having the phone details as part of
the Customer class is not a realistic OO model, and also breaks the
Single Responsibility design principle. We can refactor this into two
separate classes, each with the appropriate responsibility.
double getPayAmount() {
if (isDead) return deadAmount();
if (isSeparated) return separatedAmount();
if (isRetired) return retiredAmount();
return normalPayAmount();
};
14. Replace Parameter with
Explicit Method
• You have a method that runs different code depending on the
values of an enumerated parameter. Create a separate method for
each value of the parameter.
public TriviaData()
{
data = new ArrayList<TriviaQuestion>();
}
public TriviaQuestion()
{
question = "";
answer = "";
value = 0;
type = FREEFORM;
}
public TriviaGame()
{
// Load questions
questions = new TriviaData();
questions.addQuestion("The possession of more than two sets of chromosomes is termed?",
"polyploidy", 3, TriviaQuestion.FREEFORM);
questions.addQuestion("Erling Kagge skiied into the north pole alone on January 7, 1993.",
"F", 1, TriviaQuestion.TRUEFALSE);
questions.addQuestion("1997 British band that produced 'Tub Thumper'",
"Chumbawumba", 2, TriviaQuestion.FREEFORM);
questions.addQuestion("I am the geometric figure most like a lost parrot",
"polygon", 2, TriviaQuestion.FREEFORM);
questions.addQuestion("Generics were introducted to Java starting at version 5.0.",
"T", 1, TriviaQuestion.TRUEFALSE);
}
TriviaGame.java
// Main game loop
public static void main(String[] args)
{
int score = 0; // Overall score
int questionNum = 0; // Which question we're asking
TriviaGame game = new TriviaGame();
Scanner keyboard = new Scanner(System.in);
// Ask a question as long as we haven't asked them all
while (questionNum < game.questions.numQuestions())
{
// Show question
game.questions.showQuestion(questionNum);
// Get answer
String answer = keyboard.nextLine();
// Validate answer
TriviaQuestion q = game.questions.getQuestion(questionNum);
if (q.type == TriviaQuestion.TRUEFALSE)
{
if (answer.charAt(0) == q.answer.charAt(0))
{
System.out.println("That is correct! You get " + q.value + " points.");
score += q.value;
}
else
{
System.out.println("Wrong, the correct answer is " + q.answer);
}
}
TriviaGame.java
else if (q.type == TriviaQuestion.FREEFORM)
{
if (answer.toLowerCase().equals(q.answer.toLowerCase()))
{
System.out.println("That is correct! You get " + q.value + " points.");
score += q.value;
}
else
{
System.out.println("Wrong, the correct answer is " + q.answer);
}
}
System.out.println("Your score is " + score);
questionNum++;
}
System.out.println("Game over! Thanks for playing!");
}
}