How to deal with nested conditionals (Part 1)

Reading Time: 2 minutes

In my current project, there are 1000s of lines of code (LOC), some of which date back to 10 years ago. An important part of my day-to-day job is try to understand and debug functions just like the following example (Listing 1). I omitted the boolean expressions and the code in branches but it is still quite a long function!..Btw, there are many of them in the production code:)

Listing 1:
public Response function(){ 
if () { 
    if () 
    if () { 
       if ( ) {
         if () {
             for () {
               if () {
                 if () {
                 }
               }
             }
         } else {
         }
       }
    } else {
        if () { 
           if () {}
           if () {
               for () {
                   // Line 21: see the code in listing 2
                   if () {     
                      if () {
                      }
                   }
                   // Line 26: see the code in listing 2 
                }
                if () {
                }
           } else {
        }
    } else {
        // Line 33: see the code in listing 3
        if () { 
          if () {
            if () { 
             // Line 37: see the code in listing 3
              if () {
              }
              if () {
              }
              if () {
                if () {
                   if () {
                     if () {
                       if () {
                         if () {
                            if () {
                            }
                            if () {
                            }
                         }
                      } else {
                      }
                     } else {
                   }
                } else {
               }
             }
           } else {
          }
         }
      } else if () { 
               if () {
                 if () {
                  if () {
                    if () {
                       if () {
                       }
                    }
                    if () {
                       if () {
                         if () {
                         } else {
                         }
                         if () {
                         } else {
                         }
                       }
                     }
                   }
                 } 
               }
             }
           }
         }
       }
     } else
     if () { 
     }
   return response;
}

If you recognized, the nested if-else statements looks like an arrow (>). That’s why this kind of usage is called as arrow anti pattern.

You might think that “what is the problem? if it is working, it’s fine!”. Indeed, it is working but…

  • is it easy for you to read it?
  • can you understand the execution flow?
  • what if you add new functionality to that function? are you sure that you won’t break existing requirements?
  • have you heard of “cyclomatic complexity“?
  • how testable is that function?

I think somebody knows the answers…Any fool can write code that a computer can understand. Good programmers write code that humans can understand“, Martin Fowler, 2008.

The original code, 412 LOC in total, is much more complex in terms of readability and maintainability. There are multiple boolean expressions in almost every if statement; such as the code between line 21 and 26 in Listing 1:

Listing 2:
if (retrieveIndividualCustomerAddressInfoResponse.getAddresses()[a].getContactType() != null && retrieveIndividualCustomerAddressInfoResponse.getAddresses()[a].getContactType().getValue() == SubContactTypeCS.POSTAL_ADDRESS) {
  if (retrieveIndividualCustomerAddressInfoResponse.getAddresses()[a].getPartyRoleInContactPointType() != null && retrieveIndividualCustomerAddressInfoResponse.getAddresses()[a].getPartyRoleInContactPointType().getValue() == PartyRoleInContactPointTypeCS.HOME) {
     ...    
  }
}

Decompose Conditional

We can start refactoring with complicated conditionals. Extract methods for each boolean expression as follows. The result is still nested but there is another technique called “Consolidate Conditional Expression”.

if (isValidContactType()) {
  if (isValidPartyRoleInContactPointType()) {
     ...    
  }
}
private boolean isValidContractType(){
   return retrieveIndividualCustomerAddressInfoResponse.getAddresses()[a].getContactType() != null && retrieveIndividualCustomerAddressInfoResponse.getAddresses()[a].getContactType().getValue() == SubContactTypeCS.POSTAL_ADDRESS;
}

private boolean isValidPartyRoleInContactPointType(){ 
   return retrieveIndividualCustomerAddressInfoResponse.getAddresses()[a].getPartyRoleInContactPointType() != null && retrieveIndividualCustomerAddressInfoResponse.getAddresses()[a].getPartyRoleInContactPointType().getValue() == PartyRoleInContactPointTypeCS.HOME; 
}

Consolidate Conditional Expression

Another attempt to eliminate nested if-else statements could be combining multiple conditionals into a single conditional expression and extract it; such as the code between line 33 and 37 in Listing 1:

Listing 3:
if (customerType != Constants.CUSTOMERID) {
   if (customerType == Constants.XXX) {
      ...      
      if (retrieveCitizenResponse != null && retrieveCitizenResponse.getCitizen() != null) {
      }
     }
}

Step 1: Consolidate conditional into one statement:

Listing 3 Step 1:
if (customerType != Constants.CUSTOMERID && customerType == Constants.XXX && retrieveCitizenResponse != null && retrieveCitizenResponse.getCitizen() != null) {
}

Step 2: Decompose Conditional into relevant functions

Listing 3 Step 2:
if (getCustomerType() && retrieveCitizenResponse()) {
  ...
}
private boolean getCustomerType (){ 
   return customerType != Constants.CUSTOMERID && customerType == Constants.XXX;
} 
private boolean retrieveCitizenResponse(){
   return retrieveCitizenResponse != null && retrieveCitizenResponse.getCitizen() != null;
}