1
Refactoring Legacy Code
About me
2
• Itzik Saban
• A software developer since 2000
• B.A. in computer science (Open University)
• Agile programming mentor – TDD, Clean Code, Refactoring,
Continuous integration, etc.
• Part-time entrepreneur
• Co-organizer of the Clean-Code-Alliance at meetup.com
3
Objectives
4
• To understand the importance of refactoring.
• To acquire skills to do a day-to-day refactoring.
• To learn techniques to deal effectively with
legacy code.
• To acquire skills to deal effectively with large
scale refactoring operations
5
Introduction
6
The importance
of refactoring
7
As a system evolves, its complexity increases
unless work is done to maintain or reduce it.
Lehman’s law - increasing complexity
8
The act of improving design without
changing its behavior is called refactoring.
What is refactoring?
9
When to refactor?
• Fixing a bug.
• Adding a feature.
• The boy scout rule.
• Paying back technical debts.
Technical debts
10
What is a technical debt?
11
You need to
make a change.
You do it smart
and clean.
You do it quick
and dirty.
This will make future
changes very hard
This will take you longer
12
Every time you are compromising about your code, you are
creating a technical debt.
A technical debt is very similar to a financial debt:
• The principal amount is the cost of refactoring to a cleaner
design.
• The interest is the extra costs, which have to be paid in future
changes.
What is a technical debt?
13
Like financial debt, Technical Debt is not necessarily a bad thing.
Sometimes you create debts to leverage your business:
• First to market.
• Fines on delays.
• Special opportunities (black Friday for example)
But, if you are accumulating debts, eventually the
interest will take you down.
Is technical debt a bad thing?
14
In average applications carry a debt of
3.6$ per line of code, per year!
(Given by CAST - the world leader in software analysis and
measurement)
Technical debts – what’s the cost?
15
Technical debts
16
You are not truly free until you are debt
free!
Your code should be constantly
refactored.
Technical debts
17
IDE refactoring
catalog
18
IDE refactoring
Your IDE provides a plenty of refactoring
operations that are more safe and easy to
use.
Using these operations will improve your
code constantly.
A craftsman programmer MUST know
her/his IDE very well.
19
Rename )class/method/variable)
Motivation:
When the name of a class/method/variable does not reveal its
purpose.
“Any fool can write code that a computer can understand.
Good programmers write code that humans can
understand.”
(Martin Fowler)
IDE refactoring
20
Rename )class/method/variable)
Stand on the element you wish to rename and press
Shift+F6
IDE refactoring
21
Rename – published interface
If a class/method/field is being used by external projects
(as a .jar or as a .dll) it becomes published (a step
beyond a public)
To change a published interface, you have to retain both
the old interface and the new one, at least until your users
have had a chance to react to the change.
IDE refactoring
22
Generate getters and setters
Stand anywhere on the class and press
Alt+Insert
IDE refactoring
23
Introduce Explaining Variable
Motivation:
You have a complicated expression.
Put the result of the expression, or parts of the expression, in
a temporary variable with a name that explains the purpose.
IDE refactoring
24
Introduce Explaining Variable
IDE refactoring
25
Introduce Explaining Variable
Select an expression and press CTRL+ALT+V
IDE refactoring
26
Extract method
Motivation:
Small, well defined and well named methods are preferred.
Also useful to remove code duplications
IDE refactoring
27
Extract method (example)
IDE refactoring
28
Extract method (example)
IDE refactoring
29
Extract method
Select a block and press CTRL+ALT+M
IDE refactoring
30
Introduce Parameter Object
You have a group of parameters that naturally go together.
Replace them with an object.
IDE refactoring
31
Introduce Parameter Object
Motivation:
• Long parameter lists are hard to understand
• Pretty soon you see behavior that you can also move
into the new class
IDE refactoring
32
IDE refactoring
33
Introduce Parameter Object
Select a list of arguments and
press:
right-click->refactor->extract ->
Parameter object…
IDE refactoring
34
Extract Class
Motivation:
• Classes should be small with a single responsibility
• Often you need to extract a class to reuse it in different
places.
IDE refactoring
35
Extract Class
right-click->refactor->extract ->Delegate…
IDE refactoring
36
Extract SuperClass
Motivation:
• You need a new class which is very similar to an already
exists class except for small differences.
• Or, you already have two classes which are very similar
except for some small differences.
IDE refactoring
37
Extract SuperClass – example:
IDE refactoring
38
Extract SuperClass
right-click->refactor->extract ->Superclass…
IDE refactoring
39
Move method
Motivation:
Move methods when classes are collaborating too much
and are too highly coupled.
IDE refactoring
40
Move method – example:
IDE refactoring
41
Move method
Select a method and press
right-click->refactor->extract ->Move…
IDE refactoring
42
Pull members up
Motivation:
A field/method is duplicated on 2 or more subclasses.
This field/method should be located in the super class.
IDE refactoring
43
Pull members up
Select a method and press
right-click->refactor->extract ->Pull members up…
IDE refactoring
44
Push members down
Motivation:
A field/method on a superclass is relevant only for some
of its subclasses.
IDE refactoring
45
Push members down
Select a method and press
right-click->refactor->extract ->Push members down…
IDE refactoring
46
Exercise – Video store
47
Exercise – Video store
48
Introduction to
legacy code
49
Legacy code - intuition
BLACK BOX
SCARY
Tangled
UGLY
FRAGILE
BAD
DON’T TOUCH!!
What do you think of when you hear the term
“Legacy code”?
50
The definition of legacy code
A direct definition:
A legacy code is a code that we’ve gotten from someone else
Michael Feathers’ definition:
“To me, legacy code is simply code without tests”
Code without tests is bad code. It doesn’t matter how well written it is; it
doesn’t matter how pretty or object-oriented or well-encapsulated it is.
With tests, we can change the behavior of our code quickly and
verifiably. Without them, we really don’t know if our code is getting better
or worse.
51
Feedback loop
52
The essence of Agile programming are short
feedback loops.
The faster you get feedbacks, the smaller your
deviations are.
Short feedback loops
53
The best tool to achieve short feedback loops is
unit tests because they run very fast!
Unit tests – feedback loop
54
Refactoring is about changing the program in small steps
supported by tests. If you make a mistake, it is easy to find
the bug.
Yet again, what is refactoring?
55
What is the real purpose of automated tests?
• Automated tests are a very important tool, but not for
bug finding, not directly, at least.
• In general, the purpose of automated tests, is to
preserve behavior that is already there.
The purpose of automated tests
56
A good unit test is:
• Fast (0.1 second to run is slow!)
• Independent (why?)
• Repeatable (why?)
• Self validating (why?)
• Easy to write and maintain
Unit tests
57
A test is not a unit test if:
• It talks to a database.
• It communicates across a network.
• It touches the file system
• You have to do special things to your environment (such as
editing configuration files) to run it.
Unit tests
58
The legacy
code dilemma
59
Legacy code dilemma
60
“Before you start refactoring, check that you have a solid
suite of tests.
These tests must be self-checking.”
(Martin Fowler)
Tests & Refactoring
61
Legacy code dilemma
To put tests in place, we often have to change code. But,
when we change code, we should have tests in place.
During this process, you’ll have to make compromises.
There might be a scar left in your code after your work,
but everything beneath it can get better
If later you can cover code around the point where you
broke the dependencies, you can heal that scar, too.
62
The recipe for
legacy code
change.
63
• Identify change points
• Find tests points
• Make the code testable (break dependencies)
• Write tests
• Make changes and refactor
The recipe for legacy code change
64
To make the code clearer, do safe changes:
• Delete unused code
• Use the power of the IDE to make safe changes!
• Remove duplications
• Make heavier refactorings, but… revert changes
Identify change points
65
• Identify change points
• Find tests points
• Make the code testable (break dependencies)
• Write tests
• Make changes and refactor
The recipe for legacy code change
66
Interception points
An interception point is a place where a test can be
written to sense behavioral changes.
Class
A public method that
returns some value
Save some value to
DB / File / etc.
Save some value to a
global variable
Invoke methods of
other classes
67
Effect sketches
Effect sketches
generateIndex
elements
getElement
getElementsCount
addElement
69
Effect sketches
Effect sketches
generateIndex
elements
getElement
getElementsCount
addElement
Index.addText
Index.text
Index.getText
71
Exercise – Trivia game
72
Exercise – Trivia game
Pop Science Sport … … … … … … … … …
• You roll a dice (from 1 to 6)
• You move on the board accordingly and answer the relevant question.
• For a correct answer you get a coin
• For a wrong answer you get into penalty.
• As long as you are in penalty, your correct answers are ignored.
• You get out of penalty for every odd roll.
• The winner is the first player who has 6 coins.
73
Exercise – Trivia game
places
printCategroy
Print question
printPlayerLocation
currentCategory
askQuestion
74
• Identify change points
• Find tests points
• Make the code testable (break dependencies)
• Write tests
• Make changes and refactor
The recipe for legacy code change
75
Legacy code is almost never testable.
Make the code testable
76
In legacy, putting a class under test is hard:
• Objects of the class can't be created easily.
• The constructor we need to use has bad side effects.
• Significant work happens in the constructor, and we
need to sense it.
• High coupling between components
Make the code testable
77
Make the code testable
78
JUST DO IT.
79
Irritating parameter
80
Irritating parameter
81
Hidden dependency
82
Hidden dependency
83
Irritating global dependency
84
Irritating global dependency
Extract & Override
85
Irritating global dependency
Extract & Override
86
Irritating global dependency
87
Onion parameter
88
Onion parameter
89
Train wreck
Using the law of Demeter )tell don’t ask!)
90
Train wreck
Using Extract & Override
91
Initializer block
92
Initializer block
93
Static member initialization
94
Sprout class
95
• Identify change points
• Find tests points
• Make the code testable (break dependencies)
• Write tests
• Make changes and refactor
The recipe for legacy code change
96
So, how do we write the right tests?
Write tests
97
A characterization test is a test that characterizes the actual
behavior of a piece of code.
• Often, in legacy systems, what the system does is more
important than what it is supposed to do.
• If we make finding and fixing all of the bugs our goal, we'll never
finish.
Characterization tests
98
Algorithm for writing characterization test:
1. Write a test to some code and choose some state.
2. Write some arbitrary assertion that you know will fail.
3. Let the failure tell you what the behavior is.
4. Change the test so that it expects the behavior that the code produces.
5. Repeat.
Characterization tests
99
Characterization tests
Alter the test to pass:
Example:
100
Characterization tests
Alter the test to pass:
Another example:
101
Characterization tests
We could dedicate a good portion of our lives to writing
case after case for this class. When do we stop?
We aren't writing black-box tests here. We are allowed to look at
the code we are characterizing.
102
• Identify change points
• Find tests points
• Make the code testable (break dependencies)
• Write tests
• Make changes and refactor
The recipe for legacy code change
103
Exercise – Trivia game
104
Mikado
105
There are 2 main strategies to do large
changes:
• Big Bang
• Divide and Conquer
Refactoring strategies
106
This strategy can be implemented in two
ways:
o Do the large change on a separate branch and
do a large merge at the end.
o Stop the development of the old product and
start the development of a new one
Big Bang
107
A continuous refactoring of one unit into
smaller pieces until the desired goal is
achieved.
Divide & Conquer
108
The Mikado Method is considered as a
Divide & Conquer strategy.
The Mikado Method is a structured way
to make significant changes to complex
code.
The Mikado Method
109
Do complicated, long big changes on
the main brunch or stream, without ever
having a broken code base.
Mikado Method – the goal
110
The Mikado Method
111
The method itself is straightforward and
simple:
After you’ve decided on a goal, you start
to build a model of required changes
and dependencies by doing quick
experiments.
Mikado
112
Mikado graph
113
• Set a goal
• Experiment
• Visualize
• Undo
Mikado – basic concepts
114
Mikado – the algorithm
115
Mikado – demonstration
We want to get from this:
To this:
116
Mikado – demonstration
117
Mikado – demonstration
118
Mikado – demonstration
119
Mikado – demonstration
120
Mikado – demonstration
121
Mikado – demonstration
122
Mikado – demonstration
123
Exercise – Loans application
124
Exercise – Loans application
Three actions:
• Apply:
http://localhost:8080/?action=apply&amount=10000&contact=donald@ducks
.burg
• Fetch: http://localhost:8080/?action=fetch&ticketId=2
• Approve: http://localhost:8080/?action=approve&ticketId=2
125
Conclusions
126
Conclusions
• Refactor and clean your code
constantly, or else, it will become
harder and harder for it to evolve.
127
Conclusions
• Refactoring is about changing the
program in small steps supported
by (mostly unit) tests. If you make a
mistake, it is easy to find the bug.
128
Conclusions
• Legacy code is simply a code without
tests – without them, you are afraid to
clean it.
• If you are dealing with a legacy code,
put some constant effort to cover it
with a comprehensive suite of unit
tests.
129
Conclusions
• If you are about to make a change and
you know beforehand that this change is
going to be a medium/large scale
change – use the Mikado Method.
• If you started a change and this change
spirals out of control – revert everything
and start to use the Mikado Method.
130
Conclusions
• There are no free meals, to improve
your code, you need to put an effort!
An effort in writing tests, an effort in
refactoring.
131
Further reading
132
Automated testing
Integration tests are a scam
Pairwise testing
Why most unit testing is waste
Technical debts
Managing technical debts (1)
Technical debts with sonar
Managing techincal debt (2)
Books
Working effectively with legacy code [Michael C. Feathers]
The pragmatic programmer [Andrew Hunt, David Thomas]
Refactoring: improving the design of existing code. [Martin Fowler]
Clean Code [Robert C. Martin]
Further reading
133
Questions?
134
THE END

Refactoring workshop

Editor's Notes

  • #21 This of course will automatically rename all the references.
  • #25 Here we can see quite nested invocation of methods which hurts the code readability.
  • #26 Here we can see quite nested invocation of methods which hurts the code readability.
  • #56 That’s a very important issue – when people think about the benefits of auto testing and there ROI, they think about bugs finding and that’s a very poor goal.
  • #65 - Remove player1Name and player2Name Remove remarked code. Remove unused method “isTied()” Rename m_score1 to scoreOfPlayer1 (the same for 2) Extract the first switch case to a method called “getTiedScore” Extract the if expressions such as scoreOfPlayer1>=4 || scoreOfPlayer2>=4 to methods. Instead of scoreOfPlayer1 += 1 use scoreOfPlayer1 += POINT Show introduce variable on the code extractState(new StateLoader().loadFromDB()); Show heavier refactoring and then revert by refactoring the last switch statement.
  • #68 We need this change: generateIndex should not generate a new index every time we call it, but rather update or replace the existing index if any. Let’s find which outputs of this class our change is going to effect in order to test the right things.
  • #69 Is that it? Index.text is also influenced.
  • #71 Important to say: this does not mean that you will have to do this sketches for every change you are about to do, but it’s a good tool for complicated cases, and often it can be done informally in you head.
  • #72 Do effect sketches for the trivia game.
  • #73 SHOW THE CODE!!! INCLUDING THE RUNNER CLASS!!!
  • #78 The best way to understand if your class can go under test is to just do it.
  • #81 Clients of CreditValidator don’t need to change – they are not effected.
  • #82 Tell them what the code does and ask them what is the problem to test this kind of code.
  • #83 Again, clients of StockSeller don’t need to change – they are not effected.
  • #86 But some times it’s not worth it, the call to the singleton is scattered all over the code.
  • #87 Must the constructor of the singleton stay private? Often people create singletons because they want to have a global variable. They feel that it would be too painful to pass the variable around to the places where it is needed. In that case, we can relax the singleton property and make the constructor protected and keep the constructor of the derived class as private.
  • #92 Ask when this will be triggered? First, try to see if it can work this way, maybe it does not go to the DB…? If it’s not working on first shot, try to see if you can make it pass easily. If none of these works – refactor like in the next slide.
  • #94 Ask when this will be triggered? First, try to see if it can work this way, maybe it does not go to the DB…? If it’s not working on first shot, try to see if you can make it pass easily. If none of these works – refactor like in the next slide.
  • #104 Do effect sketches for the trivia game.
  • #124 Do effect sketches for the trivia game.
  • #125 Do effect sketches for the trivia game.