Why do we always write if-else code?

Programmers must have experienced such scenarios: at the beginning, their code is very simple, with clear logic and simplified functions, without an if-else, which can be improved with the continuous improvement of code logic and the rapid change of business: for example, the type and value of input parameters need to be judged; I’m going to check whether the object is null; Different types perform different processes.

When it comes to the concrete implementation, we can only keep adding if-else to deal with it. Gradually, the code becomes bigger and bigger, the function becomes longer and longer, the number of lines in the file quickly breaks through thousands of lines, and the maintenance becomes more and more difficult, and basically reaches a state of difficult maintenance in the later stage.

Although we are reluctant to write code with a screen full of if-else, the logic requires special judgment, desperation, but there is no way to avoid it.

In fact, if you look back at your own code, there are only two scenarios for writing if-else: exception logic handling and different state handling.

The main difference between the two is that exception logic indicates that only one branch is normal flow, while all branches are normal flow for different state processing.

How to understand? Here’s an example:

Object obj = getObj(); if (obj ! = null) {//do something}else{//do something} if (obj.getType == 1) { //do something }else if (obj.getType == 2) { //do something }else{ //do something }Copy the code

The first example is if (obj! = null) is the exception handling, is the code robustness judgment, only if is the normal processing flow, else branch is the error processing flow; The second example, whether type equals 1,2 or otherwise, is the normal flow of business. The refactoring approach is also different in both cases.

What’s the downside of too much if-else code?

The disadvantages are quite obvious: the biggest problem is that the code logic is complex, the maintainability is poor, and it is very bug prone. If if-else is used, it indicates that the if branch and the else branch are equally valued, but this is not the case in most cases, which can lead to misunderstanding and difficulty in understanding.

Is there a good way to optimize? How do you refactor?

There must be a way. When refactoring if-else, keep one rule in mind at all times:

Keep normal flow code in the outermost layer as much as possible.

When you can write if-else statements, try to keep the trunk as normal as possible and avoid too much nesting.

The implementation methods include: reducing nesting, removing temporary variables, reverse judgment of conditions, merging conditional expressions, etc.

Here are a few examples of these refactoring methods:

  • Example 1 of abnormal logic processing reconstruction method

Refactoring:

double disablityAmount(){
    if(_seniority < 2)
        return 0;
    if(_monthsDisabled > 12)
        return 0;
    if(_isPartTime)
        return 0;
    //do somethig
}
Copy the code

After the refactoring:

double disablityAmount(){
    if(_seniority < 2 || _monthsDisabled > 12 || _isPartTime)
        return 0;
    //do somethig
}
Copy the code

The refactoring technique here is called merging conditional expressions: If you have a series of conditional tests that all yield the same result, merge those result tests into a single conditional expression.

This refactoring is easy to understand and the results are obvious, effectively reducing the number of if statements, reducing the amount of code and making it logically easier to understand.

  • Example 2 of abnormal logic processing reconstruction method

Refactoring:

double getPayAmount(){ double result; if(_isDead) { result = deadAmount(); }else{ if(_isSeparated){ result = separatedAmount(); } else{ if(_isRetired){ result = retiredAmount(); else{ result = normalPayAmount(); }}}}Copy the code

After the refactoring:

double getPayAmount(){
    if(_isDead)
        return deadAmount();
    if(_isSeparated)
        return separatedAmount();
    if(_isRetired)
        return retiredAmount();
    return normalPayAmount();
}
Copy the code

How’s that? Comparing the two versions, you will find that the reconstructed version is logical and simple.

What’s the difference from pre-refactoring?

The biggest difference is the reduction of if-else nesting. As you can see, the original version of if-else has three layers of nesting at its deepest level, and it looks like there are so many branches that you can almost get confused if you enter it. In fact, if you think about it, the nested if-else has no relation to the outermost layer, so you can extract the outermost layer.

If else, the number of if-else is the same, but the logic is clear.

Another refactoring point was to do away with the result temporary variable and return directly. The benefits are also obvious: end the process directly and shorten the abnormal branch process. In the original practice, the value is assigned to result and then unified return, so it is not clear which function returns the value of the last return, which adds another layer of difficulty to understand.

Summary of refactoring points: If if-else nesting has no correlation, extract directly to the first layer, be sure to avoid logical nesting too deep. Minimize the use of temporary variables to return directly.

  • Example 3 of abnormal logic processing reconstruction method

Refactoring:

Public double adjustedCapital (){double result = 0.0; If (_capital > 0.0){if(_intRate >0 &&_duration >0){resutl = (_income / _duration) *ADJ_FACTOR; } } return result; }Copy the code

The first step is to reduce nesting and remove temporary variables using the first trick:

Public double adjustedCapital (){if(_capital <= 0.0){return adjustedCapital (); } if(_intRate > 0 && _duration >0){ return (_income / _duration) *ADJ_FACTOR; } to return 0.0; }Copy the code

This refactoring is not enough because the main statement (_income / _duration) *ADJ_FACTOR; Within if, but not at the outermost layer, refactoring can continue according to optimization principles (keeping normal flow code at the outermost layer as much as possible) :

Public double adjustedCapital (){if(_capital <= 0.0){return adjustedCapital (); } the if (_intRate < = 0 | | _duration < = 0) {return 0.0; } return (_income / _duration) *ADJ_FACTOR; }Copy the code

This is good code style, clear logic, clear at a glance, no if-else nested incomprehensible flow.

The refactoring method used here is to reverse the condition so that the exception exits first, leaving the normal flow in the main flow.

  • Example 4 of abnormal logic processing reconstruction method

Refactoring:

Public ArrayList<Student> getStudents(int uid){ArrayList<Student> result = new ArrayList<Student>(); Student stu = getStudentByUid(uid); if (stu ! = null) { Teacher teacher = stu.getTeacher(); if(teacher ! = null){ ArrayList<Student> students = teacher.getStudents(); if(students ! = null){ for(Student student : students){ if(student.getAge() > = 18 && student.getGender() == MALE){ result.add(student); }}}else {logger.error(" Failed to get student list "); }}else {logger.error(" Failed to get teacher information "); }} else {logger.error(" Failed to get student information "); } return result; }Copy the code

Typical “arrow” code, the biggest problem is too deep nesting, the solution is to exit the exception condition first, keep the trunk flow is the core flow:

After the refactoring:

Public ArrayList<Student> getStudents(int uid){ArrayList<Student> result = new ArrayList<Student>(); Student stu = getStudentByUid(uid); If (stu == null) {logger.error(" Failed to get student information "); return result; } Teacher teacher = stu.getTeacher(); If (teacher == null){logger.error(" failed to get teacher information "); return result; } ArrayList<Student> students = teacher.getStudents(); If (students == null){logger.error(" Failed to get student list "); return result; } for(Student student : students){ if(student.getAge() > 18 && student.getGender() == MALE){ result.add(student); } } return result; }Copy the code

  • Example 1 of state-processing reconstruction method

Refactoring:

double getPayAmount(){
    Object obj = getObj();
    double money = 0;
    if (obj.getType == 1) {
        ObjectA objA = obj.getObjectA();
        money = objA.getMoney()*obj.getNormalMoneryA();
    }    else if (obj.getType == 2) {
        ObjectB objB = obj.getObjectB();
        money = objB.getMoney()*obj.getNormalMoneryB()+1000;
    }}
Copy the code

After the refactoring:

double getPayAmount(){
    Object obj = getObj();
    if (obj.getType == 1) {
        return getType1Money(obj);
    }    else if (obj.getType == 2) {
        return getType2Money(obj);
    }}double getType1Money(Object obj){
    ObjectA objA = obj.getObjectA();
    return objA.getMoney()*obj.getNormalMoneryA();
}double getType2Money(Object obj){
    ObjectB objB = obj.getObjectB();
    return objB.getMoney()*obj.getNormalMoneryB()+1000;
}
Copy the code

The refactoring method used here is to wrap the if-else code into a common function. The function has the advantage of masking the internal implementation and shortening the code for the if-else branch. The code structure and logical clear, can be seen in each condition to do the function.

  • Example 2 of state-processing reconstruction method

An elegant way to do state-handling code is to replace conditional expressions with polymorphism (refactoring recommends that).

You have a conditional expression that selects different behaviors based on the object type. Put each branch of this expression into an overriding function in a subclass, and then declare the original function as an abstract function.

Refactoring:

double getSpeed(){
    switch(_type){
        case EUROPEAN:
            return getBaseSpeed();
        case AFRICAN:
            return getBaseSpeed()-getLoadFactor()*_numberOfCoconuts;
        case NORWEGIAN_BLUE:
            return (_isNailed)?0:getBaseSpeed(_voltage);
    }
}
Copy the code

After the refactoring:

class Bird{ abstract double getSpeed(); }class European extends Bird{ double getSpeed(){ return getBaseSpeed(); }}class African extends Bird{ double getSpeed(){ return getBaseSpeed()-getLoadFactor()*_numberOfCoconuts; }}class NorwegianBlue extends Bird{ double getSpeed(){ return (_isNailed)? 0:getBaseSpeed(_voltage); }}Copy the code

As you can see, the if-else is gone with polymorphism, but it takes a lot of work to modify the original code using polymorphism. It is best to use polymorphism from the beginning of design.

conclusion

If-else code is the easiest code for any programmer to write, and it’s also the easiest code to break, resulting in a bunch of hard-to-maintain and illogical code if you don’t pay attention to it.

One principle for conditional code refactoring:

As much as possible, keep the normal process code in the outermost layer and the trunk as the normal core process.

To maintain this principle, merging conditional expressions can effectively reduce the number of if statements; Reducing nesting reduces deep logic; The abnormal condition first exits and the trunk flow becomes the normal flow.

There are two methods for state-processing reconstruction: one is to encapsulate operations in different states into functions with short if-else lines of code; The other method uses object-oriented polymorphism to eliminate conditional judgment directly.

Now take a look at your code, what typical mistakes you made, and use these refactoring methods to refactor your code!

The last

Thank you for reading the last, if the article is not enough, welcome to support in the comment section, give your opinion. If you find my article helpful, give me a thumbs up.

Share Java related technical articles or industry news every day. Welcome to follow and forward this article.