Refactoring - School of Computing and Engineering

advertisement
Refactoring
Refactoring is a step in the
software design process
Refactoring – Defined
• (What) Code refactoring is the process of
changing a computer program's internal structure
without modifying its external functional
behavior.
• (Why) The purpose of refactoring is to make
existing code easier to understand, modify and
extend.
• The term ‘refactoring’ is both a noun and a verb.
There are catalogs of refactorings (noun) that
simplify the process of refactoring (verb) code.
• Refactoring reduces technical debt (the “principle”
part).
Example
...
if (i != min) {
int temp = num[i];
num[i] = num[min];
num[min] = temp;
}
...
Program behavior
doesn’t change but
the resulting code is
easier to read and
understand.
Extract Method
...
if (i != min) {
swap(ref num[i], ref num[min]);
}
...
void swap(ref int a, ref int b) {
int temp = a;
a = b;
b = temp;
}
When to Refactor
• During new development. Writing well-designed code in one pass is
not easy. Solution: divide and conquer; write code in two steps. Step 1:
make it work. Step 2: make it better; refactor the code until the design
is satisfactory. This is standard practice in test-driven development
(TDD).
• During maintenance. Making changes to existing code can be risky.
Minimize the risk by making changes in two steps. Step 1: refactor the
code to a state that will more easily accept the planned changes. Step
2: make the changes.
• In between maintenance changes. Routine changes to software
gradually increase software complexity (aka entropy or technical debt)
unless extra effort is devoted to maintaining or reducing complexity.
(This tendency is Layman’s second law of software maintenance.)
When turnaround time for a change is short, there isn’t always time to
maintain or reduce system complexity with each change. Refactoring
between code changes reverses the adverse effects of quick fixes and
makes future maintenance easier.
Systematic Refactoring is more
than just “cleaning up code”
• Refactoring is more than just “cleaning up code” the same way
an accounting audit is more than just “looking over the books”.
• The term refactoring is sometimes used to refer to any change
that improves code.
• Here the term is being used to refer to systematic code changes
that address reoccurring or identifiable problems. Both the
problem and the systematic change have well-known names.
• There are catalogs of refactorings that provide routine solutions
to reoccurring problems found in existing code just as there are
catalogs of design patterns that provide routine solutions to
reoccurring problems faced during design. Like catalogs of
design patterns, catalogs of refactoring also provide a
vocabulary for discussing possible code changes.
Refactoring makes software
development more routine
• Catalogs of refactorings serve as another
handbook for software engineers. They offer
routine solutions to reoccurring problems found in
code.
• Use of these handbooks makes software
development and maintenance more routine,
systematic and predictable.
• Documented refactorings reduces the portion of
the development process that still depends on
inspiration and creativity.
Routine Design
Is your code ready to be refactored?
• Step 0: make sure you have object-oriented code.
• Most refactorings assume OO code.
• A more serious condition—one that should be
addressed before you refactor—is not having
object-oriented code to start with.
• Code is object-oriented when you have classes
that encapsulate data and these classes have
methods that act on the data. The objects created
from these classes correspond to significant
abstractions in the problem and/or solution
domain.
Refactorings are Transformations
• Refactorings are transformations on code
A
B
• They transform existing code that is poorly structured and
difficult to understand and maintain into code with
improved structure that is easier to understand and
maintain.
• Refactorings have names like:
• Rename method
• Extract superclass
• Separate Query from Modifier
Example Transformation/Refactoring
Move Method
Knowing when to apply a
refactoring
• If you have a clear understanding of the
transformation a refactoring performs, you might
be able to use your intuition to recognize when it
applies. For example, if you find a cryptic method
name in your code, rename method seems like an
obvious choice.
• There are many instances when a refactoring can
be applied. (For example, you can rename any
method.) How do you know when a particular
refactoring should be applied?
Knowing when to apply a
refactoring [Cont]
• Could just leave it up to your intuition to
recognize when a refactoring is needed.
However, if you are just starting out, your
intuition might not be developed.
• What is needed are guidelines for when to
apply a refactoring that are a little more
tangible than “this code just doesn’t feel
right”.
Code Smells
• Code smells or pathologies are symptoms in
code that suggest a deeper problem. Most code
smells are paired with one or more
corresponding refactorings for dealing with the
problem.
• The presence of symptoms doesn't always
imply a change is needed. Design often
involves tradeoffs among competing priorities.
The code might be the way it is for a reason.
• Code smells and their corresponding
refactorings are rules of thumb not absolutes.
Candidate Refacotorings Associated with
Certain Code Smells
More Code Smells
• Too many parameters
• Inappropriate intimacy – “a class that has dependencies on
implementation details of another class.”
• Contrived complexity – forcing the use of a really cool
design pattern when a simpler design would have sufficed.
• Excessively long identifiers
• Excessively short identifiers
• Excessive use of literals – consider replacing with named
constants.
• Type Embedded in Name – “Avoid placing types in
method names; it's not only redundant, but it forces you to
change the name if the type changes.”
Some Code Smells Overlap with
Design Principles
Appropriateness of a refactoring
depends on context
• Refactorings don’t automatically take your code to
a better state. Just because there is a refactoring A
 B and you find state A in your program, there is
no guarantee that state B is better for your program
than state A.
• When to apply a refactoring depends on context.
• Here’s proof. There are pairs of refactorings that
are opposites.
–
–
–
–
Extract Method  Inline Method
Extract Class  Inline Class
Collapse Hierarchy  Extract Subclass
Hide Delegate  Remove Middle Man
Duplicated Code
•
•
•
Duplicated code is a specific case of the don’t repeat yourself principle (DRY).
Duplicate code is when the same code or nearly the same code appears in more
than once place.
Duplicate code is undesirable because:
– It takes more effort to make a change to the code. The change has to be made in all
locations. Easy to make a mistake and allow code to become inconsistent.
•
Candidate refactorings:
– Extract Method – when duplicate code is in the same class, use extract method to
get the code in one place and then replace chunks of duplicated code with a call to
this new method.
– Pull Up Field – when subclasses have duplicate fields use pull up field to move the
field declaration to a superclass. There may also be an opportunity to move
behavior in subclasses to a superclass as well.
– Form Template Method – When the same sequence of steps are repeated in
subclasses but with unique behavior for some or all steps, the Template Method
design pattern can be used to define the skeleton of the algorithm in a superclass
with the behavior of the steps deferred to polymorphic methods in subclasses.
(This is a good example of refactoring to patterns. Patterns are often targets of
refactoring.)
– Extract Class. Used when duplicate code is in two or more unrelated classes.
(Before applying extract class, consider whether or not it would be better to make
the duplicate code a method of one class that the others call.)]
Long Method
• Methods that are too long are difficult to understand and
reuse.
• The general rule-of-thumb is a method should fit on one
page. This makes it easier to comprehend.
• It’s not so much the number of lines in a method that make
it too long, it’s the variety of activity that takes place
during the method. (Single Responsibility Principle)
• When Extract Method is used to divide a long method, a
good method name can add significant semantic value.
• Candidate Refactorings:
– Extract Method. If many local variables result in long parameter
lists on extracted objects, consider using Introduce Parameter
Object to minimize the number of parameters passed to extracted
methods.
Large Class
• Similar to long methods, large classes are classes with too
much code.
• Often it’s not the number of lines of code that is the
problem, but rather how the code is used. A class is too
large if:
– It deals with more than one abstraction and/or these abstractions
are unrelated. (A direct violation of the single responsibility
principle.)
– The volume of code makes it difficult to understand.
• Candidate Refactorings:
– Extract Class. When class cohesion begins to break down and the
features of a class no longer pertain to a single responsibility,
consider moving some of the features to a second class.
– Extract Subclass. When features of a class only apply to certain
instances of a class, consider moving some of these conditional
features to a subclass.
Divergent Change
• The divergent change code smell is what you notice when
inspecting a class that doesn’t follow the Single
Responsibility Principle (SRP) of design.
• Divergent change is when a class changes in different
ways for different reasons.
• Consider the most likely reasons for making changes to the
software you are writing. What modules are affected by
each of the reasons for change? Is there any one module
that is changed for more than one reason? If so, consider
using Extract Class to split this class into two classes
based on the reasons for change.
• Example: class that calculates and prints values for a
report. It will change when calculations change or format
of report changes.
Shotgun Surgery
• Shotgun surgery is the opposite of divergent
change. Instead of having one class with
multiple reasons to change you have one
reason for a change affecting multiple
classes.
• Candidate Refactorings:
– Move Method – Move Field – Inline Class -
• Divergent Change: a module has more than
one reason to change.
• Shotgun Surgery: a single reason to change
affects more than one module.
Feature Envy
• A tall tale sign of feature envy is a method that relies more
on data from another class than its own class in order to
fulfill its responsibility.
• Characteristic of good OOD is keeping data and the
methods that operate on this data in close proximity.
• Candidate Refactorings:
– Use Move Method to move the method to the class that has the
preponderance of data it needs to fulfill its responsibility.
– When it is a section of a method that seems envious, consider
Extract Method to isolate the code followed by Move Method to
place it closer to the data it needs.
Lazy Class
• A lazy class is one that doesn't do much.
Perhaps you had grand plans for it when
first created, but now it contains just a few
fields and mostly getter and setter methods.
• Candidate Refactorings:
– Collapse Hierarchy –
– Inline Class –
Data Class
• Classes with mostly getting and setting methods.
• To promote a data class to a genuine class, find the
methods that use the data in the data class and consider
moving them to the data class.
• Candidate Refactorings:
– Move Method –
– If the method that uses the data can’t be moved as is, consider
applying Extract Method to isolate the logic that is using the data
and then Move Method on the newly isolated logic.
Speculative Generality
• Abstraction in program design can be a good thing. It is the
primary means of managing complexity and can enable
reuse. However, like all good things in life it can be
pursued to excess.
• For example, you defined an abstract class with visions of
having lots of code written to the interface of the abstract
class working with a number of subclasses. However, so
far you have just one subclass and prospects for others is
increasingly unlikely.
• Candidate Refactorings:
– Collapse Hierarchy –
– Inline Class –
Middle Man
• Delegation is one way of achieving information hiding.
For example, a grocery store is a delegate for farmers and
ranchers. It acts as a middle man between consumers and
producers. Buying produce from the grocery store
simplifies your life and insulates you from the complexity
of dealing directly with producers.
• However, if you find yourself going to the same store just
for milk, eggs and butter, maybe it’s more efficient to just
skip the middle man and go straight to the dairy farmer (or
farmer’s market) for your produce.
• Candidate Refactorings:
– Remove Middle Man –
– Inline Method –
Remove Middle Man
Remove Middle Man
Hide Delegate
Comments
• Comments are great you should use them regularly;
however, if you have to comment code to make it
understandable, consider refactoring to make the code
more understandable.
• For example, you might use Extract Method in order
to create a descriptive name for a chunk of code.
• Another example: you might use Replace Magic
Number with Symbolic Constant to make code more
readable.
• Good variable and method names make comments a
part of the code.
Refused Bequest
• A class that violate the principle of
substitution is refusing the bequest of its
superclass.
• Consider: Replace Inheritance with
Delegation
Extract Interface
• Refactoring can also help you conform to
principles of good design.
• Example: Interface
Segregation Principle
• If a clients of a class
are interested in only a subset of methods
on a class, consider using Extract Interface
to create a more narrow interface for the
client.
Refactoring – How to
• Each refactoring provides a systematic procedure
for making a particular code change.
• For each refactoring there are step-by-step
instructions for making the (function-preserving)
code change.
• Small steps reduce the changes of making an
error. They also make it easy to catch any errors
that are made.
• During refactoring you shouldn’t:
– Add new functionality
– Change existing functionality
– Fix defects
Many programming devolvement
environments (IDE’s) offer automated
support for refactoring
• Eclipse (right)
• Visual Studio (below)
Systematic Refactorings
• Rename Method – Shakespeare clearly wasn’t a programmer. Ask any
good programmer, “What’s in a name?” and the answer you are likely
to get is: “A lot!” Good names convey meaning in a way that is
inextricably tied to the code.
• Move Method and Move Field – Moving methods and fields from one
class to another can reduce coupling and/or increase cohesion.
• Encapsulate Field – Make a public field private and add accessors.
With C# you can convert the field to a property.
• Reduce Scope of Variable –
• Extract method – Turn a sequence of statements into a method with a
descriptive name.
• Inline method – (Opposite of extract method.)
• Hide delegate – Add methods to hide a delegate.
• Replace conditional with polymorphism (complex)
More Systematic Refactorings
• Introduce Assertion – Add an assertion to make an assumption in your
code explicit.
• Extract Superclass – When you have two or more classes with similar
features (e.g. SceneItem)
• Extract Subclass – A class has features that are only used by some
instances.
• Pull Up Field – Move a field from a subclass to a superclass.
• Pull Up Method – Move a method from a subclass to a superclass.
• Push Down Field – Move a field from a superclass to a subclass.
• Push Down Method – Move a method from a superclass to a subclass.
Extract Method
• Extract method is not only for removing duplicate
code. It can also be used to improve readability
and understandability.
• Extract method is also useful when a method gets
too long to easily read and understand. Adding
descriptive method names introduces selfdocumentation.
• Added bonus: fine-grain methods are easier to test
and reuse.
Extract Method Example
// Returns true if there is a collision.
public bool pixelPerfectCollision(Sprite s) {
// Check to see if area of s overlaps with
// with self
. . .
if (!objectsOverlap) {
return false;
}
else {
// Check to see if non-opaque pixels in
//
both objects overlap
. . .
}
}
// Returns true if there is a collision.
public bool pixelPerfectCollision(Sprite s) {
return (
boundsOverlap(s)&& opaquePixelsOverlap(s)
);
}
Reduce Scope of Variable
Encapsulate Field
Replace Inheritance with Delegation
Inline Class
Extract Class
Replace Magic Number with
Symbolic Constant
Functional:
// Calculate the length of the outer crust of each
// piece of pizza given the diameter of the pizza
// and number of desired pieces.
public float lengthOfOuterCrust(float diameter, int numberOfPieces) {
return (3.1415 * diameter) / numberOfPieces;
}
Better:
public static final float PI = 3.1415;
public float lengthOfOuterCrust(float diameter, int numberOfPieces) {
float circumference = PI * diameter;
return circumference / numberOfPieces;
}
Remove Middle Man
Download