The Nested-If Swap Refactoring

Posted June 22, 2016 by Bill Wake

You sometimes would prefer that two nested if statements were in the opposite order.

The Nested-If Swap lets you change that order—under certain circumstances.

Why?

The most important reason to use the Nested-If Swap has to do with the power of symmetry. When things could be symmetric but aren't, you can often improve the design by increasing the symmetry. For example:

if (x == a) { // do something                     if (x == a) { // do something
} else if (x == b) { // do something else         } else if (x == b) { // do something else
} else if (age >= 18) {                     =>    } else if (x == c) {
    if (x == c) {  // do a third thing                if (age >= 18) { // do a third thing
    }                                                 }
}                                                }

See the asymmetry on the left? Mostly we test for values of x, but in the third case we check age first.

After we do the swap, the outer if statements are all about x. That makes it easier to explore whether x should be a class with subclasses for a-c, or perhaps we need a map keyed on x.


A second, more rare, reason to apply this refactoring is for performance. For example:

  1. Suppose the conditions are equally likely, but the first condition is expensive to evaluate, while the second is cheap. Swapping the order would let us avoid evaluating the first condition so much. —or—
  2. Suppose the first condition is almost always true, but the second is more of a toss-up. Swapping them would mean evaluating the first condition only half as much.

Using the swap in either of these cases could speed things up.

When it's safe, the Nested-If Swap can increase symmetry in your code, enabling further improvements.

Mechanics

Apply the refactoring like this: (where ?⇒ indicates that this refactoring is not always possible)

    if (condition-1) {                          if (condition-2) {
        if (condition-2) {          ?               if (condition-1) {
            do work                                   do work
        }                                           }
        // No "else" clause or 
        // other code here                             
    }                                           }

(There are ways to handle an else clause or code after the inner if, but this example shows the easiest, most common case.)


Let's watch that transformation in slow motion: (green moves are always safe; yellow moves require care)

    if (condition-1) {
        if (condition-2) {
            do work
        }
        // Nothing here
    }
[Nested if to &&] (green)
    if ((condition-1) && (condition-2)) {
        do work
    }
  ?
[Change && to & - if legal!] (yellow)
    if ((condition-1) & (condition-2)) {
        do work
    }
[Swap left- and right-hand sides] (green)
    if ((condition-2) & (condition-1)) {
        do work
    }
[Change & to &&] (green)
    if ((condition-2) && (condition-1)) {
        do work
    }
[Change && to nested if] (green)
    if (condition-2) {
        if (condition-1) {
            do work
        }
    }

That Iffy Middle Step

The critical part is the change from && to &: this refactoring is only allowed if they're equivalent in this context. That is, the safety of evaluating the second if doesn't depend on the result of the first one.

That prevents improper transformations like:

    if (p != null) {              if (p.age >= 18) {
        if (p.age >= 18) {         if (p != null) {
    ...                           ...

If p is null, the first version works but the second version throws an exception.

So: The Nested-If Swap is safe only if the validity of the second condition doesn't depend on the first condition being true.

Implementation Note

I don't know any development environments that have this refactoring, but some have a keystroke that quickly moves a line (or a selection) up or down. (In Eclipse, it's Option-UpArrow and Option-DownArrow; in IntelliJ, Shift-Option-UpArrow and Shift-Option-DownArrow.)

Conclusion

The nested-if swap is ideal when you want to safely rearrange if statements to make them more symmetric, so you can improve the design.

When you'd like to swap a pair of nested if statements, it's legitimate if the only thing in the outer if statement is the inner one, and if it's safe to evaluate the second condition before the first.

Code Katas
Want to try? Make this move on the Gilded Rose code in our Code Katas album (free!):
http://industriallogic.com/katas

Thanks to Joshua Kerievsky, Chris Freeman, Nayan Hajratwala, and Gerard Meszaros for feedback on earlier drafts. Thanks to Dave Bradshaw for catching a mistake (now fixed) in the first example.
  • Dave Bradshaw

    It’s probably a typo but this refactoring also isn’t safe when the outer if statement has an associated else statement, which is the case in your very first example. You may want to delete that final else statement to make it safe to do the swap.

    • Bill Wake

      You’re right of course – I’ve taken care of it. –Thanks, Bill