Upload
doris-watkins
View
219
Download
3
Embed Size (px)
Citation preview
Chapter 6: Code Refactoring
Omar Meqdadi
SE 3860 Lecture 6
Department of Computer Science and Software Engineering University of Wisconsin-Platteville
2
Topic Covered
Refactoring Refactoring Categories Refactoring Limitations Refactoring Tools
3
Refactoring
Process of changing a software system in such a way that it Does not alter the external behavior of the code Improves its internal structure
A program restructuring operation to support Design evolution Reuse of object oriented frameworks Preserve the behavioral aspects of the program
Refactoring Specifies
Source to source transformation Remain inside the same language, e.g., C++ to C++ Does not change the programs behavior – according to a
test suite Originally designed for object-oriented languages, but
can also be applied to non-object oriented language features, (i.e., functions)
Refactoring: Why?
Design Preservation Design Evolution Problems
Code changes often lead to a loss of the original design Loss of design is cumulative:
Difficulties in design comprehension Difficulties in preserving design More rapid decay of design
Solution: Refactoring improves the design of existing code
Refactoring: Why?
Comprehension Developer's are most concerned with getting the program
to work, not about future developers Refactoring makes existing code more readable Increases comprehension of existing code, leading higher
levels of code comprehension Often applied in stages
Refactoring: Why?
Debugging Improved program comprehension leads to easier
debugging Increased readability leads to the discovery of possible
errors Understanding gained during debugging can be put back
into the code
Refactoring: Why?
Faster Programming Counterintuitive argument made by Fowler Good design is essential for rapid development Poor design allows for quick progress, but soon slows the
process down Spend time debugging Changes take longer as you understand the system and find
duplicate code
Refactoring: When?
Adding Functionality Comprehension of existing program Preparation for addition
Debugging Comprehension of existing program
Code Review Preparation for suggestions to other programmers Stimulates other ideas Remove bad code smells
Refactoring Catalog
Refactoring entry composed of: Name Summary Motivation Mechanics Examples
Refactoring: Categories
Composing Methods Creating methods out of inlined code
Moving Features Between Objects Changing of decisions regarding where to put
responsibilities Organizing Data
Make working with data easier
Refactoring: Categories
Simplifying Conditional Expressions Making Method Calls Simpler
Creating more straightforward interfaces Dealing with Generalization
Moving methods around within hierarchies Big Refactoring
Refactoring for larger purposes
Composing Methods
Extract Method Inline Method Inline Temp Replace Temp with Query Introduce Explaining Variables Split Temporary Variable Remove Assignments to Parameters Replace Method with Method Object Substitute Algorithm
Remove Assignments to Parameters
Example: Remove Assignments to Parameters
Extract Methods
Create a new method, and name it after the intention of the method (name it by what it does, not by how it does it)
Copy the extracted code from the source method into the new target method
Scan the extracted code for references to any variables that are local in scope to the source method
See whether any temporary variables are used only within this extracted code. If so, declare them in the target method as temporary variables
Extract Methods (cont.)
Look to see whether any local-scope variable are modified by the existing code
Pass into the target method as parameters local scope variables that are read from the extracted code
Compile when you have dealt with all the locally- scoped variables
Replace the extracted code in the source method with a call to the target method
Compile and test
Extract Methods
void compAndPrint() { // compute X // compute Y ... // print X // print Y ... // store X // store Y}
void compute () { // compute X // compute Y print(X, Y); // store X // store Y}
void print(X,Y) { // print X // print Y}
Inline Methods
void compute () { X = next(X); Y = next(Y);}
int next(N) { return N++;}
void compAndPrint() { ... X++; Y++; ...}
Inline Temp
Example:
double basePrice = anOrder.basePrice();return (basePrice > 1000) return (anOrder.basePrice > 1000)
Replace Temp with Query
When: using a temporary variable to hold the result of an expression.
double rectarea = length * width; if (rectarea > 1000) return rectarea * 0.95; else return rectarea * 0.98;
If ( RectArea( ) > 1000 ) return RectArea( ) * 0.95;else return RectArea( ) * 0.98;}
double RectArea( ){ return length * width;}
Split Temp Variable
When: using a temporary variable to hold several expressions.
double temp = 2 * (_height * _widgth);cout << “Perimeter is “ << temp << endl;temp = _height * _width; cout << “Area is “ << temp << endl;
double perimeter = 2 * (_height * _widgth);cout << “Perimeter is “ << perimeter << endl;double area = _height * _width; Cout << “Area is “ << area << endl;
Introduce Explaining Variable
When: You have a complicated expression. Then, Put the result of the expression, or parts of the expression, in a temporary variable with a name that explains the purpose.
// return = baseprice – quantitydiscount + shippingreturn (quantity*price)-((quantity-50)*discount) +price * 0.05
double baseprice = quantity * price;double quantitydiscount = (quantity-50)*discount;double shipping =price * 0.05;return baseprice – quantitydiscount + shipping;
Moving Object Features
Move Method Move Field Extract Class Inline Class Hide Delegate Remove Middle Man Introduce Foreign Method Introduce Local Extension
Move Method
When: if a method is used by another class more than the class on which its defined, move it to the other class
class Student { bool IsEnroll(Course course) { return (course.getStudents().contains(this)); } }
class Course { Student students[size]; Student * getStudents() { return students; } }
class Student { }
public class Course { Student students[size]; bool isEnroll(Student student) { return students.contains(student); } }
Extract Class
class Student{ private: string name; string street; string city; string state; }
class Student{ private: string name; Address address; }
class Address{ private: string street; string city; string state; }
Inline Class
When: a class that isn’t doing too much and shouldn’t be around any more. Then, move all its features to another class. So inline class is the reverse of Extract Class
Hide Delegate
When: a client calls a delegate class of an object Refactoring: create methods on the server to hide the delegate
Client must know about Person and Department
Person must know about department
Client only calls Person
Person calls department
Hide Delegate Class Person {
Department _department;
public Department getDepartment()
{ return _department;}
public void setDepartment (Department arg)
{ _department = arg;}
} // end person
Class Department {
private String _chargeCode;
private Person _manager;
public Department (Person manager)
{ _manager = manager; }
public Person getManager()
{ return _manager; }// end Dept
Client Class:
joe.getDepartment().getManager();
Person Class:
public Person getManager () {
return _department.getManager();
} // end getManager
Client Class:
manager = joe.getManager();
Remove Middle Man
When: after several “Hide delegate”, the server becomes just a middle man for all the delegate methods.
Refactoring: Change the client to call the delegate directly
Client only calls Person
Person calls department
Client must know about Person and Department
Person must know about department
Introduce Foreign Method
When: a server class needs an additional methods, but you can’t modify it
Refactoring: Create the method in the client class Set one of the argument of the method to the instance of
the server class StrDate newStart = new Date
(previousEnd.getYear(),
previousEnd.getMonth(),
previousEnd.getDate()+ 1);
Date newStart = nextDat(previousEnd);
private static Date nextDay (Date arg)
{
return new Date (arg.getYear(),arg.getMonth(),
arg.getDate() + 1);
} // end nextDay
Introduce Local Extension
When: a class needs several additional methods, but you can’t modify it ( such as server class)
Refactoring: Create a new class that contains these additional methods Make the new class a subclass of the original one
(inheritance) or use wrapper approach (delegation)
Organizing Data
Self Encapsulate Field Replace Data Value with Object Change Value to Reference Change Reference to Value Replace Array with Object Duplicate Observed Data Change Unidirectional Association to Bidirectional Change Bidirectional Association to Unidirectional
Organizing Data (cont.)
Replace Magic Number with Symbolic Constant Encapsulate Field Encapsulate Collection Replace Record with Data Class Replace Type Code with Class Replace Type Code with Subclasses Replace Type Code with State/Strategy Replace Subclass with Fields
Change Reference to Value
void swap ( int * x , int * y){ int temp = *x; *x = *y; *y = temp;}void main (){ int a = 5; int b = 7; swap (&a , &b);}
void swap ( int &x , int &y){ int temp = x; x = y; y = temp;}void main (){ int a = 5; int b = 7; swap (a , b);}
Replace Array with Object
String[] row = new String[3];row[0] = “Platteville”;row[1] = “15”;
Performance row = new Performance();row.setName(“Platteville”);row.setWins(“15”);
Encapsulate Field
Replace Magic Number with Constant
Example:
double Energy (double m, double h) {
return m * 9.81 * h;
}
static Final double GRAVITATIONAL_CONSTANT * 9.81;
double Energy (double m, double h) {
return m * GRAVITATIONAL_CONSTANT * h;
}
Simplifying Conditional
Decompose Conditional Consolidate Conditional Expression Consolidate Duplicate Conditional Fragments Remove Control Flag Replace Nested Conditional with Guard Clauses Replace Conditional with Polymorphism Introduce Null Object Introduce Assertion
Decompose Conditional
Example: Decompose Conditional
Consolidate Conditional
double disabilityAmount() { if (_seniority < 2) return 0; if (_monthsDisabled > 12) return 0; if (_isPartTime) return 0; // do smoething else}
double disabilityAmount() {{ if (isNotEligableForDisability()) return 0; // do something else}
bool isNotEligableForDisability(){ if (_seniority < 2 || _monthsDisabled > 12 || _isPartTime() ) return false; else return true;}
Consolidate Duplicate Conditional Fragments
if (isSpecial()) { total = price * 0.95; send(); }else{ total = price * 0.98; send();}
if (isSpecial()) { total = price * 0.95;}else{ total = price * 0.98;}
Send();
Introduce Null Objects
When: for null or default behavior, use inheritance
class Animal { Voice MakeSound() { return voice; } }
Animal obj = new Animal;if (obj == null) voice = Voice.basic();else voice = obj.MakeSound();
class Animal{ Voice MakeSound() { return voice; }}
class NullAnimal : public Animal{ Voice MakeSound() { return Voice.basic(); }}
Replace Nested Conditional with Guard
When: A method has unclear conditional behavior. Then , use Guard Clauses for all the special cases
double getAmount() { double result; if (isDead ()) result = deadAmount(); else { if (isSeparated( ) ) result = separatedAmount(); else { if (isRetired ( ) ) result = retiredAmount(); else result = normalPayAmount(); } } return result;}
double getAmount() { if (isDead ( ) ) return deadAmount(); if (isSeparated ( ) ) return separatedAmount(); if (isRetired ( ) ) return retiredAmount(); return normalPayAmount();}
Replace Conditional with Polymorphism
class Bird {……double getSpeed() { switch (_type) { case EUROPEAN: return getBaseX(); case AFRICAN: return getBaseX() – _number; }}…..
Introduce Assertion
When: the code assumes something. Then, make the assumption explicit with an assertion.
double getExpenseLimit() {
// should have either expense limit or // a primary // project
return (_expenseLimit != NULL_EXPENSE) ?
_expenseLimit:
_primaryProject.getMemberExpenseLimit();
}
double getExpenseLimit() {
Assert.isTrue (_expenseLimit != NULL_EXPENSE || _primaryProject != null);
return (_expenseLimit != NULL_EXPENSE) ?
_expenseLimit:
_primaryProject.getMemberExpenseLimit();
}
Remove Control Flag
When: remove a control flag and use break or return void checkSecurity( ) {
bool found = false;
for (int i = 0; i <length; i++) {
if (! found) {
if(people[i].equals("Don")){
sendAlert();
found = true; }
if(people[i].equals ("John")){
sendAlert();
found = true; }
}
}
}
void checkSecurity( ) {
for (int i = 0; i <length; i++) {
if(people[i].equals("Don")){
sendAlert();
break; }
if(people[i].equals ("John")){
sendAlert();
break; }
}
}
Simplifying Method Calls
Rename Method Add Parameter Remove Parameter Separate Query from Modifier Parameterize Method Replace Parameter with Explicit Methods Preserve Whole Object Replace Parameter with Method
Simplifying Method Calls(cont.)
Introduce Parameter Object Remove Setting Method Hide Method Replace Constructor with Factory Method Encapsulate Downcast Replace Error Code with Exception Replace Exception with Test
Remove Setting Method
When: A field should be set at creation time and never altered. Then, remove any setting method for that field.
class Account {
private String _id;
Account (String id)
{ setId(id); }
void setId (String arg)
{ _id = arg; }
}
class Account {
private String _id;
Account (String id)
{ _id = arg; }
}
Hide Method
When: A method is not used by any other class
Refactoring: Make the method private
Parameterize Method
When: Several methods do similar things but with different values contained in the method body. Then, Create one method that uses a parameter for the different values.
class Employee {
void tenPercentRaise () {
salary *= 1.1; }
void fivePercentRaise () {
salary *= 1.05;}
}
class Employee {
void Raise (double factor) {
salary *= (1+ factor); }
}
Replace Parameter with Explicit Method
When: a method that runs different cases depending on the values of a parameter. Then, create a separate method for each value of the parameter
void setValue (string name, int value) { if (name.equals("height")) height = value; if (name.equals("width")) width = value; }
void setHeight(int value) { height = value;}
void setWidth (int value) { width = value;}
Introduce Parameter Object
When: a group of parameters that naturally go together. Then , replace them with an object
Preserve Whole Object
When: You are getting several values from an object and passing these values as parameters in a method call. Then, send the whole object instead.
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
withinPlan = plan.withinRange(low, high);
withinPlan = plan.withinRange(daysTempRange);
Replace Error Code with Exception
When: A method returns something to indicate an error is better performed with an Exception
bool withdraw(int amount) { if (amount > balance) return false; else { balance -= amount; return true; }}
void withdraw(int amount) throws BalanceException { if (amount > balance) { throw new BalanceException(); } balance -= amount;}
Replace Exception with Test
When: if you are catching an exception that could be handled by an if-statement, use that instead
int getValuebyIndex (int index) { try { return values[index]; } catch (ArrayIndexOutOfBoundsException e) { return 0; }}
int getValuebyIndex (int index) { if (index >= values.length) return 0; return values[index]; }
Replace Constructor with Factory Method
Factory Class: A utility class that creates an instance of a class from a
family of derived classes Useful in a situation that requires the creation of many
different types of objects, all derived from a common base type
Replace Constructor with Factory Method
Class Computer
{
………
};
class Laptop: public Computer
{
………
};
class Desktop: public Computer
{
......
};
class ComputerFactory {
public:
static Computer *NewComputer(const std::string &description)
{
if(description == "laptop")
return new Laptop;
if(description == "desktop")
return new Desktop;
return NULL;
}
};
Client Side:
Computer * lapobj = ComputerFactory::NewComputer(“laptop”);
Computer * deskobj = ComputerFactory::NewComputer(“desktop”);
Dealing with Generalization
Pull Up Field Pull Up Method Pull Up Constructor Body Push Down Method Push Down Field Extract Subclass Extract Superclass Extract Interface
Dealing with Generalization
Collapse Hierarchy Form Template Method Replace Inheritance with Delegation Replace Delegation with Inheritance
Method/Field Movement
Pull Up Method/Field When: Two subclasses have the same method/field Refactoring: Move the method/field to the superclass
Push Down Method/Field When: A method/field is only used in subclass Refactoring: move the method/field to the subclass
Method/Field Movement
Example: Push Down Method
Pull Up Constructor Body
When: constructors on subclasses with mostly identical bodies.
Refactoring: Create a superclass constructor; call this from the subclass
methods
Pull Up Constructor Body
class Employee{
private:
string name;
string id;
}
class Manager : public Employee{
private:
int grade;
public:
Manager (string n, string i, int g) {
name = n;
id = i;
grade = g;
}
}
class Employee{
private:
string name;
string id;
public:
Employee(string n , string I ){
name = n;
id = I;
}
}
class Manager : public Employee{
private:
int grade;
public:
Manager (string n, string i, int g) {
Employee (n, i);
grade = g;
}
}
Extract Subclass
When: A class has features that are used only in some instances. Then, create a subclass for that subset of features
Extract Subclass
class JobItem ...
public JobItem (int unitPrice, int quantity,
boolean isLabor, Employee employee) {
_unitPrice = unitPrice;
_quantity = quantity;
_isLabor = isLabor;
_employee = employee; }
public int getTotalPrice() {
return getUnitPrice() * _quantity;
}
public int getUnitPrice(){
return (_isLabor) ? _employee.getRate() : _unitPrice;
}
public int getQuantity(){
return _quantity;
}
public Employee getEmployee() {
return _employee;
}
private int _unitPrice;
private int _quantity;
private Employee _employee;
private boolean _isLabor;
class Employee...
public Employee (int rate) {
_rate = rate;
}
public int getRate() {
return _rate;
}
private int _rate;
class JobItem...
protected JobItem (int unitPrice, int quantity,
boolean isLabor) {
_unitPrice = unitPrice;
_quantity = quantity;
protected boolean isLabor() {
return false;
}
public int getUnitPrice(){
return _unitPrice;
}
class LaborItem extends JobItem
public LaborItem (int quantity, Employee employee) {
super (0, quantity, true);
_employee = employee;
}
protected boolean isLabor() {
return true;
}
public int getUnitPrice(){
return _employee.getRate();
}
Extract Superclass
When: several classes with similar features
class Employee { private: string name; string jobTitle; public: string GetName();}
class Student { private: string name; Course course;public: string GetName(); }
class Person { private: string name; public: string GetName();}
class Employee : public Person { private: string jobTitle; }
class Student : public Person { private: Course course; }
Extract Interface
When: several classes have part of their interface in common
Interface: is an empty shell, there are only the signatures (name / params / return type) of the methods. The methods do not contain anything. The interface can't do anything. It's just a pattern.
Extract Interface
double charge(Employee emp, int days) { int base = emp.getRate() * days; if (emp.hasSpecialSkill()) return base * 1.05; else return base;}
interface Billable { public int getRate(); public boolean hasSpecialSkill();}
class Employee implements Billable ...
double charge(Billable emp, int days) { int base = emp.getRate() * days; if (emp.hasSpecialSkill()) return base * 1.05; else return base;}
Collapse Hierarchy
When: A superclass and subclass are not very different
Refactoring Solution: Merge them together
Replace Inheritance with Delegation
When: A subclass uses only part of a superclasses interface or
does not want to inherit data. Refactoring Solution:
Create a field for the superclass, change methods to delegate to the superclass, and remove the subclassing
Replace Inheritance with Delegation
Example:
class MyStack extends Vector {
public void push(Object element) {
insertElementAt(element,0);
}
public Object pop() {
Object result = firstElement();
removeElementAt(0);
return result;
}
class MyStack {
private Vector _vector = new Vector();
public boolean isEmpty() {
return _vector.isEmpty();
}
public void push(Object element) {
_vector.insertElementAt(element,0);
}
public Object pop() {
Object result = _vector.firstElement();
_vector.removeElementAt(0);
return result;
}}
Big Refactoring
Tease Apart Inheritance Split an inheritance hierarchy that is doing two jobs at once
Convert Procedural Design to Objects Separate Domain from Presentation
GUI classes that contain domain logic Extract Hierarchy
Create a hierarchy of classes from a single class where the single class contains many conditional statements
Convert Procedural Design
Take each record type and turn it into a dumb data object with accessors
Take all procedural code and put it into a single class Take each long method and apply Extract Method and the
related factorings to break it down. As you break down the procedures use Move Method to move each one to the appropriate dumb data class
Continue until all behavior is removed from the original class
Convert Procedural Design
Example:
Refactoring: Limitations
Tentative list due to lack of experience Database
Database schema must be isolated, or schema evolution must be allowed
Changing Published Interfaces Interfaces where you do not control all of the source code that
uses the interface Must support both old and new interfaces Don't publish interfaces unless you have to
Refactoring: Tools
Smalltalk Refactoring Browser Development environment written in Smalltalk Allows for Smalltalk source code to transform Smalltalk source
code Comments as a first-class part of the language
XRefactory Allows standard refactorings for C++