Developers tend to use a lot of conditional statements in their code. Of course, each flow has one specific path, its own specific validations, etc. It is very natural that your code has a lot of decisions to be made at runtime, but, mostly, when you use a lot of conditional statements in specific area of your code, probably you are not using it right.
Conditional statements should be used for small decisions, not big decisions, not for choosing one path of five others. (It really hurts to see chained else ifs in a method. Really.)

Want an example? Let's check the code below:

if("visa".equals(transaction.getNetwork())){  
    //do visa stuff
} else if ("diners".equals(transaction.getNetwork())) {
    //do diners stuff
} else if ("mastercard".equals(transaction.getNetwork())) {
    //do mastercard stuff
} else if ("amex".equals(transaction.getNetwork())) {
    //do amex stuff
}

//common stuff in there

return transaction;  

When you need to make a change in this code, what is your first thought?

Am I going to break another block from another decision if I change this?

Probably, yes, especially if you are not cautious. There are a lot of big decisions in only one method. And there is more code at the "//common stuff in there". This block is shared by all the decisions, so, if you change something in there, probably will affect all those other flows.

Besides, it is very hard to predict what is going to happen when this method is executed. You don't know where it could end. There are a lot of ifs, a lot of elses, and also there are shared code blocks.

The first step you need to do is: try not to use the else if.

No, I'm not crazy. You can just do this and make your code considerably more readable:

if("visa".equals(transaction.getNetwork())){  
    //do visa stuff
    return doCommonStuff(transaction);
} 

if ("diners".equals(transaction.getNetwork())) {  
    //do diners stuff
    return doCommonStuff(transaction);
} 

if ("mastercard".equals(transaction.getNetwork())) {  
    //do mastercard stuff
    return doCommonStuff(transaction);
} 

if ("amex".equals(transaction.getNetwork())) {  
    //do amex stuff
    return doCommonStuff(transaction);
}

throw new InvalidInvocationException("There is no support for this network type");

This is what Martin Fowler named as Guard Clauses, and you really should consider using it.

However, the code does not look good yet. We can still do a lot better before saying we are done refactoring this method.

Last week I wrote a post about design patterns. Specifically about which patterns every developer should know. Taking advantage, we are going to apply the Factory Pattern and also The Strategy Pattern in this case.

So, let's create an interface named as NetworkTransactionPerformer. This interface will have one method, called perform.
As we are done doing it, we will have four implementations of this interface. Each one implementing its own credit card network. One for visa, another for mastercard, one other for diners and one for amex. Yes, those implementations are our strategies.

Now, using the Factory Pattern, our code will look like:

NetworkTransactionPerformer performer = factory.get(transaction.getNetwork());  
return doCommonStuff(performer.perform(transaction));  

Based on the Factory Pattern, through the factory.get(network), we can get any of those four NetworkTransactionPerformer strategies. If you implement your factory using a Map, you won't even need to use ifs in the get() method.

Now our code looks a lot better than before, doesn't it? Also, it is much easier to read. Also, you can change something with no fear to break other decisions.

Any questions? Please, leave a comment!

Thanks.

[]'s