How to do Refactoring - University of Sunderland

Download Report

Transcript How to do Refactoring - University of Sunderland

How and When to do Refactoring
CSE301
University of Sunderland
Harry R. Erwin, PhD
Resources
• Eclipse automates a lot of refactoring (8)).
• Fowler, 2000, Refactoring: Improving the Design
of Existing Code, Addison-Wesley. Note: this is
available electronically at the library.
• http://www.amazon.com/exec/obidos/tg/detail//0321109295/103-4060125-0311065
• http://www.amazon.com/exec/obidos/ASIN/01306
48841/103-4060125-0311065
• http://www.refactoring.com/catalog/
• http://www.win.ua.ac.be/~lore/refactoringProject/i
ndex.php
Final Year Project Comments
• A few comments on OO programming. Important
refactorings will be in dark blue.
– Don't translate a C program into Java. Think objects.
– Avoid overusing static fields and methods. That suggests
you're writing a C program in Java. Don't go there.
– Use 'move method' to push responsibilities into subclasses.
Students often define methods in the calling classes rather
than in the called classes, and then need to correct the design.
– Look for polymorphism. It can be used rather elegantly.
– Are you using JUnit and TDD? Use the existing suite and
read the documentation about asserts!
Refactoring that Eclipse Supports
Already!
•
•
•
•
Rename
Move
Change Method Signature
Convert Anonymous
Class to Nested Class
• Change Nested Class to
Top-Level Class
• Push Down
• Pull Up
• Extract Interface
• Use Supertype Where
Possible
• Inline
• Extract Method
• Extract Local Variable
• Extract Constant
• Convert Local Variable to
Field
• Encapsulate Field
A Catalog of Refactorings
•
•
•
•
•
•
•
Composing Methods
Moving Features Between Objects
Organizing Data
Simplifying Conditionals
Making Method Calls Simpler
Generalization
Big Refactorings
The Basic Rule of Refactoring
• “Refactor the low hanging fruit”
http://c2.com/cgi/wiki?RefactorLowHangingFruit
• Low Hanging Fruit (def): “The thing that gets you
most value for the least investment.”
• In other words, don’t spend much time on it.
There are ways to improve any design
incrementally. We will explore a few of them.
The Goal of Refactoring
• To improve code without changing what it
does.
• This in some ways is similar to how an
optimizing compiler restructures code.
• These how-to’s describe things you can do.
• Think about why they work!
Composing Methods
•
•
•
•
Extract Method
Inline Method
Replace Method with Method Object
Substitute Algorithm
Extract Method
• Extract Method is one of the most important
refactorings. If you see a patch of coherent code
that does something like compose a pane in a GUI
encapsulate it as a method. This is my favorite
refactoring; usually I apply it when a method gets
too long.
• Take a clump of related code and make it into a
small method. eclipse handles this well.
• It does have problems with local variables….
Example for Extract Method
final JCheckBox oos = new JCheckBox(
"Out of Supply ",unit.outOfSupply);
ActionListener oosListener = new ActionListener(){
public void actionPerformed(ActionEvent e){
theUnit.outOfSupply = oos.isSelected();
theBattle.recompute();
}
};
oos.addActionListener(oosListener);
res.add(oos);
That Code Becomes
final JCheckBox oos = addOOS(unit, theUnit);
res.add(oos);
AND
private JCheckBox addOOS(Ground unit, final Ground theUnit) {
final JCheckBox oos = new JCheckBox(
"Out of Supply ",unit.outOfSupply);
ActionListener oosListener = new ActionListener(){
public void actionPerformed(ActionEvent e){
theUnit.outOfSupply = oos.isSelected();
theBattle.recompute();
}
};
oos.addActionListener(oosListener);
return oos;
}
Inline Method Reverses Extract
Method
• You use Inline Method when the code the results
from Extract Method is uglier than the original.
• You also use it when the method’s body is just as
clear as its name.
• Or if you have a group of badly factored
methods—inline them and then re-extract
methods.
• Remember to get rid of the method—needless
indirection is irritating and makes you look dumb.
Replace Method with Method
Object
• Replace Method with Method Object moves local
variables into class fields of a ‘functor’, so
Extract Method can be used more easily.
• The curse of extracting methods is local variables.
So introduce method objects if you have a big
method with lots of local variables. Applying this
refactoring converts all those local variables to
fields of the method object.
• Then you can extract methods freely.
Method Object Implementation
(after Beck via Fowler)
•
•
•
•
•
Create a new class named after the method.
Define a final field for the object that owned the original
method and a modifiable field for every local variable.
Define a constructor with those fields as arguments.
Give the class a method named ‘compute()’.
Replace the old method with one that creates the object and
calls compute().
int gamma(int val1, int val2, int val3) {
return new Gamma(this,val1,val2,val3).compute();
}
•
Now you can freely decompose compute() without passing
any parameters. This is a good thing.
Substitute Algorithm
• This one sounds a bit dumb. Have you
thought of a smarter or clearer way of doing
something? Go ahead and replace the
method with the new algorithm.
• If you’re using test-driven development,
you can quickly check whether the new
version works. If it doesn’t, backtrack, and
nobody will ever know…
Moving Features Between
Objects
• A fundamental design decision is where to
put responsibilities. You’ll never get it right
the first time. Refactoring allows you to
change your mind.
• When you want to move functions between
objects, these refactorings handle it. Move
Method, Move Field, and Extract/Inline
Class are the heavyweights here.
Move Field
• “Moving state and behavior between classes is the
very essence of refactoring.” (Fowler)
• You do this to shuffle responsibilities around.
Consider a move if a field is used more by other
classes than by its home class.
• If you do an Extract Class, the fields get moved
first and then the methods follow them.
• You may need to encapsulate a field first. Then
move the field and point any accessors at the new
location, perhaps using new accessors there.
Move Method
• “The bread and butter of refactoring.” (Fowler)
• A method is used by another class more than by its
home class. I know of cats like that.
• Create a similar method in the other class. Either
convert the old method to a simple delegation or
remove it entirely.
• Think about doing this after you’ve moved some
fields between classes. Warning: watch for
polymorphism on the original class. That can
make the move impossible.
Extract Class
• Classes grow, and you may have one class doing
work that should be done by two. You find you’re
violating the Single Responsibility Principle
(SRP) with lots of data and methods. Time to get
the scalpel out.
• Create a new class and move the relevant fields
and methods there. Delegate!
• If you don’t know why the SRP is important, wait
till next semester.
Inline Class
• You find a class isn’t doing very much.
This often occurs after a refactoring that has
removed fields and methods.
• Move its features into another class and
then delete it.
• Use Encapsulate Field, Move Field, and
Move Method to do this.
Hide Delegate
• A client calls a method defined on a field of a
server object, possibly violating encapsulation.
theServer.getManager().getDepartment();
• To do this, the client needs to know that theServer
has a Manager field. Replace with a
getDepartment() method for theServer. Then
unless getManager() is needed, remove it.
theServer.getDepartment();
Remove Middle Man
• “Here, you take the phone.” After some
development, you notice that a class is
doing a lot of simple delegation.
• Get the client to call the delegate directly.
• You may need to create an accessor for the
delegate, but thereafter a method only needs
to call on the delegate.
End of First Hour
Organizing Data
• These refactorings generally clean up problems
with how classes define and access fields.
• The most interesting refactoring here is probably
Duplicate Observed Data. That’s how you fix a
class that mixes business logic with GUI or SQL
code. We’ll spend some time on it since Fowler
isn’t very clear.
• By the way, value objects are objects that are
equal if their fields are equal—you override
equals() and hashCode() for them.
Self Encapsulate Field
• You are accessing a field of a class directly in a
method, but use of the field is becoming awkward.
• Create getting and setting methods (‘accessors’) for
the field and make the field private. Use these methods
within the class.
• Why not always do this? Because getting and setting
methods obscure what the code is doing.
• Doing this is particularly important if a subclass may
later need to override direct access with a computed
value.
• A closely related refactoring: Encapsulate Field.
Replace Data Value with Object
• You find that you have a data item (a primitive or
value type) that needs additional data or behavior.
• For example, the data might be a String that has a
special format.
• Turn the data item into an object that holds the
original data as a final field. Provide a constructor
and a getting method. If you need a setting
method, have it create a new instance of the class.
• You may need to Change Value to Reference, if
multiple instances need to share the data object.
Change Value to Reference
• Reference objects stand for one object in the real world.
• Value objects are defined entirely through their data values. You
need to know when two are equal, so you have to override equals()
and hashCode().
• A class has many instances, many of which have the same value.
You want to replace them with a single instance.
–
–
–
–
Replace the value constructor with a factory method.
Decide how to provide access to the objects.
Decide whether to precreate or create as required.
Alter the factory method to return the reference object.
Change Reference to Value
• You have a reference type object that is small, immutable, and
awkward to manage.
• Turn it into a value object.
– Check that it is really immutable (final) or can become immutable.
– Create an equals() and a hashCode() method. The easiest way to write
hashCode() is to do a bitwise xor (^) on all the hashcodes of the fields
referred to in the equals() method. Primitive types need to be ‘boxed’ for
this. E.g., to box i, Integer iBoxed = new Integer(i);
– The consider removing the factory method and replacing it with a public
constructor.
– Finally make the contents final.
Replace Array with Object
• You have inherited a design that uses an array
where different elements mean different things.
This is bad programming practice.
• Replace with an object that has a field for each
element.
• You do this by creating getters and setters for each
entry in the array, creating the fields, and then
removing the array and repointing the getters and
setters at the new fields.
Duplicate Observed Data
• This is how you pull a domain model (the business
logic) out of a poorly-designed system with no
model. If there are multiple windows, see
Separate Domain from Presentation.
• Copy the business data in the system to a domain
object. Set up observers to synchronize the domain
model with whatever is dependent on it.
• This is a tricky dribble, so pay close attention.
DOD Implementation (Part 1)
• Start with the dependent objects (e.g., parts of the GUI,
other interfaces or a database).
• Create a domain class as an Observable. Create
references to the dependent objects in the domain class.
• Make the dependent objects Observers of the domain
class, so that if it changes, they change. Write an
update() method for each. Each constructor has to
connect to the domain class, add itself as an Observer of
that class, and finally call its update() method on the
domain class instance.
• Use Self-Encapsulate Data on any domain data in the
dependent objects. Test—behavior should not change.
DOD Implementation (Part 2)
• Now for GUIs, add a method to the event handler that
uses the new setting methods to update the component
with its current value using direct access to the field. This
ensures user input goes through the setting method. This
will be important later. Test.
• Define corresponding data fields and getting/setting
methods in the domain class. Setting methods need to
trigger model updates and the notify mechanism. Test.
• Redirect the accessors for the dependent classes to read
and write the domain class fields. Modify the observer’s
update method to copy from the domain fields to the
dependent class using direct access. Test one last time.
Change Unidirectional to
Bidirectional Association
• You need a backpointer? First, lie down to see if
the need passes. If it doesn’t, do the following:
– Add a field for the backpointer.
– Decide which class controls the link.
– Create a helper method on the other side to send back
the backpointer.
– If the existing link modifier is on the controlling side,
just modify it to update the backpointers.
– If not, have it call a routine on the other side to update
the backpointers.
Change Bidirectional to
Unidirectional Association
• Reduce bidirectional to unidirectional whenever
possible.
• If nobody needs one direction, simply remove it.
• For clients who do need the other direction:
– Try Encapsulate Field on the backpointer
– Then try Substitute Algorithm on the getter and test
– If clients don’t need the getter, change each to get the
object in the field some other way.
– When no reader is left, dump the updater and remove
the backpointer.
Encapsulate Field
• A class field is publically accessible. Hide it.
• Some people consider this good programming
practice. Other people find it makes code
inefficient or hard to read. YMMV.
• Provide accessors—getField(), setField(arg)
• Then find all users and change them to use the
accessors.
• Finally, make the field private. This will tell you if
you have really found all the users.
• Closely related to Self Encapsulate Field.
Encapsulate Collection
• If a class contains a collection of instances, getters and
setters don’t work really well.
• First, initialize an empty collection when you create it.
• Encapsulate the collection field to create a getter and
setter.
• Next provide public methods to add and remove
elements. Modify users to use them instead.
• Modify your getter to return a read-only iterator (see
the Collections class for how).
• Think about moving code that uses the getter into the
class that owns the collection.
Replace Type Code
• With Class
– When a class has a numeric type code that doesn’t
affect its behavior—replace with a new class for each
code.
• With Subclasses
– If the type code does affect behavior, use subclasses
• With State/Strategy
– Or replace the type code with a state object if
subclassing doesn’t work
Replace Subclass with Fields
• You have subclasses that differ only in
methods that return constant (final) data.
• Define final fields to contain the data,
eliminate the subclasses, and return the field
values instead.
Simplifying Conditionals
• These simplify conditional logic (if/then/else).
Decompose Conditional is the most important.
The elements of an if/then/else are replaced with
method calls. These refactorings can also clean up
if/then/else messes where flags are used to control
sequencing or returns.
• Switch statements should be replaced with
polymorphism.
• A null object is a real ‘do-nothing’ object that
substitutes for a null value in a reference.
Decompose Conditional
• Suppose you have a complicated if-thenelse (or ?:) statement.
• Use Extract Method on the condition, the
then part, and the else part.
• If there is a nested conditional, try Replace
Nested Conditional with Guard Clauses
first.
Consolidate Conditional
Expression
• If you have a long list of ‘if(condi) return
0;’ statements in a method, combine them
into a single conditional and extract it.
• This does the same thing, is clearer, and sets
you up to use Extract Method.
Consolidate Duplicate
Conditional Fragments
• The same fragment of code is in all
branches of a conditional expression.
• Then move it outside of the expression.
• This makes things clearer. Consider using
Extract Method, too.
Replace Nested Conditional with
Guard Clauses
• If a method contains a complicated if-thenelse expression that doesn’t make clear
what the normal path is, use ‘guard
clauses’ for all the special cases.
• Handle each special case with an immediate
return. Then execute the normal path.
• Be clear!
Replace Conditional with
Polymorphism
• Indicator—you have logic that chooses
different behavior based on the type of an
object or the value of a flag.
• Consider splitting the behavior among
subclasses. Make the original method
abstract.
• You may have to replace type codes with
subclasses or state/strategy first.
Introduce Null Object
• You’re getting very tired of checking for null.
This is often the case for framework code.
• Then replace the null value with a do-nothing
‘null object’. (This is what an Adaptor class is in
swing, by the way.)
• This has the advantage of inheriting from Object
(or even from the base class of your hierarchy).
Unlike null, a ‘null object’ can have do-nothing
methods called on it. A lot of if-then-else cases
then disappear.
Making Method Calls Simpler
• These refactorings make interfaces more
straightforward.
• Rename Method is convenient to document what a
method does.
• Most of the remaining refactorings are used to get
rid of parameters, but be cautious in concurrent
programming.
• Factory methods hide the concrete implementation
of an interface.
Rename Method
• You have a method with a name like ‘foo’.
Change the name to mean something.
• This is important when you have lots of
small methods as a form of documentation.
• eclipse handles this very well.
Separate Query from Modifier
• You have a method that returns a value but
also changes the state of an object.
• This violates the SRP (discussed later).
• Break the method into two: one returns the
value, and the other changes the state.
Preserve Whole Object/
Replace Parameter with Method
• You get values from an object and use them as
parameters to a method call.
• Why not simply pass the object to the method and
let it get its own values? (You can even create a
parameter object to do this.)
• If you call a method to get a value to pass as a
parameter to a second method, why not let the
second method get the parameter?
Replace Constructor with
Factory Method
• You find you need to do more than simple
construction when you create an object.
• Make the constructor private and replace it
with a factory method.
• Factory methods are often static methods
that do a smart construction. See the
Singleton pattern for an example.
Generalization
• These refactorings clean up inheritance
hierarchies. They also allow you to ‘evolve’ the
hierarchy.
• Template methods are methods that call private
methods that can be subclassed to replace multiple
methods with the same signature.
• Sometimes delegation works better than
inheritance or vice versa, so don’t be afraid to try
it both ways.
Pull Up/Push Down
Field/Method
• These refactorings move fields and methods
within an inheritance hierarchy to improve the
structure and eliminate duplication.
• You may need to rename related fields or methods
to do this when they have acquired different
names during development.
• Take the method or field code, copy it to the new
location, and remove it from the old.
Pull Up Constructor Body
• You have constructors on subclasses that
are mostly identical.
• You can’t use Pull Up Method because
constructors aren’t inherited. 8)
• Create a superclass constructor containing
the common code and call this from the
subclass methods using super(args).
Extract Subclass/
Superclass/Interface
• When a class has features that are used only in certain
cases, give those features to subclasses. This is similar
to Extract Class using delegation. Extract Subclass is
usually simpler but more limited than Extract Class.
• Push down the methods first and later the fields. Watch
for fields that you can throw away.
• You can also use this to define a ‘null object’.
• Extract Superclass is the reverse process.
• Extract Interface creates an interface the same way.
Collapse Hierarchy
• After a while, a class hierarchy grows complex. If
you refactor it a bit, moving fields and methods
around and extracting classes and interfaces, you
may discover that some classes no longer do
anything useful. Get rid of them.
• Choose a class to sacrifice, move its remaining
fields and methods, and kill it. Don’t get blood on
your clothes.
Form Template Method
• You have two methods associated with sibling
subclasses that perform similar operations in the
same order, but the operations are not the same.
• Exploit polymorphism. Extract the operations into
methods with the same signature (names and
argument lists), so that the original methods
become the same. You may need to rename one.
Then you can pull the original methods up into a
superclass, and the differences live in the
subclasses.
Replace Inheritance with
Delegation (and vice versa)
• See the discussion of Extract Class versus
Extract Subclass.
• A subclass only uses part of a superclass
interface or doesn’t use inherited data.
• Change the subclass to a separate class and
delegate.
• Or the reverse…
• Whatever works.
Big Refactorings
•
•
•
•
Tease Apart Inheritance
Convert Procedural Design to Objects
Separate Domain from Presentation
Extract Hierarchy
Tease Apart Inheritance
• Your design was by a C++ programmer who believed
in multiple inheritance. 8(
• Create two hierarchies from the complex one and use
delegation to invoke one from the other.
– The more important of the two responsibilities should define
inheritance in the original tree.
– The lesser responsibility should have its own tree.
– Link them at the hip (abstract base class to abstract base
class) so that to the outside it looks like a single complex
hierarchy.
– You may need to Replace Constructor with Factory Method.
Convert Procedural Design to
Objects
• Someone who never took CSE301 did an OO design. It
looks like a C program 8(.
– Turn data into objects. Each record type should become a
dumb data object with get and set operations.
– Take all the procedural code and give it to a main class
(creating a big heap of powerful and fertile material, aka BS).
– Break up the main class behavior (using a shovel and Extract
Method).
– Move the behavior into the objects (using a rake and Move
Method).
– Finally delete the original main class, flush it down the
sewer, and celebrate with a beer.
Separate Domain from
Presentation
• Someone who never took CSE301 designed a GUI
program 8(.
– Create a domain class for each window.
– If the data on a window forms a grid, create classes for
each row and a collection to hold the rows.
– Separate the presentation from the domain using Move
Field and Duplicate Observed Data.
– Use Extract Method and Move Method to do the same
for behavior.
– Refactor the resulting mess mercilessly to clean up.
Extract Hierarchy
• You have a class that has a zillion special cases
since the designer never heard of the SRP or
polymorphism 8(.
• Make a list of important special cases. Extend it as
you learn more. Take your time to get it right.
– Create a subclass for each special case.
– Use Replace Constructor with Factory Method to
produce the subclasses.
– Methods with conditional logic should Replace
Conditional with Polymorphism or Extract Method.
– Refactor mercilessly to clean up.
Beck’s Comments on Putting it
all Together
• Even if you know the refactorings, you may not
know when to do it. Beck claims there is a rhythm
to refactoring.
• Practice it.
• You’re getting it when you start to feel confident
that you can take anything that has been screwed
up and make it better.
• You’re getting it when you can ‘stop with
confidence.’ You’ve done enough. If the code is
better, release it. If not, flush it.
The Feeling You Want
• You have a different relationship with your
program. The design is fluid and plastic and
under your control. It changes and adapts to
changes in your requirements.
• You can feel it.
Learning Refactoring
• Pick a goal—something’s wrong with the
program. Resolve to fix it. Then fix it.
• Stop when you’re unsure.
• Backtrack. Use test-driven development to make
sure you haven’t broken something. If you have,
back up.
• Work with a friend. ‘We explain to each other
what we don’t understand.’ You’ll learn a lot in
the process.