Refactoring is a core technique for improving code. The canonical reference for refactoring is Martin Fowler’s book Refactoring: Improving the Design of Existing Code (Addison-Wesley, 1999). I refer you to that book for more information about the kind of refactoring you can do when you have tests in place in code.
In this chapter, I describe one key refactoring: Extract Method. It should give you a flavor of the mechanics involved in refactoring with tests.
Of all refactorings, Extract Method is perhaps the most useful. The idea behind Extract Method is that we can systematically break up large existing methods into smaller ones. When we do this, we make our code easier to understand. In addition, we can often reuse the pieces and avoid duplicating logic in other areas of our system.
When you want to extract a method, the first thing that you need is a set of tests. If you have tests that thoroughly exercise a method, you can extract methods from it using these steps:
1. Identify the code you want to extract, and comment it out.
2. Think of a name for the new method and create it as an empty method.
3. Place a call to the new method in the old method.
4. Copy the code that you want to extract into the new method
5. Lean On the Compiler (315) to find out what parameters you’ll have to pass and what values you’ll have to return.
6. Adjust the method declaration to accommodate the parameters and return value (if any).
7. Run your tests.
8. Delete the commented-out code.
Here is a simple example in Java:
public class Reservation
{
public int calculateHandlingFee(int amount) {
int result = 0;
if (amount < 100) {
result += getBaseFee(amount);
}
else {
result += (amount * PREMIUM_RATE_ADJ) + SURCHARGE;
}
return result;
}
...
}
The logic in the else-statement calculates the handling fee for premium reservations. We need to use that logic someplace else in our system. Instead of duplicating the code, we can extract it from here and then use it in the other place.
Here is the first step:
public class Reservation
{
public int calculateHandlingFee(int amount) {
int result = 0;
if (amount < 100) {
result += getBaseFee(amount);
}
else {
// result += (amount * PREMIUM_RATE_ADJ) + SURCHARGE;
}
return result;
}
...
}
We want to call the new method getPremiumFee
, so we add the new method and its call:
public class Reservation
{
public int calculateHandlingFee(int amount) {
int result = 0;
if (amount < 100) {
result += getBaseFee(amount);
}
else {
// result += (amount * PREMIUM_RATE_ADJ) + SURCHARGE;
result += getPremiumFee();
}
return result;
}
int getPremiumFee() {
}
...
}
Next we copy the old code into the new method and see if it compiles:
public class Reservation
{
public int calculateHandlingFee(int amount) {
int result = 0;
if (amount < 100) {
result += getBaseFee(amount);
}
else {
// result += (amount * PREMIUM_RATE_ADJ) + SURCHARGE;
result += getPremiumFee();
}
return result;
}
int getPremiumFee() {
result += (amount * PREMIUM_RATE_ADJ) + SURCHARGE;
}
...
}
It doesn’t. The code uses variables named result
and amount
that aren’t declared. Because we are computing only a portion of the result, we can just return what we compute. We can also get hold of the amount if we make it a parameter to the method and add it to the call:
public class Reservation
{
public int calculateHandlingFee(int amount) {
int result = 0;
if (amount < 100) {
result += getBaseFee(amount);
}
else {
// result += (amount * PREMIUM_RATE_ADJ) + SURCHARGE;
result += getPremiumFee(amount);
}
return result;
}
int getPremiumFee(int amount) {
return (amount * PREMIUM_RATE_ADJ) + SURCHARGE;
}
...
}
Now we can run our tests and see if they still work. If they do, we can go back and get rid of the commented code:
public class Reservation
{
public int calculateHandlingFee(int amount) {
int result = 0;
if (amount < 100) {
result += getBaseFee(amount);
}
else {
result += getPremiumFee(amount);
}
return result;
}
int getPremiumFee(int amount) {
return (amount * PREMIUM_RATE_ADJ) + SURCHARGE;
}
...
}
The example I’ve just shown is just one way of doing Extract Method. When you have tests, it is a relatively simple and safe operation. If you have a refactoring tool, it is even easier. All you have to do is select a portion of a method and make a menu selection. The tool checks to see if that code can be extracted as a method and prompts you for the new method’s name.
Extract Method is a core technique for working with legacy code. You can use it to extract duplication, separate responsibilities, and break down long methods.