Date post: | 17-Dec-2015 |
Category: |
Documents |
Upload: | camron-lambert |
View: | 244 times |
Download: | 12 times |
Lectures 17 and 18 A Refactoring Micro-Example
FOR0383 Software Quality Assurance
04/18/23 1Dr Andy Brooks
Refactoring is really easy using this tool.
18/04/23 Dr Andy Brooks 2
Case StudyDæmisaga
ReferenceA Refactoring Micro-Example, David Parsons,http://www.dparsons.co.uk/Refactoring.html
18/04/23 Dr Andy Brooks 3
What is refactoring?
• There is a dedicated website: www.refactoring.com
“Refactoring is the process of improving the design of existing code without changing its observable behaviour.”
“It is an ongoing process of continual cleanup.”Dave Parsons
18/04/23 Dr Andy Brooks 4
Replace smelly code• Signs of decay include:
– Duplicated code– Switch statements– Long methods– Large classes– Data classes (only getters and setters in the API)– Long parameter lists– Use of primitives rather than objects– Temporary variables and fields
When you write smelly code you are hacking...
18/04/23 Dr Andy Brooks 5
Test the changes
• Every refactoring change should be tested.
• Recompile and test after every change.
“If we want to refactor, the essential precondition is having solid tests.”
Martin Fowler
18/04/23 Dr Andy Brooks 6
Fowler´s ‘Rule of Three’
• The first time you write something you just do it.• The second time, you wince at the duplication.• The third time, you refactor.
‘Two hats rule’• Do not try to develop code at the same time as
you refactor code. Refactor while you wear your refactoring hat: do not develop.
RD
18/04/23 Dr Andy Brooks 7
The micro example• A method of some dice game class that throws a
couple of dice and returns a result. • ‘dice’ is an array of ‘Die’.
public int getScore(){
int result;result = (int)(Math.random() * 6) + 1;dice[0].setFaceValue(result);result = (int)(Math.random() * 6) + 1;dice[1].setFaceValue(result);int score = dice[0].getFaceValue() + dice[1].getFaceValue();return score;
}
0<=x<11<=x<7(int) truncates fraction
18/04/23 Dr Andy Brooks 8
Refactoring 1 – Self Encapsulate Field
• Use accessor methods, do not directly access an object´s fields within its methods.
public int getScore(){
int result;result = (int)(Math.random() * 6) + 1;getDice(0).setFaceValue(result);result = (int)(Math.random() * 6) + 1;getDice(1).setFaceValue(result);int score =getDice(0).getFaceValue()
+getDice(1).getFaceValue(); return score;
}
The underlying representation of a die can be changed.
18/04/23 Dr Andy Brooks 9
Refactoring 2 – Extract Method• Split up a longer method into smaller methods.public int getScore(){
int result;result = rollDie();getDice(0).setFaceValue(result);result = rollDie();getDice(1).setFaceValue(result);int score = getDice(0).getFaceValue() + getDice(1).getFaceValue();return score;
} public int rollDie(){
return (int)(Math.random() * 6) + 1;}
18/04/23 Dr Andy Brooks 10
Refactoring 3 – Rename class/method/field
• Change names to be more meaningful.
public int throwDice(){
int result;result = rollDie();getDice(0).setFaceValue(result);result = rollDie();getDice(1).setFaceValue(result);int score = getDice(0).getFaceValue() + getDice(1).getFaceValue();return score;
} public int rollDie(){
return (int)(Math.random() * 6) + 1;}
18/04/23 Dr Andy Brooks 11
Refactoring 4 – Replace Temp with Query• Use a query method instead of a temporary variable.
public int throwDice(){ int result;
result = rollDie();getDice(0).setFaceValue(result);result = rollDie();getDice(1).setFaceValue(result);return getDiceValue();}
public int rollDie(){ return (int)(Math.random() * 6) + 1;} int getDiceValue(){ return getDice(0).getFaceValue() +
getDice(1).getFaceValue();}
18/04/23 Dr Andy Brooks 12
Refactoring 5 – Move Method• Dice objects are data objects.• It would be better to move the rollDie() method to
the Dice class and have this method set the state of the object.
• The rollDie() method can also be renamed to roll().
public void roll(){
setFaceValue((int)(Math.random() * 6) + 1);}
18/04/23 Dr Andy Brooks 13
Refactoring 5 – Move Methodpublic int throwDice(){
getDice(0).roll();getDice(1).roll();return getDiceValue();
} int getDiceValue(){
return getDice(0).getFaceValue() +getDice(1).getFaceValue();
}
The code is beginning to look much cleaner.
18/04/23 Dr Andy Brooks 14
Refactor 6 – Replace conditional with polymorphism
• Switch statements that depend on the type of an object should be replaced with class hierarchies and polymorphic methods.
• Suppose the Die class had code to model two kinds of dice: NORMAL or LOADED.
static final int NORMAL = 1;static final int LOADED = 2;private int type;
18/04/23 Dr Andy Brooks 15
Refactor 6 – Replace conditional with polymorphism
public void roll(){
switch(getType()){
case NORMAL:setFaceValue((int)(Math.random() * 6) + 1);break;
case LOADED:// random is a static java.util.Random object – easier// to fix a range of integers (2 – 6) than Math.randomsetFaceValue(random.nextInt(5) + 2);break;
default:setFaceValue(1);
}}
The roll() method now has a switch.
inclusive of 0exclusive of 5
18/04/23 Dr Andy Brooks 16
Refactor 6 – Replace conditional with polymorphism
Die
Normal Loaded
//Normal Die implementationpublic void roll(){
setFaceValue((int)(Math.random() * 6) + 1);} //Loaded Die implementationpublic void roll(){
setFaceValue(random.nextInt(5) + 2);}
abstract superclass
18/04/23 Dr Andy Brooks 17
Refactor 7 – Replace Magic Number with Symbolic Constant
public static final int MAX_RANDOM = 6;public static final int MIN_DICE_VALUE = 2;
public void roll(){ setFaceValue(random.nextInt(MAX_RANDOM - 1) + MIN_DICE_VALUE);}
6 rather than 5 because we want to express the maximum value of a die.
18/04/23 Dr Andy Brooks 18
Replace indexed access with iteration?
• The real question is: do we need indexed access if we just have two die objects?
• The answer is no. We will ‘never’ need to be able to use a different number of dice. The use of an array or collection provides too much functionality. We just need two named die objects:
public int throwDice() {
getDice(0).roll();getDice(1).roll();return getDiceValue();
}
indexed access
getFirstDie().roll();getSecondDie().roll();
18/04/23 Dr Andy Brooks 19
Replace polymorphism with parameterised algorithm
• Do we really need to use inheritance and have a polymorphic roll() method?
• No. We could make use of a parameterised method. If you have two algorithms that do something similar, consider if they can be combined into a single algorithm in a parameterised method.
int getRandomInt(int min, int max);
18/04/23 Dr Andy Brooks 20
Knowing that we will only ever have two die and recognising that a parameterised method can be used to model a normal and loaded die ... completely changes the look of the code...
18/04/23 Dr Andy Brooks 21
Reflections
• Were the refactorings carried out in the right order?
• Can refactorings be prioritised?
• Should refactoring first consider core architectural issues?
• Should we start with the simplest refactorings?
18/04/23 Dr Andy Brooks 22
Reflections
• Refactorings are often reversible. ‘Replace Inheritance with Delegation’ and ‘Replace Delegation with Inheritance’. We may do a refactoring only to undo it.
• A tool providing a direct link between software metric scores and suggested refactorings would be very useful.
18/04/23 Dr Andy Brooks 23
Some refactoring examples explained with the help of UML.
http://www.uml.org
18/04/23 Dr Andy Brooks 24
Pull Up Field
• Eliminating duplicate fields can help reduce defects and allows methods that use the fields to be moved to the superclass.
18/04/23 Dr Andy Brooks 25
Push Down Field
• Push a field down if it is not needed in the superclass.
18/04/23 Dr Andy Brooks 26
Pull Up Method
• Eliminating duplicate methods can help reduce defects.
18/04/23 Dr Andy Brooks 27
Push Down Method
• Methods should be pushed down if the they are not used by all the subclasses.
18/04/23 Dr Andy Brooks 28
Replace inheritance with delegation
class Sub extends Super {...}
• Suppose an object of Sub never uses the methods ying() and yang().
• Class Sub should then not inherit from class Super.
18/04/23 Dr Andy Brooks 29
Replace inheritance with delegation
class Sub { Super s = new Super(); int foo() { return s.foo(); } // delegation void bar() { s.bar(); } // delegation int baz() {...} // new method }
18/04/23 Dr Andy Brooks 30
Replace delegation with inheritance
class Sub { Super s = new Super(); int foo() { return s.foo(); } // delegation void bar() { s.bar(); } // delegation char ying() {return s.ying();} // delegation char yang() {return s.yang();} // delegation int baz() {...} // new method }
18/04/23 Dr Andy Brooks 31
Replace delegation with inheritance
• Suppose an object of Sub uses all of the methods foo(), bar(), ying() and yang().
• Class Sub should then inherit from class Super.
class Sub extends Super {...}
32
Hide Delegate
• Hide a delegate class (Department) by creating a method on the server (Person).
manager = andy.getDepartment().getManager();manager = andy.getManager();
33
Remove Middle Man
• The server class (Person) is delegating too much, so have the client call the delegate class (Department) directly.
manager = andy.getManager();manager = andy.getDepartment().getManager();
18/04/23 Dr Andy Brooks 34
Hide Delegate and Remove Middle Man:
Which refactoring is appropriate depends on the number of delegating methods involved (such as getManager()).
If there are a lot of delegating methods then the client should access the delegated class directly.
18/04/23 Dr Andy Brooks 35
Hide Method
• If a method is not used by any other class make the visibility private.
18/04/23 Dr Andy Brooks 36
Do no forget the Two Hats Rule.Refactor code in the context of a testing framework.Each change has to be tested.
Do not forget many refactorings come in pairs.A refactoring may be reversed at a later date.
The part played by data members can influence a refactoring decision.
There are many more recognised refactorings.