Refactoring: Pull Common Code from Conditional

We’ll look at two refactorings suitable for conditional statements: Pull Up Common Head of Conditional and Pull Down Common Tail of Conditional.

I originally learned these as performance optimizations but they also help simplify and clarify code.

In Refactoring, Martin Fowler describes these as “Consolidate Duplicate Conditional Fragments” but goes into less detail.

Pull Up Common Head of Conditional

You have an if, if-elseif, or switch/case statement where the code at the start of each branch is identical.
So: Pull up the common code.

        if (cond-expr)                    head
          head                ?           if (cond-expr)
          if-clause          <=>            if-clause
        else                              else
          head                              else-clause
          else-clause                     endif
        endif

Note: The descriptions will say “if” but the refactoring applies to if-elseif and switch/case statements as well.

Limitations

This refactoring is legal if cond-expr and head have no influence on each other. (See the description of Swap Statement since it’s related.)

Here’s an example where the refactoring isn’t valid.

The Starting CodeA Non-Refactoring
if (x > 0)
  x = 7
  if-clause
else
  x = 7 
  else-clause
endif
x = 7

if (x > 0)
  if-clause
else
  else-clause
endif

The modified code moves identical code above the if but it is not equivalent since it sets x to 7 before checking it.

Mechanics

Check before refactoring:

  1. Check that the head code is identical in all branches.
  2. Check that the head code is self-contained; it can’t contain unterminated statements (such as the first half of a while statement).
  3. Check that the conditional expression and the code to be moved don’t change any overlapping objects. If so, this refactoring does not apply.

The fast way:

  1. Copy the head code from one branch to before the if.
  2. Delete the head code from both branches.
  3. Compile and test

-or- the safer way:

  1. Extract the conditional expression into a local variable condition.
  2. Compile and test.
  3. Move the common head code to between the assignment of condition and the if.
  4. Delete the common head code from the branches.
  5. Compile and test.
  6. Swap Statement [if legal!] so that the local variable is below the head code and next to the if again.
  7. Compile and test.
  8. Inline the condition variable.
  9. Compile and test.

Example

Here’s the “safer” way step by step.

Initial code:

if (year != 0)
  StringBuffer text = new StringBuffer()
  text.append(title)
  text.append(" (")
  text.append(year)
  text.append(").")
  formattedTitle = text.toString()
else
  StringBuffer text = new StringBuffer()
  text.append(title)
  text.append(".")
  formattedTitle = text.toString()
endif

=>  Introduce local variable (condition)

bool condition = year != 0
if (condition)
  StringBuffer text = new StringBuffer()
  text.append(title)
  text.append(" (")
  text.append(year)
  text.append(").")
  formattedTitle = text.toString()
else
  StringBuffer text = new StringBuffer()
  text.append(title)
  text.append(".")
  formattedTitle = text.toString()
endif

=> Pull up the common head and Delete the common head from both branches

bool condition = year != 0
StringBuffer text = new StringBuffer()
text.append(title)
 
if (condition)
  text.append(" (")
  text.append(year)
  text.append(").")
  formattedTitle = text.toString()
else
  text.append(".")
  formattedTitle = text.toString()
endif

=>?  Swap Statement – careful – not always legal! It’s easy to see it’s safe in this case, but it’s trickier when the conditions are complicated and the common code is longer.

StringBuffer text = new StringBuffer()
text.append(title)
bool condition = year != 0
 
if (condition)
  text.append(" (")
  text.append(year)
  text.append(").")
  formattedTitle = text.toString()
else
  text.append(".")
  formattedTitle = text.toString()
endif

=>  Inline Variable

StringBuffer text = new StringBuffer()
text.append(title)
 
if (year != 0)
  text.append(" (")
  text.append(year)
  text.append(").")
  formattedTitle = text.toString()
else
  text.append(".")
  formattedTitle = text.toString()
endif

Example – Going the Other Way

Sometimes we want to run the refactoring backwards, pushing code from before an if to inside it.

Initial Code:

if (type == STAR) 
  stars++
endif

if (type == STAR) 
  text.append("*")
  text.append(name)
else 
  text.append(name)
  text.append(" (supporting)")
endif

Check Conditions: The first if statement is self-contained, and changes nothing that the second condition (type == STAR) looks at, so we can proceed.

=> Pull in the code (to be the new common head)

if (type == STAR)
  if (type == STAR)
    stars++
  endif

  text.append("*")
  text.append(name)
else
  if (type == STAR)
    stars++
  endif

  text.append(name)
  text.append(" (supporting)")
endif

The refactoring is officially done, but we can do more:

=> Delete the Redundant Test: The first inner condition is always true, the second one never is:

if (type == STAR)
  stars++
  text.append("*") 
  text.append(name)
else
  text.append(name)
  text.append(" (supporting)")
endif

If you’ve been coding for any length of time, you probably realized you could just move stars++ into the proper branch without all this fuss. But when the statements are bigger and the conditions are more complex, it’s safer to go in smaller steps.

Pull Down Common Tail of Conditional

You have an if, if-elseif, or switch/case where the code at the end of each branch is identical.
Pull down the common tail code. 


        if (cond-expr)               if (cond-expr)
          if-clause                    if-clause
          tail                       else
        else                 <=>       else-clause
          else-clause                end 
          tail                       tail
        end

Be extra careful if the tail code uses variables declared in the scope of the if or else branches.

Mechanics

Check before using:

  • Check that the tail code is identical.
  • Check that the tail code consists of whole statements. (You can’t extract the last half of an inner while loop, for example.)
  • Significant local variables are those that are declared in the if-clause or else-clause preceding the common tail, and are referenced in the tail. If there are any, check that they have identical types.
  • Useful technique if you have automated refactorings: Start (but don’t finish) an Extract Method on the tail code; the tool will show you if there are incomplete statements or issues with mismatched types.

The steps:

  • If there are any significant local variables, pull up their declaration (but not their initialization) to before the if.
  • Compile and test.
  • Copy the tail code to after the if.
  • Delete the tail code from each branch of the if.
  • Compile and test.

Example

Initial Code:

if (type == STAR)
    StringBuffer text = new StringBuffer("*")
    text.append(name)
    text.append(".")
    formattedName = text.toString()
else
    StringBuffer text = new StringBuffer(name) 
    text.append(" (supporting)")
    text.append(".")
    formattedName = text.toString()
endif

Check that the tail code is identical: The last two lines of each branch are the same.
Check that the tail code is whole statements: No issues there.
Check that significant local declarations are the same: The text variable has the same type in both branches. (Had one been a StringBuffer and the other a StringBuilder, we couldn’t do the refactoring yet. Or if text were declared both before the if and inside only one branch, we couldn’t do the refactoring yet.)

=> Pull up declaration of significant local variables

StringBuffer text;
if (type == STAR)
    text = new StringBuffer("*")
    text.append(name)
    text.append(".")
    formattedName = text.toString()
else
    text = new StringBuffer(name) 
    text.append(" (supporting)")
    text.append(".")
    formattedName = text.toString()
endif

=> Pull down the common tail and Delete the tail from both branches

StringBuffer text;
if (type == STAR)
    text = new StringBuffer("*")
    text.append(name)
else
    text = new StringBuffer(name) 
    text.append(" (supporting)")
endif

text.append(".") 
formattedName = text.toString()

Summary

Conditionals increase the complexity of code, and often hide duplication. Refactorings to Pull Common Code from Conditional remove duplication and usually make the code easier to understand.

—–

Thanks to Nayan Hajratwala, Justin Knowles, and Rick McAdams for comments on an earlier draft.