There are a lot of if/else in the code, what optimization plan do you have?

Point of View One (Spirit Sword):

        Periodic iteration is too lazy to optimize, come to a demand, add an if, over time, it becomes a pyramid.

 

 

When the code has become too complex to maintain, it can only be refactored and optimized. Then, is there any solution to elegantly optimize these redundant if/else? 

 

1. Return in advance

This is the way to reverse the judgment condition. The code will be more clear in logical expression. Look at the following code:

if (condition) {
 // do something
} else {
  return xxx;
}

 

if (!condition) {
  return xxx;

} 
// do something

 

2. Strategy Mode

    There is a scenario where different logics are used according to different parameters. In fact, this scenario is very common. The most general implementation:

if (strategy.equals("fast")) {
  // 快速执行
} else if (strategy.equals("normal")) {
  // 正常执行
} else if (strategy.equals("smooth")) {
  // 平滑执行
} else if (strategy.equals("slow")) {
  // 慢慢执行
}

Looking at the code above, there are 4 strategies and two optimization schemes.

      2.1 Polymorphism

                  

interface Strategy {
  void run() throws Exception;
}

class FastStrategy implements Strategy {
    @Override
    void run() throws Exception {
        // 快速执行逻辑
    }
}

class NormalStrategy implements Strategy {
    @Override
    void run() throws Exception {
        // 正常执行逻辑
    }
}

class SmoothStrategy implements Strategy {
    @Override
    void run() throws Exception {
        // 平滑执行逻辑
    }
}

class SlowStrategy implements Strategy {
    @Override
    void run() throws Exception {
        // 慢速执行逻辑
    }
}

 

The specific strategy object is stored in a Map, the implementation after optimization

Strategy strategy = map.get(param);
strategy.run();

The above optimization scheme has a drawback. In order to quickly get the corresponding strategy implementation, a map object is needed to save the strategy. When a new strategy is added, it also needs to be manually added to the map, which is easy to be ignored.

 

2.2 Enumeration

I found that many students do not know that methods can be defined in enumerations. Here we define an enumeration that represents the state, and we can also implement a run method.

public enum Status {
    NEW(0) {
      @Override
      void run() {
        //do something  
      }
    },
    RUNNABLE(1) {
      @Override
       void run() {
         //do something  
      }
    };

    public int statusCode;

    abstract void run();

    Status(int statusCode){
        this.statusCode = statusCode;
    }
}

Redefine the strategy enumeration

public enum Strategy {
    FAST {
      @Override
      void run() {
        //do something  
      }
    },
    NORMAL {
      @Override
       void run() {
         //do something  
      }
    },

    SMOOTH {
      @Override
       void run() {
         //do something  
      }
    },

    SLOW {
      @Override
       void run() {
         //do something  
      }
    };
    abstract void run();
}

The code after optimization by enumeration is as follows

Strategy strategy = Strategy.valueOf(param);
strategy.run();

3. Learn to use Optional

Optional is mainly used for non-empty judgment. Because it is a new feature of jdk8, it is not used very much, but it is really cool to use. Before use:

if (user == null) {
    //do action 1
} else {
    //do action2
}

If the logged-in user is empty, execute action1, otherwise execute action 2. After using Optional optimization, make non-null verification more elegant and indirectly reduce if operations

Optional<User> userOptional = Optional.ofNullable(user);
userOptional.map(action1).orElse(action2);

4. Array Tips

From Google’s explanation, this is a programming mode called table-driven method. The essence is to query information from the table instead of logical statements. For example, there is a scenario where the number of days in the month is obtained by month. It is only used as a case demonstration, and the data is not rigorous.

General realization:

int getDays(int month){
    if (month == 1)  return 31;
    if (month == 2)  return 29;
    if (month == 3)  return 31;
    if (month == 4)  return 30;
    if (month == 5)  return 31;
    if (month == 6)  return 30;
    if (month == 7)  return 31;
    if (month == 8)  return 31;
    if (month == 9)  return 30;
    if (month == 10)  return 31;
    if (month == 11)  return 30;
    if (month == 12)  return 31;
}

Optimized code

int monthDays[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int getDays(int month){
    return monthDays[--month];
}

If else, as an indispensable conditional statement in every programming language, it will be widely used in programming. It is generally recommended that the nesting should not exceed three levels. If there is too much if else nesting in a piece of code, the readability of the code will drop rapidly, and the difficulty of subsequent maintenance will also be greatly improved.

 

View Two (IT Technology Control):

 Don't pay too much attention to the number of if/else layers, but pay attention to whether the interface semantics is clear enough; simply reduce the number of if/else layers, and then take out a bunch of do_logic1, do_logic2... such interfaces are useless.

The execution process of any interface can be expressed as: input + internal state -> output. We will discuss it in the following situations:

          The input, internal state, and output are all simple, but the intermediate logic is complicated. For example, a carefully optimized numerical calculation program may need to adopt different strategies in different value ranges according to the input, and there are many logics to deal with boundary values ​​that will cause problems (such as division by 0). In this case, if A large number of /else is unavoidable. Separating some internal methods according to the steps is helpful, but it cannot completely solve the problem. The best thing to do in this case is to write a detailed document, starting from the most primitive mathematical model, and then show what kind of calculation strategy to take under what circumstances, how to derive the strategy, know the specific form used in the code, and then Add notes to the entire method and attach the document address, and add notes to each branch to indicate which formula corresponds to the document. In this case, although the method is complicated, the semantics are clear. If you don't modify the implementation, you can understand the semantics. If you want to modify the implementation, you need to refer to the formula in the control document.

         The input is too complicated, for example, the input has a bunch of different parameters, or there are various strange flags, and each flag has a different function. In this case, the abstraction level of the interface needs to be improved first: if the interface has multiple different functions, it needs to be split into different interfaces; if the interface is divided into different branches according to different parameters, these parameters and corresponding branches need to be packaged in the Adapter and used The parameter is rewritten into the interface of the Adapter, and different implementations are entered according to the type of Adapter passed in; if there is a complex parameter conversion relationship within the interface, it needs to be rewritten into a lookup table. The main problem in this case is the abstraction of the interface itself. With a clearer abstraction, there are naturally not so many if/else implementations.

         The output is too complicated. In order to save trouble, too many things are calculated in one process, and a bunch of flags are added to control whether to calculate or not for performance. In this case, you need to decisively split the method into multiple different methods, and each method only returns what it needs. What if there are internal results shared between different calculations? If this internal result calculation does not form a bottleneck, just extract the internal method and call it separately in different processes; if you want to avoid double calculation, you can add an additional cache object as a parameter. The cache content is not transparent to the user, and the user only guarantees The same cache object can be used for the same input. In the calculation, the intermediate result is saved in the cache. Before the next calculation, check whether there is a result already obtained, which can avoid double calculation.

        The internal state is too complicated. First check whether the state setting is reasonable. Is there something that should be used as an input parameter in the internal state (for example, it is used to implicitly pass parameters between two different method calls)? Secondly, which aspects are controlled by these states, and can they be grouped and implemented in different StateManagers? Third, draw a state transition diagram, try to divide the internal state into single-level branches, and then implement them into methods such as on_xxx_state, and then call them through a single-level switch or lookup table.

In fact, what usually needs to be optimized is the abstraction of the overall interface, rather than the realization of a single interface. The unclear realization of a single interface is usually because the interface implementation and requirements are structured differently.

 

Guess you like

Origin blog.csdn.net/u011694328/article/details/113102890