Fred Porciúncula
Fred Porciúncula

Reputation: 8902

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?

Upvotes: 2

Views: 1519

Answers (4)

caduceus
caduceus

Reputation: 1838

Move your logging side effects into the condition methods and stream the operations

  public void execute() {
    Stream.of(context)
        .filter(c -> c.condition1())
        .peek(this::doSomeStuff)
        .filter(c -> c.condition2())
        .filter(c -> c.condition3())
        .forEach(this::doRestOfStuff);
  }

Upvotes: 0

dkatzel
dkatzel

Reputation: 31648

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();
    }
}

Upvotes: 3

Adi
Adi

Reputation: 2094

Try to read about switch Statement, it is the solution in many languages and specifically in java:

https://docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html

Upvotes: 1

Fred Porci&#250;ncula
Fred Porci&#250;ncula

Reputation: 8902

This is what I've came up with until now:

public void execute() {
    checkCondition1AndExecute();
}

private void checkCondition1AndExecute() {
    if (context.notCondition1()) {
        doSomeStuff();
        checkCondition2AndExecute();
    } else {
        LOGGER.info("Condition 1 not satisfied.");
    }
}

private void checkCondition2AndExecute() {
    if (context.notCondition2()) {
        checkCondition3AndExecute();
    } else {
        LOGGER.info("Condition 2 not satisfied.");
    }
}

private void checkCondition3AndExecute() {
    if (context.notCondition3()) {
        doRestOfStuff();
    } else {
        LOGGER.info("Condition 3 not satisfied.");
    }
}

It doesn't look great but it does smell better for me. Instead of one big method with multiple exits, I have small methods, each one responsible for one specific condition (and exit point). Also, it gets rid of the dumb return; lines.

However, I'm still wondering if there might be a pattern or just a better strategy to handle this.

Upvotes: 0

Related Questions