Refactoring
The "bible" of refactoring is Fowler's book,
Refactoring. It is a very well-written book which I would have made a
required text for the course if it didn't cost $50.
Refactoring by Example
We spent a whole lecture working through the
example of Refactoring Chapter 1.
Foundations of Refactoring
Here are the key points of Fowler chapter 2.
Why Refactor?
- To improve the quality of the codebase
- Makes software easier to understand
- This in turn helps in finding bugs
- .. and in turn allows you to program faster in the end.
When to Refactor?
- Most important: refactor in order to elegantly incorporate new
functionality.
-- one of the biggest sins is not to refactor in this case,
and after several extensions, one arrives at . . . evil spaghetti code!
- The rule of three: if you do three things the same time in an
implementation, its time to refactor to avoid more repetition in the future
- around a consistently buggy area: bugs are a sign that code is bad, and
refactoring will improve that code.
- at "code review" time in the project lifecycle. A code review is a meeting
at which the state of the code is reviewed in detail.
Everyone has a
different design methodology that requires more or less refactoring.
- Beethoven was a serious refactorer: he wrote, threw out, redid, patched
etc his symphonies
- Mozart wrote great musical code right off his head.
Shortcomings with Refactoring
- External systems that can't be refactored: if you are using a external
database or large code framework, those can't be refactored, limiting the
improvements that can be made.
- Interfaces that are published should change as little as possible, but
refactoring often wants to change those interfaces. There is a
significant tension here.
- If the design is too awful, its not worth trying to refactor---throw it
out!
Refactoring and Design
- Refactoring is almost as important in design as in implementation
Same
principles apply, but at higher level.
- Don't overdesign, spending too much time on design
--some problems are
more clear when code takes shape.
Testing and Refactoring
Unit testing is critical to refactoring. Fowler
chapter 4 covers JUnit, the unit testing framework. We covered this in the implementation
lecture.
Here is the sequence of events to follow to test in the context of a
refactoring:
- Make sure you have unit tests covering the code you are about to change;
if not write them
- Make sure you are 100% compliant with tests before refactoring
- Refactor
- Re-run tests and get back to 100%
If you have a series of
refactorings in mind, re-test between each change.
Bad Smells in Code
See Fowler Chapter 3.
Bad Smells and refactorings to clean them up. (We
are not going to delve into the proposed refactorings below in detail, but the
names should give the idea.)
- Duplicated Code
--extract out the common bits into
their own method (extract method) if code is in same class
--if
two classes duplicate code, consider extract class to create a new
class to hold the shared functionality.
- Long Methods
--extract method!
- Long Parameter List
--replace parameter with
method (receiver explicitly asks sender for data via sender getter
method)
Example: day month, year, hour minute second ==> date
- Divergent Change
If you have a fixed class that does
distinctly different things consider separating out the varying code into
varying classes (extract class) that either subclass or are contained
by the non-varying class.
- Shotgun Surgery
The smell: a change in one class
repeatedly requires little changes in a bunch of other classes.
--try to
move method and move field to get all the bits into one
class since they are obviously highly dependent.
- Feature Envy
Method in one class uses lots of pieces
from another class.
--move method to move it to the other class.
- Data Clumps
Data that's always hanging with each other
(e.g. name street zip).
--Extract out a class (extract class) for
the data. Will help trim argument lists too since name street zip now passed
as one address object.
- Switch (case) statements
--Use inheritance and
polymorphism instead (example of this was in Fowler Chapter 1; this is one of
the more difficult refactorings)
- Lazy Class
Class doesn't seem to be doing anything.
Get rid of it!
- collapse heirarchy if subclasses are nearly vacuous.
- inline class (stick the class' methods and fields in the class
that was using it and get rid of original class).
- Speculative generality
Class designed to do something
in the future but never ends up doing it. Thinking too far ahead. Or you
though you needed this generality but you didn't.
--like above,
collapse hierarchy or inline class
- Message chains
Say you want to send a message to
object D in class A but you have to go through B to get C and C to get D.
--use hide delagate to hide C and D in B, and add a method to B
that does what A wanted to do with D.
- Inappropriate Intimacy
Directly getting in and munging
with the internals of another class.
--To fix this, move methods, inline
methods, to consolidate the intimate bits.
- Incomplete Library Class
If method missing from
library, and we can't change the library, so either:
- make this method in your object (introduce foreign method) If
there is a lot of stuff you want to change:
- make your own extension/subclass (introduce local extension)
- Data Class
We have already talked about this
extensively: in data-centric design, there are some data classes which are
pretty much struct
s: no interesting methods.
--first don't let
other directly get and set fields (make them private) and don't have setter
for things outsiders shouldn't change
--look who uses the data and how they
use it and move some of that code to the data class via a combination of
extract method and move method (see the Fowler chapter 1
example for several examples of this)
- Comments
Comments in the middle of methods are
deodorant. You should really refactor so each comment block is its own method.
Do extract method.
Last modified: Fri Nov
8 23:06:05 EST 2002