Search code examples
javadesign-patterns

How to handle multiple exit points in Java


Sometimes I'm faced with a routine that will test some conditions and either continue or exit, something like the following code:

public void execute() {
    if (context.condition1()) {
        LOGGER.info("Condition 1 not satisfied.");
        return;
    }
    doSomeStuff();

    if (context.condition2()) {
        LOGGER.info("Condition 2 not satisfied.");
        return;
    }
    if (context.condition3()) {
        LOGGER.info("Condition 3 not satisfied.");
        return;
    }

    doRestOfStuff();
}

Let's assume there is no way to test these conditions before entrying this method. Maybe this is a servlet or a controller with this single entry point. From my experience (which is far from vast), this seems to be a common situation.

Now, this code doesn't smell good, does it? What would be the best strategy to handle this?


Solution

  • How many different exit points are we talking about?

    If it's only 3 like in your example, then your original code is just fine. It's easy to read. I would consider the "only have one exit point" as a general guide line and not a hard set rule.

    Changing it to a nested group of if / else if blocks I find harder to read especially after indenting. Also you run the risk of forgetting to properly place new conditions to the chain if you have to add more condition checks in the future.

    If however, you have a lot of conditions and you might need to add many more, possibly at runtime, then you might be able to leverage the Chain of Responsibility pattern.

    The end result would look something like this:

    public void execute() {
        List<Condition> conditionChains = ....
    
        for(Condition condition : conditionChain){
              if(condition.notSatisfied(context)){
                   //here use condition#toString() to explain what wasn't satisfied. 
                   //Could use another method like getDescrption() instead...
                   LOGGER.info(condition + " not satisfied.");
                  return;
              }
              condition.execute(context);
        }
    }
    

    Where you would have an interface Condition that had 2 methods to check satisfability and then another to execute any relevant code.

    Example implementations:

    class Condition1 implements Condition{
    
        public boolean isSatisfied(Context context){
           ...
        }
    
        public void execute(Context context){
            doSomeStuff();
        }
    }
    
    
    class Condition2 implements Condition{
    
        public boolean isSatisfied(Context context){
           ...
        }
    
        public void execute(Context context){
           //do nothing
        }
    }
    
    
    class Condition3 implements Condition{
    
        public boolean isSatisfied(Context context){
           return !context.notCondition3();
        }
    
        public void execute(Context context){
            doRestOfStuff();
        }
    }