preface

Compared with procedural programming, object-oriented programming has relatively fewer conditional expressions because many conditional behaviors can be handled by polymorphic mechanisms. But sometimes we come across some conditional expressions written by friends that are no different from procedural programming, such as this code:

The whole code has three layers, and each layer has if-else. The logic of this code itself is hard enough to understand, and what is more disgusting is that the caller of this method and other methods that call it also have several layers of if-else nested; In addition, there is a big problem with this code is that the parameter object passed in has been modified many times internally and in other methods called, which makes it even more difficult to understand; It’s too hard to read on an ordinary single-core CPU, and I feel physically drained to maintain this code

Sometimes we may encounter complex conditional logic that requires us to find a way to break it up into small pieces that separate the branch logic from the operation details. See a programmer code how, first to see whether his conditional expression is simple enough to understand; Today we are going to share some common ways to simplify conditional expressions. Most of the examples in this article come from Refactoring to Improve existing Code Design


Decomposition conditional expression

Complex conditional logic is one of the most often linked to an increased complexity places, if other branches of the internal logic is very much also, eventually we will have a big function, a long way to readability itself will fall, so we need to put the big way of harlem, the method of multiple of implementation for each method to take an easy crystal clear internal logical name of the method, This will greatly improve readability.

For example:

if (date.before (SUMMER_START) || date.after(SUMMER_END)) {
    charge = quantity * _winterRate + _winterServiceCharge;
} else {
    charge = quantity * _summerRate
}
Copy the code

This is the kind of code that many people might think is unnecessary to extract methods, but if we want to understand this code, we have to think about it to know what we’re doing; So let’s modify it

if (notSummer(date)) {
    charge = winterCharge(quantity);
} else {
    charge = summerCharge(quantity);
}

private boolean notSummer(Date date){
    date.before (SUMMER_START) || date.after(SUMMER_END)
}

private double summerCharge(int quantity) {
    return quantity * _summerRate;
}

private double winterCharge(int quantity) {
    return quantity * _winterRate + _winterServiceCharge;
}
Copy the code

After this modification is very clear, do not need to write good code itself annotations (code is self explanatory), do not need to be inside the method to write any more comments, sometimes we will see a homecoming within the method a few lines will write a comment, it shows itself code from declarative is not good enough, can improve the readability of the code by means of this example just now


Merge conditional expression

When encountering multiple if conditions in a piece of code, but the logic inside the conditions is not similar, we can merge the conditions together and then extract the method.

Example 1:

double disabilityAmount () { if(_seniortiy <2 ) return 0; if(_monthsDisabled > 12) return 0; if(_isPartTime) return 0; / / to omit... }Copy the code

All of these conditions are going to return the same result, so let’s just combine the conditions

double disabilityAmount () { if(_seniortiy <2 || _monthsDisabled > 12 || _isPartTime) { return 0; } // omit... }Copy the code

Next, we will extract judgment conditions into methods to improve readability

double disabilityAmount () { if(isNotEligibleForDisableility()) { return 0; } // omit... } boolean isNotEligibleForDisableility() { return _seniortiy <2 || _monthsDisabled > 12 || _isPartTime; }Copy the code

Example 2:

if(onVacation()) {
    if(lengthOfService() > 10) {
        return 2;
    }
}
return 1;
Copy the code

The merged code

if(onVacation() && lengthOfService() > 10){
    return 2
}
return 1;
Copy the code

We can then use the ternary operator to simplify the modified code even further:

return onVacation() && lengthOfService() > 10 ? 2:1;Copy the code

From these two examples, we can see how easy it is to improve code readability by separating conditional logic from branch logic in different ways. So a good method of extraction is the key; I think there should be applause here


Merge repeated conditional fragments

Let’s start with an example. Children under the age of 10 get 50% off

If (ageLt10(age)) {price = price * 0.5; doSomething(); } else { price = price * 1; doSomething(); }Copy the code

We found that the different branch’s late code performs the same logic, at that time we can add this code to extract condition judgment, here have example is relatively simple, usually encountered in the work may not be such a simple method, but many lines of complex logic conditions, we can put this code extracted into a method, The method call is then placed either before or after the condition judgment

The modified code

If (ageLt10(age)) {price = price * 0.5; } else { price = price * 1; } doSomething();Copy the code

We can also do this when we encounter the same logic in a try-catch


Guard statements replace nested conditional expressions

Deep nested logic in a method makes it difficult to follow the main thread of execution. When using theif-elseIndicates that both branches are equally important and both are mainline processes; As shown below,

But most of the time we will encounter only one mainline flow and the rest will be individual exceptions, used in this caseif-elseInstead of nested expressions, use a nested statement.

Example 1:

In the compensation system, special rules are used to deal with the salaries of deceased employees, overseas employees and retired employees. These situations rarely occur and are not normal logic.

double getPayAmount() {
    double result;
    if(isDead){
        result = deadAmount();
    } else {
        if(isSeparated) {
            result = separatedAmount();
        } else {
            if(isRetired) {
                result = retiredAmount();
            } else {
                result = normalPayAmount();
            }
        }
    }
    return result;
}
Copy the code

In this code, we can’t see what normal flow is at all. These occasional situations cover up normal flow. Once an occasional situation occurs, we should just go back, and directing the maintainer of the code to a meaningless else will only hinder understanding; Let’s change that with return

double getPayAmount() {
    if(isDead){
        return deadAmount();
    }
    if(isSeparated) {
        return separatedAmount():
    }
    if(isRetired) {
        return retiredAmount();
    }
    return normalPayAmount();
}
Copy the code

Polymorphic substitution conditional expression

Sometimes we encounter if-else-if or switch-case structures, which are not only untidy, but also hard to understand when faced with complex logic. This situation can be modified by using object-oriented polymorphism.

For example, let’s say you’re developing a game and you need to write a way to get damage from Bartizan, Archer and Tank. It took two hours to complete the feature; The code after development is as follows:

int attackPower(Attacker attacker) {
    switch (attacker.getType()) {
        case "Bartizan":
            return 100;
        case "Archer":
            return 50;
        case "Tank":
            return 800;
    }
    throw new RuntimeException("can not support the method");
}
Copy the code

After the self-test, there is no problem, at this time your mood is very cool

When you submit your code to your boss for review, your boss will reply that the code implementation is not elegant enough and rewrite it

1. Enumerate polymorphism

I’m not happy with this reply, but I can’t help it because I still have to work here. Mouth or answer OK

You think about it for a while and you realize you can just do it with enumeration polymorphisms, so you write the next version

int attackPower(Attacker attacker) { return AttackerType.valueOf(attacker.getType()).getAttackPower(); } enum AttackerType {Override public int getAttackPower() {return 100; }}, Archer(" Archer ") {@override public int getAttackPower(); }}, Tank(" Tank ") {@override public int getAttackPower() {return 800; }}; private String label; Attacker(String label) { this.label = label; } public String getLabel() { return label; } public int getAttackPower() { throw new RuntimeException("Can not support the method"); }}Copy the code

This time, I submitted the review to the leader, and it passed smoothly. You thought you had got the point of the leader

2. Class polymorphism

Did not expect you are not happy for a few days, and received a new demand, the method of obtaining the attack need to be modified, according to the level of the attacker is different and the attack is different; Considering that the last version had a fixed value of attack power, using enumeration is still appropriate. However, this change will calculate attacks according to the attacker’s own level, so using enumeration estimation is not appropriate. At the same time, you also think that the last simple implementation was hated by the leader, this time if the implementation of the last enumeration version, it will not have a good result; Finally you decide to use class polymorphism to do it

int attackPower(Attacker attacker) { return attacker.getAttackPower(); } interface Attacker { default int getAttackPower() { throw new RuntimeException("Can not support the method"); } } class Bartizan implements Attacker { public int getAttackPower() { return 100 * getLevel(); } } class Archer implements Attacker { public int getAttackPower() { return 50 * getLevel(); } } class Tank implements Attacker { public int getAttackPower() { return 800 * getLevel(); }}Copy the code

After completion, I submitted it to the leader for review. The leader smiled and passed the code review.

3. Strategic mode

You thought this was the end of the game, but the plan failed to catch up with the change. After the launch of the game, the effect was not very good, and you received another demand change. The calculation of attack power should not be so rough, we need to configure rules in the background, so that the attack power of some participating players can be improved according to the rules.

You are very angry, thinking: never heard of killing programmers do not need to use a gun, change the requirements three times can be, MD is trying to kill me.

Angry angry, but dare not show it, who let you are the leader, then open to do

Considering the logic joined the rules, the rules themselves can design a simple, also can design is very complicated, if late rules become more complex, so the whole attack object classes can appear particularly bloated, scalability is not good, so you no longer use this kind of polymorphism, consider using the strategy pattern, after the completion of the code is as follows:

// Interface AttackPowerCalculator {Boolean support(Attacker); int calculate(Attacker attacker); The towers} / / damage calculation class BartizanAttackPowerCalculator implements AttackPowerCalculator {@ Override public Boolean support(Attacker attacker) { return "Bartizan".equals(attacker.getType()); } @override public int calculate(Attacker) {return doCalculate(getRule()); }} / / archer damage calculation class ArcherAttackPowerCalculator implements AttackPowerCalculator {@ Override public Boolean support(Attacker attacker) { return "Archer".equals(attacker.getType()); } @override public int calculate(Attacker) {return doCalculate(getRule()); }} / / tank damage calculation class TankAttackPowerCalculator implements AttackPowerCalculator {@ Override public Boolean support(Attacker attacker) { return "Tank".equals(attacker.getType()); } @override public int calculate(Attacker) {return doCalculate(getRule()); }} / / aggregate all class class AttackPowerCalculatorComposite implements AttackPowerCalculator {List < AttackPowerCalculator > calculators = new ArrayList<>(); public AttackPowerCalculatorComposite() { this.calculators.add(new TankAttackPowerCalculator()); this.calculators.add(new ArcherAttackPowerCalculator()); this.calculators.add(new BartizanAttackPowerCalculator()); } @Override public boolean support(Attacker attacker) { return true; } @Override public int calculate(Attacker attacker) { for (AttackPowerCalculator calculator : calculators) { if (calculator.support(attacker)) { return calculator.calculate(attacker); } } throw new RuntimeException("Can not support the method"); Int attackPower(Attacker) {AttackPowerCalculator = new} // The entry completes the computation by calling the aggregate class AttackPowerCalculatorComposite(); return calculator.calculate(attacker); }Copy the code

I submitted the code to the leader for review again. The leader was very satisfied with it and praised me. He said: “Boy, it’s good. You answer: Thank your leader for the recognition (thinking of course, after all, I’ve already figured out where your point is)

Feel this time you complete this function is still more satisfied, please click on the like to pay attention to the comments go oh

Introduction of assertions

The last operation to simplify conditional expressions is the introduction of assertions, which is simpler, and the Spring framework itself provides utility classes for assertions, such as this code:

public void getProjectLimit(String project){
    if(project == null){
        throw new RuntimeException("project can not null");
    }
    doSomething();
}

Copy the code

The code after adding Spring’s assertions

public void getProjectLimit(String project){
    Assert.notNull(project,"project can not null");
    doSomething();
}
Copy the code

Write at the end and thank you for your patience. Of course, there may be more or less deficiencies and mistakes in the article, suggestions or opinions are welcome to comment and exchange. Finally, I hope friends can click on the like comment to follow the three, because these are all the power source I share 🙏