1. +
The Dark Art of Refactoring
Systems Analysis and Design
Michael Heron
2. +
Introduction
Refactoring is sometimes thought of as a vaguely ‘black art’ of
programming.
That stems from a basic misunderstanding of what refactoring
is all about.
Part of this impression comes from a sense of the subjectivity
that goes into refactoring.
While it’s easy to identify code that is wrong, it’s less easy to
identify which is the ‘best’ out of bits of ‘good’ code.
The ease at which a system can be refactored is influenced by
the way that system is designed.
3. +
Refactoring and the Impact of
Change
Refactoring is an ‘invisible’ action.
If you do it right, no-one should know you’ve done anything at
all.
Refactoring is not about adding extra functionality.
It may be a precursor to this.
Refactoring is not about fixing bugs.
Bugs may be fixed as a consequence of it.
Refactoring is about fixing the problems caused by code you
‘would have done correctly’ given the opportunity.
4. +
Refactoring and the Impact of
Change
Refactoring is highly dependent on the impact of change that
goes with your modifications.
In object orientation, this is predicted by the visibility of your
methods and attributes.
The benefit of techniques such as abstraction and
encapsulation can readily be seen when it comes time to
refactor code.
Refactoring an abstracted, encapsulated method is easy.
Refactoring a system with complex side-effects and distributed
processing is a nightmare.
Refactoring allows us to redesign architecture in running
programs.
5. +
The Aim of Refactoring
When we refactor, we’re looking to improve
the quality of the code.
Remove dead code
Make inefficient code more efficient
Make code more readable
Self-describing code is the best documentation you can
provide.
Make code more maintainable.
When we refactor, we try to get the code
looking like it would have done, if we’d have
written it properly the first time.
6. +
A Simple Example
Consider the following simple class:
public class Example {
private int bing;
public int getValue() {
return bing;
}
public void setValue (int b) {
bing = b;
}
}
Refactoring can be as simple as changing the name of a
variable to be more meaningful.
7. +
A Simple (?) Example
public class Example {
public int bing;
public int getValue() {
return bing;
}
public void setValue (int b) {
bing = b;
}
}
Depending on the impact of change, even changing a
variable name can be problematic.
8. +
Impact of Change
There’s a simple hierarchy we can follow when working out the
impact of change of refactoring:
Private variables and methods have low impact
Protected variables and methods have medium impact
Public variables and methods have high impact.
Impact of Change relates to the maintainability of your code.
How much of your code do you have to change?
As developers, we strive to ensure minimum impact of change.
You need to labour under the assumption that if someone has
access to a method or variable, they have taken advantage of that.
9. +
Impact of Change
Structural elements of a system usually carry with them a high
impact of change.
It’s usually safe to specialise, it’s usually not safe to generalise.
In all cases, we want to refactor in such a way that our changes
have no impact on anyone else.
Fellow developers, mainly.
All of this relates to the code that we produce at the end of a
systems development project.
But as a direct consequence is relates to our design.
We don’t stop designing or analysing just because a system is
‘finished’
10. +
Another Example
Let’s say we have a method to which we need to add a
parameter – say we need getValue to accept an integer
parameter:
public class Example {
private int bing;
public int getValue() {
return bing;
}
public void setValue (int b) {
bing = b;
}
}
11. +
Another Example
How do we do this? There are two real choices:
Add the parameter to the method definition.
High impact of change – every other class making use of
getValue will need to change.
Add in an overloaded method.
Low impact of change.
However, there’s a trade-off here:
Adding a parameter may require lots of code to change.
Adding an overloaded method may reduce internal
consistency.
12. +
Remit of Refactoring
Where does your remit for refactoring lie?
It depends on how much of the code for which you
are responsible.
This may extend over a whole program.
It may extend over a handful of classes.
You can unilaterally refactor only those elements of
the program for which you have responsibility.
There are few things more frustrating than
finding your programs no longer work
because of someone else’s refactoring…
13. +
A Third Example
public class Example {
private int value;
public int makeDesposit (int value, int rate) {
if (value > 100) {
return -1;
}
else if (valid < 0) {
return -1;
}
else {
if (rate < 30) {
value = value * rate;
}
else {
value = value * rate;/2
}
}
}
return value;
}
}
14. +
Code Aesthetics
The aesthetics of your code are important.
They are usually a hint at the maintainability.
However, it’s important not to just discard
complicated code as needing total refactoring.
Old code may not be ugly, it may be battle-scarred.
However, as you gain in experience and
confidence as a developer, you can generally tell
where the bits of code needing attention lie.
Everyone has their own way of doing this – you
might investigate the Wodehouse Method of
Refactoring for one interesting example.
15. +
Code Aesthetics
One way to improve the aesthetics is to break complicated
functionality out into separate methods.
This fulfils a general rule of object oriented programming, in that
each method should have one responsibility only.
Complicated and nested structures are usually a good warning
sign of the need to refactor.
Your activity diagrams will help you understand this.
Look for lots of loops, lots of branches.
If you see complicated structures, seek to simplify them.
Strive to reduce the cyclomatic complexity.
16. +
Cyclomatic Complexity
Cyclomatic complexity is a software metric, and is used to
determine how complicated a piece of code is.
It’s computed using the flow of logic through the constituent
paths of a unit.
A function, a class, or a whole system.
The higher the complexity, the more complicated the code.
The more complicated the code, the harder it is to modify.
Think of your complexity as ‘the number of decisions made in a
piece of source code’
It’s more complicated than that, but that’s good enough for now.
17. +
A Third Example
public class Example {
public int value;
private boolean getValid (int value) {
if (value > 100 || value < 0) {
return false;
}
return true;
}
private int getRate (int rate) {
int rate;
if (rate > 20 && rate < 30) {
rate = value;
}
else {
rate = value / 2;
}
return rate;
}
public int makeDeposit (int value, int rate) {
int rate;
if (getValid (value) == false) {
return -1;
}
rate = getRate (rate);
value = value * rate;
return value;
}
}
18. +
Some Common Structual
Refactoring Tasks
Generalising classes.
Specialising classes.
Improving encapsulation.
Lowering impact of change
Merging subclasses into fields
Extracting fields into subclasses
Implementing interfaces
Adding abstract classes
Making better use of polymorphism.
Reduce inconsistency.
19. +
Some Common Internal
Refactoring Tasks
Simplifying internal structures.
Improving variable names.
Simplifying logical comparisons
Substituting one algorithm for another
Consolidating conditionals
Extracting functionality into separate methods.
20. +
Refactoring and Test Driven
Development
Refactoring introduces no new functionality.
You can thus use the tests you have put in place previously.
Having a comprehensive, full test-suite ensures that your
refactored code behaves identically to the previous code.
The importance of that in a multi-developer environment cannot be
stressed enough.
We’ve referred to test driven development in the past.
We write the tests before we write the code, so that when we make
any change we can ensure the whole thing works.
Refactoring doesn’t violate this rule.
If it does, you’re not refactoring at all.
21. +
When Do We Refactor?
Refactoring is an ongoing process we do all
the time.
Yeah, right.
The ideal case is that we refactor our code
on a continual basis. The realities of life
dictate that we must prioritise.
Generally, we refactor code that is actively getting
in the way.
There’s also often a ‘wish list’ we keep as
developers of code that we regret…
22. +
When Do We Refactor?
We refactor when code ‘smells bad’
Code is duplicated across locations.
There are unjustifiable ‘god objects’
When cohesion is too low
When coupling is too high
When people have been ‘too clever’ for their own good.
http://www.soberit.hut.fi/mmantyla/BadCodeSme
llsTaxonomy.htm has a good list of ‘bad smells’
23. +
Conclusion
Refactoring is the process of looking back at old designs and
fixing them before they become problems.
Preventative maintenance, in other words.
You are going to make mistakes when you develop a system.
Nobody is perfect.
The ease with which you can fix those mistakes is going to be
based on the designs that you produce.
Think of refactoring as ‘design band aids’.
Bearing this in mind at the design stage can save much
heartache in the long run.