Dragon
Dragon

Reputation: 2481

Java: refactoring of multiple if statements

"Multiple if statements" is a standard code smell. There's a bunch of approaches for how to refactor it. In simple cases I try to use strategy pattern. But recently I ran at the code where multiple if statements with int ranges were used. And I have no ideas how to make this code clean.

Here is an example of such a code:

public void calculate(int i) {
    if(i > 0 && i < 5) {
        // do smth
    } else if(i > 4 && i < 10) {
        //do smth
    } else if (i >= 10 && i <20 ) {
        //do smth
    }
    //...
    else if (i > 90 && i < 100) {
        //do smth
    }
}

I tried to extract every range to some logical unit in order to use strategy, but all these if statements were just moved out of this method and wouldn't disappear at all.

Is there any way to refactor such if statements (i.e. where int ranges are checked)?

Upvotes: 1

Views: 4745

Answers (4)

Jotunacorn
Jotunacorn

Reputation: 526

I dont think there is a good way to do it without the if branching but you may skip the lower bound since you are using else if e.g.

public void calculate(int i) {
    if( i <= 0) {
        return;
    } else if(i < 5) {
        // do smth
    } else if(i < 10) {
        //do smth
    } else if (i <20 ) {
        //do smth
    }
    //...
    else if (i < 100) {
        //do smth
    }
}

Edit: Updated it to include the 0 case. Thanks Stultuske

Upvotes: 2

DDeMartini
DDeMartini

Reputation: 337

A possible design pattern to consider:

Facing a similar problem (range testing ICAO Squawk values with sparseness), I too was confounded by the rather messy look of the code.

My solution was a static function that performed a range test, then simply called that to compute the 'between' test (if there is a Java between function, I don't know it...). Here are snippets of how I solved this; not a good pattern for everything but maybe this sparks an idea for another solution.

/**
 * Range test shorthand 
 * 
 * @param value - value to test
 * @param min   - lower bound
 * @param max   - upper bound
 * 
 * @return  true | false  
 */
private static boolean inRange(int value, int min, int max){
    return min <= value && value <= max;
}

This is how I used it:

        // not a pre-defined code...  run some range tests next to quess
    if (inRange(squawk,41,57)) {
        message = "test";
    }
    else if (inRange(squawk,100,400)){
        message = "Unique Purpose and Experimental activities";  // ud 17-OCT
    }
    else if (inRange(squawk,100,700)){  // Note! this is an overlap defined IN Order 7110.66E
        message = "Oceanic Airspace";   // ud 17-OCT
    }
    else if (inRange(squawk,1207,1272)){
        message = "DVFR Aircraft (USA)";  // ud 17-OCT
    }
    else if (inRange(squawk,1273,1275)){
        message = "Calibration Performance Monitoring Equipment";
    }

[...]

Using simple cascading would not work for me since it's a sparse set, and there is also a map involved... (here is part of that, for reference):

private static final Map<Integer,String> codes;
static {
    codes = new HashMap<Integer,String>();

    /* unremarkable codes */
    codes.put(0,    "");
    codes.put(0000, "");  // there message defined for 0000 in 7110.66E spec, but I'm using an empty string
    codes.put(0021, "VFR below 5000ft.");
    codes.put(0022, "VFR above 5000ft.");
    codes.put(0033, "Parachute Drop Operations");
    codes.put(0100, "Airport Flight Operations");
    codes.put(0500, "External ARTCC subsets");
    codes.put(0600, "External ARTCC subsets");
    codes.put(0700, "External ARTCC subsets");

Upvotes: 0

duffymo
duffymo

Reputation: 308733

This isn't the best example to make that point.

It's arguable that this code is relatively clean. It certainly is easy to read, perhaps more so than a complex strategy pattern. I think of strategy when polymorphism comes into play.

This example can easily be cleaned up using a Map, where the key is the max value in the range and the value is an interface type reference from the java.util.function package in JDK 8. Perhaps IntToDoubleFunction is what you need.

Why does your calculate method appear to do nothing? Shouldn't it return a calculation result?

private Map<Integer, IntToDoubleFunction> strategy = new TreeMap<Integer, IntToDoubleFunction>() {{
    put(5, new IntToDoubleFunction() { // add function here });
    put(90, new IntToDoubleFunction() { // add function here });
}};

void calculate(int input) {
    double result = 0.0;
    for (Integer maxValueInRange: this.strategy.keySet()) {
        if (input <= maxValueInRange) {
            result = this.strategy.get(maxValueInRange).applyAsDouble(input);
            break;
            // what happens to result?
        }
    }
}

Upvotes: 4

Stultuske
Stultuske

Reputation: 9427

try to change

if(i > 0 && i < 5) {
    // do smth
} else if(i > 4 && i < 10) {
    //do smth
} else if (i >= 10 && i <20 ) {
    //do smth
}
//...
else if (i > 90 && i < 100) {
    //do smth
}

to something like:

if(i > 0){
  if ( i < 5 ){

  } else if (i < 10 ) {
  //
  }
}

simpler, and leads to the same result

Upvotes: 1

Related Questions