Block-Level Refactorings

Replacing Single Exits with Multiple Exits (Block Level)

Problem

The Structured Programming movement (Djikstra, Hoare, Wirth, the Pascal language, etc.) first addressed the problem of overly complicated control structures. The conclusion was that control structures would be easier to understand, maintain, and validate if they had a single entry point. The main violator of this principle was the goto statement. For example:

   int n = 0;
   while (n++ < 10)
   {
A:    cout << "n = " << n << endl;
   }
   cout << "while command exited\n";
   goto A;

Unfortunately, many programmers take this idea too far and tend to avoid writing control structures with multiple exit points, which, ironically, can greatly simplify a control structure.

Half way through a task that's being iterated we may discover that there is no point in completing the task. Either a new iteration of the task should begin or no further iterations of the task are needed. In order to maintain a single exit point for the iteration, we need to introduce complicated multi-way conditionals (else-if, else-if, else-if, ...):

while(more) {
   if (num == 0) {
      cout << "Quitting\n";
      more = false;
   } else if (num < 0) {
      cout << "Error: input must be positive\n";
   } else {
      cout << "Result = " << sqrt(num) << '\n';
   }
}

Solution

Alternatively, we can use the continue command to immediately start a new iteration of the task, or a break command to terminate the current task as well as further iterations. This eliminates the need for multi-way conditionals:

while(true) {
   if (num == 0) {
      cout << "Quitting\n";
      break;
   }
   if (num < 0) {
      cout << "Error: input must be positive\n";
      continue;
   }
   cout << "Result = " << sqrt(num) << '\n';
}

Replacing Single Exits with Multiple Exits (Function Level)

Problem

Function blocks can be simplified by providing multiple exits. For example, the following function uses a multi-way conditional to set a local variable:

double tax(double income) {
   double tax;
   if (income < 0)
      throw AppError("income must be non-negative");
   else if (income < 10000)
      tax = 0;
   else if (income < 20000)
      tax = .2 * income;
   else if (income < 35000)
      tax = .3 * income;
   else if (income < 50000)
      tax = .5 * income;
   else
      tax = .75 * income;
   return tax;
}

Solution

Instead, we can return a value as soon as the value as known or throw an exception as soon as an error is detected:

double tax(double income) {
   if (income < 0)
      throw AppError("income must be non-negative");
   if (income < 10000) return 0;
   if (income < 20000) return .2 * income;
   if (income < 35000) return .3 * income;
   if (income < 50000) return .5 * income;
   return .75 * income;
}

Extract Method

Problem

A method has become too long or a task appears in many methods.

Solution

The investment calculator contains a long method that computes and displays the future value of an investment based on a 2.5% monthly rate of return for one year:

public class InvestmentCalculator {
   public static void value(double prin) {
      double rate = .025;
      double term = 12;
      double eVal = 0;
      double temp1 = 1 + rate;
      double temp2 = Math.pow(temp1, term);
      eVal = prin * temp2;
      System.out.println("+++++++++++++");
      System.out.println("rate = " + rate);
      System.out.println("investment = " + prin);
      System.out.println("term = " + term);
      System.out.println("value = " + eVal);
      System.out.println("+++++++++++++");
   }
   public static void main(String[] args) {
      value(12000);
   }
}

In the interests of decoupling presentation logic from application logic, let's apply the extract method refactoring to the output task. Unfortunately, this task uses some of the local variables declafred earlier, so these will need to be passed as parameters to the new method:

public class InvestmentCalculator {
   public static void value(double prin) {
      double rate = .025;
      double term = 12;
      double eVal = 0;
      double temp1 = 1 + rate;
      double temp2 = Math.pow(temp1, term);
      eVal = prin * temp2;
      printResult(rate, prin, term, eVal);
   }
   public static void printResult(
      double rate, double prin, double term, double eVal) {
      System.out.println("+++++++++++++");
      System.out.println("rate = " + rate);
      System.out.println("investment = " + prin);
      System.out.println("term = " + term);
      System.out.println("value = " + eVal);
      System.out.println("+++++++++++++");
   }
   public static void main(String[] args) {
      value(12000);
   }
}

Of course printResult does have a rather long parameter list. Fortunately, there are refactorings for dealing with this problem.

Next, we turn our attention to the calculation of the future or expected value. Applying extract method here is trickier because it modifies the eVal local in the caller's environment. In this case we must return the computed value as a result:

public class InvestmentCalculator {
   public static void value(double prin) {
      double rate = .025;
      double term = 12;
      double eVal = expectedValue(rate, prin, term);
      printResult(rate, prin, term, eVal);
   }
   public static double expectedValue(
      double rate, double prin, double term) {
      double eVal = 0;
      double temp1 = 1 + rate;
      double temp2 = Math.pow(temp1, term);
      eVal = prin * temp2;
      return eVal;
   }
   public static void printResult(
      double rate, double prin, double term, double eVal) {
      System.out.println("+++++++++++++");
      System.out.println("rate = " + rate);
      System.out.println("investment = " + prin);
      System.out.println("term = " + term);
      System.out.println("value = " + eVal);
      System.out.println("+++++++++++++");
   }
   public static void main(String[] args) {
      value(12000);
   }
}

Notice that temp1 and temp2 are placed inside of the new method. This is because they are only needed in the calculation of the expected value.

What happens if the extracted method needs to modify several of the caller's local variables?

Solution 1: The extracted method can return an object, the fields of which are the new values of the local variables.

Solution 2: If the local variables can be passed by reference, then the extracted method can change them directly.