Foundations of Software Engineering

Kenneth M. Anderson <kena@cs.colorado.edu>

Lecture 25: Refactoring, Part 1

Credit where Credit is Due

Review: What is Refactoring?

Refactoring Considered Harmful?

Getting It Right the Second Time

The Refactoring Process

Making It Safe

Why adopt refactoring?

When should you refactor?

Problems with Refactoring

Bad Smells

Examples

Example Refactorings

Extract Method

Example (I)

void printBill(Customer c, double amount) {
    printBanner();
    String name = c.getName();
    //print details
    System.out.println("name: " + name);
    System.out.println("amount: " + amount);
}

Example (II)

void printBill(Customer c, double amount) {
    printBanner();
    printDetails(c.getName(), amount);
}

void printDetails(String name, double amount) {
    System.out.println("name: " + name);
    System.out.println("amount: " + amount);
}

Note: comment deleted, temp variable removed

Replace Temp with Query

Example (I)

…
double basePrice = _quantity * _itemPrice;
if (basePrice > 1000) {
    return basePrice * 0.95;
} else {
    return basePrice * 0.98;
}
…

Example (II)

double basePrice() {
    return _quantity * _itemPrice;
}
…
if (basePrice() > 1000) {
    return basePrice() * 0.95;
} else {
    return basePrice() * 0.98;
}
…

Note: a lot of developers dislike this refactoring. Why?

Move Method

Example (I)

Example (II): Original Code

class Account {
    …
    double overdraftCharge() {
        if (_type.isPremium()) {
            double result = 10;
            if (_daysOverdrawn > 7 ) {
                result += (_daysOverdrawn - 7) * 0.85;
            }
            return result;
        } else {
            return _daysOverdrawn * 1.75;
        }
    }

    double bankCharge() {
        double result = 4.5;
        if (_daysOverdrawn > 0) {
            result += overdraftCharge();
            return result;
        }
    }

    private AccountType _type;
    private int _daysOverdrawn;
}

Example (III): Move method to AccountType

class AccountType {
    …
    double overdraftCharge(int daysOverdrawn) {
        if (isPremium()) {
            double result = 10;
            if (daysOverdrawn > 7 ) {
                result += (daysOverdrawn - 7) * 0.85;
            }
            return result;
        } else {
            return daysOverdrawn * 1.75;
        }
    }
    …
}

Note: daysOverdrawn lives in Account, so it becomes a parameter;
isPremium used to be accessed via a variable of type AccountType;
now the method lives in that class and can access the method directly

Example (III): Clean up Account Class

class Account {
    …
    double overdraftCharge() {
        return _type.overdraftCharge(_daysOverdrawn);
    }
    …
}

Note: here the old method delegates to the new method in AccountType

Example (IV): Clean up Account Class, Take Two

class Account {
    …
    double bankCharge() {
        double result = 4.5;
        if (_daysOverdrawn > 0) {
            result += _type.overdraftCharge(_daysOverdrawn);
            return result;
    }
    …
}

Note: here the old method is deleted;
bankCharge() is then updated to access overdraftCharge() in its new location

Replace Conditional with Polymorphism

Example (I)

…
double getSpeed() {
    switch (_type) {
        case EUROPEAN:
            return getBaseSpeed();
        case AFRICAN:
            return getBaseSpeed() - getLoadFactor() * _numberOfCoconuts;
        case NORWEGIAN_BLUE:
            return (_isNailed) ? 0 : getBaseSpeed(_voltage);
    }
    throw new RuntimeException(“Unreachable”);
}
…

Example (II)

Birds

This refactoring eliminates the need for the conditional by creating the above class structure

Example (III)

public abstract class Bird {
    …
    double getSpeed();
    …
}

class europeanBird extends Bird {
    …
    double getSpeed() {
        return getBaseSpeed();
    }
    …
}

class africanBird extends Bird {
    …
    double getSpeed() {
        return getBaseSpeed() - getLoadFactor() * _numberOfCoconuts;
    }
    …
}

class norwegianBlueBird extends Bird {
    …
    double getSpeed() {
        return (_isNailed) ? 0 : getBaseSpeed(_voltage);
    }
    …
}

Example (IV)

Note: by splitting the branches of the conditional into the appropriate subclasses, the conditional goes away. And now, polymorphism can be used to get the correct speed, when needed:

void printSpeed(Bird[] birds) {
    for (int i=0; i < birds.length; i++) {
        System.out.println("" + birds[i].getSpeed());
    }
}

Introduce Null Object

Example (I)

Create an extension of the relevant class that represents the “null value” for that class. Make that class a singleton and initialize it with appropriate values

NullObject

The NullCustomer class would be implemented as a singleton and would contain code that looks like this:

public class NullCustomer extends Customer {
    …
    public void getName() {
        return "Will U. Leavemealone";
    }
    …
}

Example (II)

The findCustomer() method shown previously would be modified to return an instance of NullCustomer whenever it previously would have returned null

public Customer findCustomer(Map params) {
    Customer result;
    …
    if (result == null) {
        result = NullCustomer.instance();
    }
    return result;
}

Example (III)

Summary

Coming Up Next