Skip to content

Instantly share code, notes, and snippets.

@char-1ee
Last active June 23, 2022 18:24
Show Gist options
  • Save char-1ee/6bef696136cfa5b7a0df07c8b2aa8892 to your computer and use it in GitHub Desktop.
Save char-1ee/6bef696136cfa5b7a0df07c8b2aa8892 to your computer and use it in GitHub Desktop.
How to optimize if-else writing

If-else optimizations

With the growth of logics, long nested if-else writing could be a burden for code extensibility and maintainability. There are a series of optimization schemes. In the language syntax level, we have approaches like

  • Switch-case syntax, ternary conditional operator
  • Optional (JDK 8+) for nullable checks
  • Enum methods

In the logic/code structure level, we can do

  • Early return in if-else blocks / remove unnecessary else
  • Merges of condition expressions

While more practical ways based engineering practices:

Table of contents

  1. Thoughts on optimizations
  2. Optimization schemes
    1. Optional
    2. Table driven
    3. Enumeration
    4. Factory pattern
  3. References

Thoughts on optimizations

Assume there encounters a business logic like

if (a) {
   if (b)   f1(); 
   else     f2(); 
} else {
   if (b)   f3(); 
   else     f4(); 
}

If conditions a and b return integer type 0 and 1 to represent boolean false and true, we can easily using the return values as 2D array indexes, to replace above if-else code to

let array = [[f1, f2], [f3, f4]];
array[a][b]();

where stores function pointers in array. In this way, a 4-branch code block is refactored with a 2D table-driven approach.
Actually what we did is encapsulate a complex logic with a basic logic unit into a simpler logic with a more complex logic unit.
That is a trade-off between readability and complexity.

Another example is

if (x % 2 == 0) {
    printf("even");
} else {
    printf("odd");
}

We write as

int arr[] = {"even", "odd"};
printf("%s", arr[x % 2]);

It is like we compress the logic of conditional checks to make code so-called __elegant__, but the cost is less information is exposed to whoever read your code. Fewer lines of code with higher complexity. So give much more considerations before writing 'table-driven' like code in engineering practices.

But optimizations are needed when nested if-else statements grow bigger, complex and unmanageable in nature. Under that premise, I summarized several useful optimization schemes in my stand.

Optimization schemes

Optional

Optional is introduced in JDK 8. It can be used to reduce if-else on not-empty or null judgements.

Before optimization

String str = "Hi I am Xingjian"; 
if (str != null) {    
    System.out.println(str); 
} else {     
    System.out.println("Null"); 
} 

After optimization

Optional<String> strOptional = Optional.of("Hi I am Xingjian");
strOptional.ifPresentOrElse(System.out::println, () -> System.out.println("Null"));

Table driven

Before optimization

if (param.equals(value1)) {     
    doAction1(someParams); 
} else if (param.equals(value2)) {     
    doAction2(someParams); 
} else if (param.equals(value3)) {     
    doAction3(someParams); 
}

After optimization

// Function<T> used as the assignment target for a lambda expression or method reference.
Map<T, Function<T> action> conditionTable = new HashMap<>();

// Initialization
conditionTable.put(condition1, (someParams) -> { doAction1(someParams) });
conditionTable.put(condition2, (someParams) -> { doAction2(someParams) });
conditionTable.put(condition3, (someParams) -> { doAction3(someParams) });

// Apply: pass the condition as Key in table and pass parameters via apply().
conditionTable.get(condition).apply(someParams);

Table-driven method uses Array/HashMap/TreeMap/Set to establish reflections from a conditional check result to a resulting assignment/function/entry to a branching code block. In nature, table-driven is a trade-off between data structure complexity and logic complexity.

Enumerations

Before optimization

String orderStatusDes;
if (orderStatusDes == 0) {
    orderStatusDes = "Order not paid";
} else if (orderStatusDes == 1) {
    orderStatusDes = "Order paid";
} else if (orderStatusDes == 2) {
    orderStatusDes = "Shipped";
} /*...*/

After optimization

public enum OrderStatusEnum {
    UN_PAID(0, "Order not paid"),
    PAIDED(1, "Order paid"),
    SENDED(2, "Shipped");
    /*...*/
    
    private int index;
    private String desc;
    
    public int getIndex() {
        return index;
    }
    
    public String getDesc() {
        return desc;
    }
    
    OrderStatusEnum(int index, String desc) {
        this.index = index;
        this.desc = desc;
    }
    
    OrderStatusEnum of(int orderStatus) {
        for (OrderStatusEnum temp : OrderStatusEnum.values()) {
            if (temp.getIndex() == orderStatus) {
                return temp;
            }
        }
        return null;
    }
}

Apply the enum in if-else block

String orderStatusDes = OrderStatusEnum.of(orderStatus).getDesc();

Compared to last Table-Driven method, above Enumeration example is more likely a value-to-value reflection, which assigns a value to the return result according to the condition checks. Mostly it is because nature Enums in programming languages are value-based. So how Enumberation method is used in value-to-function pattern, aka what if after condition checks the program requires to execute a function in each branch?

Scenario

public int calculate(int a, int b, String operator) {
    int res = Integer.MIN_VALUE;
    if ("add".equals(operator)) {
    	res = a + b;
    } else if ("multiply".equals(operator)) {
        res = a * b;
    } else if ("divide".equals(operator)) {
        res = a / b;
    } else if ("subtract".equals(operator)) {
        res = a - b;
    }
    return res;
}

After optimization

public enum Operator {
    ADD {
        @Override
        public int apply(int a, int b) {
            return a + b;
        }
    },
    MULTIPLY {
        @Override
        public int apply(int a, int b) {
            return a * b;
        }
    },
    SUBTRACT {
        @Override
        public int apply(int a, int b) {
            return a - b;
        }
    }, 
    DIVIDE {
        @Override
        public int apply(int a, int b) {
            return a / b;
        }
    };
    public abstract int apply(int a, int b);
}

Then in the Calculator class, we define the method

public int calculate(int a, int b, Operator operator) {
    return operator.apply(a, b);
}

This method of Enumerations benefits from the syntax sugar of Java Enums.

Factory pattern

We can using the same scenario in Enumerations.

  1. Fecth out the repeated operation pattern as an interface
    public interface Operation {
       int apply(int a, int b);
    }
  2. Implement the interfaces (strategy implementation)
    public int AddOperation implements Operation {
       @Override
       public int apply(int a, int b) {
          return a + b;
       }
    }
    /*...*/
  3. Construct the Factory class
    public class OperatorFactory {
       private static Map<String, Operation> operationMap = new HashMap<>();
       static {
          operationMap.put("add", new AddOperation());
          operationMap.put("subtract", new SubtractOperation());
          operationMap.put("multiply", new MultiplyOperation());
          operationMap.put("divide", new DivideOperation());
       } // store the strategies 
       
       // Returns instance of Operations according to given operator
       public static Operation getOperation(String operator) {
          return operationMap.get(operator);
       }
    }
  4. Query the factory
    public int calculate(int a, int b, String operator) {
       Operation op = OperatorFactory.getOperation(operator);
       return op.apply(a, b);
    }

Factory pattern is widely used together with strategy pattern. It adds an abstraction layer on original if-else logics, which we can regard it as a full utilization of OOP concepts and engineering practices.

Further reading

References


Copyright © 2022 - 2024 Li Xingjian. All rights reserved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment