Tuesday, November 4, 2008

 

Some Refactoring techniques


"Code Refactoring", is no more strange words for anyone who has been developing software for some time. The necessity of code refactoring is an interesting topic to be discussed. Followings are basic and more important reasons why we should
do refactoring.

More importantly, it would improve the Design of Software. Secondly, properly refactored software code would be easier to understand and maintain. Moreover, refactoring would help to find bugs while helping to program faster. How refactoring help to speed up programming is interesting. Proper design would always speedup software programming and refactoring would help to have a better design of software.

Next important point we should understand is when we should refactor. I have read about a very interesting rule "The Rule of Three"

1. Refactor when you add Function
Refactor code to understand existing code. Then it would be easier to add new functionality to software

2. Refactor when you need to fix a bug
Refactoring code would help you to understand software while helping you finding bugs.

3. Refactor as you do a code review.
To do an effective code review you have to understand business logic behind the code. It will always be easier for you to understand the business implementation of a properly refactored code. Hence, refactor code while you review the code.

I do not intend to explain steps to be followed when using each refactoring technique. (You are welcome to explore information). Make sure you add sufficient amount of unit test before you start refactoring. Also, it should be noted that there are no hard and fast rules of refactoring. It depends on your requirement. Now, let's find out some basic and more important techniques of refactoring.
If you understand these refactoring techniqes, you will be able to apply them at the time of coding. This would make your code more readable, scalable, and maintainable.

Extract method
This is a fundamental code refactoring technique. Although, this is fundamental, if you properly follow what is explained by 'extract method', you will be able to clean most of your code. You might have noticed that there are some methods addressing more than
one business logic implementations. 'Extract method' says to introduce new methods for the code segments that could be grouped. Make sure that new method should explain the purpose of the method hence, to have better readability of the code.

Before:

void processCharge(){
// code block to get the charges to be processed
...
// code block to verify spending limit
...
// code block to process the charge and create transactions
...
}

After:

void processCharge(){
Charge[] charges = getChargesToBeProcessed();
boolean isSpendingLimitOverflowed = verifySpendingLimit();
Transaction[] transactions = processCharges();
}

Charge[] getChargesToBeProcessed() {
// code block to get the charges to be processed
...
}

boolean verifySpendingLimit() {
// code block to verify spending limit
...
}

Transaction[] processCharge(){
// code block to process the charge and create transactions
...
}


Inline method
This is opposite of 'Extract method'. If body of a method is as clear as its name then move the method body into its caller method and remove the method. Let me give an example so that you would understand it better.

Before:
void createCharge(Charge charge, Date now) {
if(isChargeApplicable(charge, now)) {
// create charge
}
}

boolean isChargeApplicable(final Charge charge, final Date now) {
return charge.getNextChargeDate().getTime() < now.getTime(); } 


After: 
void createCharge(Charge charge, Date now) { 
    if(charge.getNextChargeDate().getTime() < now.getTime()) { 
        // create charge 
    } 


Replace temp with Query
Most of the time temporary variables are used to hold result of an expression. Temporary variables cause method to be longer and difficulties in refactoring, especially when 'Extract method' is used. To avoid temp variables, extract the expression into a method with a meaningful name and replace the temp variable with method call. This technique not only helps to minimize temp variables hence, make easier to refactor code, but, facilitate other methods to use new method introduced.

Before:

double monthlySalary = wagePerDay * days;

if(monthlySalary < LOWER_SALARY_LEVEL) { 

    return monthlySalary; 
} else if (monthlySalary > LOWER_SALARY_LEVEL && monthlySalary < HIGH_SALARY_LEVEL){ 
    return monthlySalary * MODERATE_SALARY_TAX_RATE; 
} else if (monthlySalary > HIGH_SALARY_LEVEL) {
     return monthlySalary * HIGH_SALARY_TAX_RATE;
}

After:
if(getMonthlySalary () < LOWER_SALARY_LEVEL) { 

     return getMonthlySalary (); 
} else if (getMonthlySalary () > LOWER_SALARY_LEVEL && getMonthlySalary () < HIGH_SALARY_LEVEL){
     return getMonthlySalary()*MODERATE_SALARY_TAX_RATE; 
} else if (getMonthlySalary () > HIGH_SALARY_LEVEL) {
     return getMonthlySalary () * HIGH_SALARY_TAX_RATE;
}

double getMonthlySalary() {
    return wagePerDay * days;
}


Introduce explaining variable
Every software code would contain complex expressions, calculations, conditions etc. It is very important to introduce 'explaining variables' in these situations. This would improve the readability of the code while providing a more scalability.

Before:

double monthlySalary = (regularHourRate * regularHoursDay * numberOfDaysPerMonth) + (overTimeHourRate *
totalOverTimeHoursPerMonth) -
(sportsSocietyMemberShipFee + EPF + socialSecurityLevi + tax)

After:
double regularSalary = regularHourRate * regularHoursDay * numberOfDaysPerMonth;
double overTimeSalary = overTimeHourRate * totalOverTimeHoursPerMonth;
double totalDeductions = sportsSocietyMemberShipFee + EPF + socialSecurityLevi + tax;

double monthlySalary = regularSalary + overTimeSalary - totalDeductions;

Split temporary variables
As mentioned above temporary variables can be used to hold results of complex expressions. Using the same temporary variable to hold results of different expressions make the code difficult to understand and introduce bugs if, not handled properly. To make the code more understandable and minimize the risk of bugs, use different temporary variables to hold results of different expressions.

Before:

double temp = regularRatePerHour * hours; // regular wage
System.out.println("Regular wage = " + temp);

temp = overtimeRatePerHour * hours; // overtime wage
System.out.println("Overtime wage = " + temp);

After:

double regularWage = regularRatePerHour * hours; // regular wage
System.out.println("Regular wage = " + regularWage);

double overtimeWage = overtimeRatePerHour * hours; // overtime wage
System.out.println("Overtime wage = " + overTimeWage);

Remove assignment to parameters
I have seen code samples in which parameters passed to a method are used to hold result of expressions executed within the method. This would cause to create bugs which are difficult to figure out and also, reduce the readability of code. If we consider an object passed to a method, it is OK to modify the object but, assigning object parameter to a totally new object is not recommended.

Before:

double return calculatePriceWithTax(double price, double taxRate) {
price = price + price * taxRate;
return price;
}

After:
double return calculatePriceWithTax(double price, double taxRate) {
double tax = price * taxRate;
return (price + tax);
}

Replace magic numbers/Strings with symbolic constants
If you have numbers, strings with particular meanings then, replace them with constants. This would make code more understandable.

Before:

String findGrade(double marks) {
String grade;
if(marks >= 70) {
grade = "First Class";
} else if(marks >= 50 & marks < 70) { grade = "Second Class"; } else if(marks >= 25 & marks < 50) { grade = "Pass"; } else { grade = "Fail"; } return grade; } 


After: 

final static double FIRST_CLASS_LEVEL = 70; final static double SECOND_CLASS_LEVEL = 50; final static double PASS_LEVEL = 25; final static String FIRST_CLASS = "First Class"; final static String SECOND_CLASS = "Second Class"; final static String PASS = "Pass"; final static String FAIL = "Fail"; String findGrade(double marks) { String grade; if(marks >= FIRST_CLASS_LEVEL) {
grade = FIRST_CLASS;
} else if(marks >= SECOND_CLASS_LEVEL && marks < FIRST_CLASS_LEVEL) { grade = SECOND_CLASS; } else if(marks >= PASS_LEVEL && marks < SECOND_CLASS_LEVEL) { grade = PASS; } else { grade = FAIL; } return grade; }  


Encapsulate collections
Collections are heavily used in java programs. Provide add/remove methods to manipulate the collection rather than using a setter method which takes a collection as parameter. This would provide a better control over the collection class for the class which contains that collection.

Before:

class Cart{
private List itemList;
public List getList() {
return itemList;
}
public void setList(List items) {
itemList = items;
}
}

Client program is responsible for initializing item list and call the set method. This would not provide better control over the ‘itemList’ for the class Cart and causes client program to have more responsibilities.

After:


class Cart{
private List itemList = new ArrayList();
public void addToList(Item item) {
itemList.add(item);
}
public void remoteFromList(List items) {
itemList.remove(item);
}
public List getItemList(){
return itemList;
}
}

There are a lot of refactoring techniques which should be discussed and put in practice. If you follow above techniques at least, you would be able to resolve immediate issues exist in your code.

Reference : Improving the Design of Existing Code
by Martin Fowler, Kent Beck (Contributor), John Brant (Contributor), William
Opdyke, don Roberts

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]