This article was first published on Our official account :mp.weixin.qq.com/s?__biz=MzU…

version The date of note
1.0 2019.1.18 The article first
1.1 2021.5.21 Improve the punctuation of headings

In everyday code writing,if… Else statements are extremely common. Because of its commonness, many students write code without thinking about its use in the current code. And as the project grew, the bad if… Else statements would be everywhere, making the project dramatically less maintainable. So in this article, I’d like to talk about how to avoid writing a bad if… The else statement.

As a result of secret and other reasons. The sample code in this article will use open-source code or abstracted production code as examples.

The problem code

When we look at a set of if… Else, there’s usually no reading burden. But when we see code like this:

    private void validate(APICreateSchedulerMessage msg) {
        if (msg.getType().equals("simple")) {
            if (msg.getInterval() == null) {
                if(msg.getRepeatCount() ! =null) {
                    if(msg.getRepeatCount() ! =1) {
                        throw new ApiMessageInterceptionException(argerr("interval must be set when use simple scheduler when repeat more than once")); }}else {
                    throw new ApiMessageInterceptionException(argerr("interval must be set when use simple scheduler when repeat forever")); }}else if(msg.getInterval() ! =null) {
                if(msg.getRepeatCount() ! =null) {
                    if (msg.getInterval() <= 0) {
                        throw new ApiMessageInterceptionException(argerr("interval must be positive integer"));
                    } else if ((long) msg.getInterval() * (long) msg.getRepeatCount() * 1000L + msg.getStartTime() < 0 ) {
                        throw new ApiMessageInterceptionException(argerr("duration time out of range"));
                    } else if ((long) msg.getInterval() * (long) msg.getRepeatCount() * 1000L + msg.getStartTime() > 2147454847000L) {
                        throw new ApiMessageInterceptionException(argerr("stopTime out of mysql timestamp range")); }}}if (msg.getStartTime() == null) {
                throw new ApiMessageInterceptionException(argerr("startTime must be set when use simple scheduler"));
            } else if(msg.getStartTime() ! =null && msg.getStartTime() < 0) {
                throw new ApiMessageInterceptionException(argerr("startTime must be positive integer or 0"));
            } else if(msg.getStartTime() ! =null && msg.getStartTime() > 2147454847) {// mysql timestamp range is '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' UTC.
                // we accept 0 as startDate means start from current time
                throw new ApiMessageInterceptionException(argerr("startTime out of range"));
            }

            if(msg.getRepeatCount() ! =null && msg.getRepeatCount() <= 0) {
                throw new ApiMessageInterceptionException(argerr("repeatCount must be positive integer")); }}if (msg.getType().equals("cron")) {
            if (msg.getCron() == null|| ( msg.getCron() ! =null && msg.getCron().isEmpty())) {
                throw new ApiMessageInterceptionException(argerr("cron must be set when use cron scheduler"));
            }
            if((! msg.getCron().contains("?")) || msg.getCron().split("").length ! =6) {
                throw new ApiMessageInterceptionException(argerr("cron task must follow format like this : \"0 0/3 17-23 * * ? \ ""));
            }
            if(msg.getInterval() ! =null|| msg.getRepeatCount() ! =null|| msg.getStartTime() ! =null) {
                throw new ApiMessageInterceptionException(argerr("cron scheduler only need to specify cron task")); }}}Copy the code

Or code like this:

try {
   for (int j = myConfig.getContentStartNum(); j <= rowNum; j++) {
        row = sheet.getRow(j);
        T obj = target.newInstance();
        for (int i = 0; i < colNum; i++) {

            Field colField = ExcelUtil.getOneByTitle(metaList, titleList[i]);
            colField.setAccessible(true);
            String fieldType = colField.getType().getSimpleName();
            HSSFCell cell = row.getCell(i);
            int cellType = cell.getCellType();
            System.out.println(colField.getName()+"|"+fieldType+"|"+cellType);

            if(HSSFCell.CELL_TYPE_STRING == cellType){
                if("Date".equals(fieldType)){
                    colField.set(obj, DateUtil.parse(cell.getStringCellValue()));
                }else{ colField.set(obj, cell.getStringCellValue()); }}else if(HSSFCell.CELL_TYPE_BLANK == cellType){
                System.out.println("fieldName"+colField.getName());
                if("Boolean".equals(fieldType)){
                    colField.set(obj, cell.getBooleanCellValue());
                }else{
                    colField.set(obj, ""); }}else if(HSSFCell.CELL_TYPE_NUMERIC == cellType){
                if("Integer".equals(fieldType) || "int".equals(fieldType)){
                    colField.set(obj, (int)cell.getNumericCellValue());
                }else{ colField.set(obj, cell.getNumericCellValue()); }}else if(HSSFCell.CELL_TYPE_BOOLEAN == cellType){ colField.set(obj, cell.getBooleanCellValue()); } } result.add(obj); }}catch (InstantiationException | IllegalAccessException | ParseException e) {
    e.printStackTrace();
}
Copy the code

After looking at these two pieces of code, I believe you and I are in the same mood:

The burden of reading them is too great — we have to memorize several branches of logical judgment to know exactly what the case is to get that result. Not to mention how expensive it is to maintain, you have to read it every time you maintain it, and then make changes based on that. Over time, our code becomes “arrow code”.

/ /... / /... / /... / /... / /... / /... / /... / /... / /... / /...Copy the code

Objectives and key indicators

As mentioned, our goal is to reduce bad if… Else code. So what is bad if… Else code? We can briefly sum it up:

  • Double or more nesting
  • There are multiple judgment conditions for a logical branch, such as:A && B || CSo this is — this is actually kind of a nesting of variations

As you can see, our key metric is to reduce nesting.

Common Tips

1. Ternary expressions

Ternary expressions are also common in code and can simplify if… Else, such as:

    public Object getFromOpaque(String key) {
        return opaque == null ? null : opaque.get(key);
    }
Copy the code

Why do you say some? Therefore, a ternary expression must have a return value.

You can’t use a ternary expression in this case

    public void putToOpaque(String key, Object value) {
        if (opaque == null) {
            opaque = new LinkedHashMap();
        }
        opaque.put(key, value);
    }
Copy the code

2. switch case

In Java, the switch can focus on a variable (byte short int or char, String supported since Java7) and then check for a match in each case and enter the branch if so.

In general,switch case is more readable than if… Else would be better. Because if can put complex expressions, switch can’t. Having said that, nesting can still be gross.

Therefore, if you only use String values for byte,short,int, and char, switch is preferred.

3. Turn around

   /* Find the list of students older than 18 and male */
    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 {
                    throw new MyException("Failed to get student list"); }}else {
                throw new MyException("Failed to obtain teacher information"); }}else {
            throw new MyException("Failed to obtain student information");
        }
        return result;
    }
Copy the code

In this case, we should throw an exception (or return) in time to ensure normal flow in the outer layer, such as:

   /* Find the list of students older than 18 and male */
    public ArrayList<Student> getStudents(int uid){
        ArrayList<Student> result = new ArrayList<Student>();
        Student stu = getStudentByUid(uid);
        if (stu == null) {
             throw new MyException("Failed to obtain student information");
        }
 
        Teacher teacher = stu.getTeacher();
        if(teacher == null) {throw new MyException("Failed to obtain teacher information");
        }
 
        ArrayList<Student> students = teacher.getStudents();
        if(students == null) {throw new MyException("Failed to get student list");
        }
 
        for(Student student : students){
            if(student.getAge() > 18&& student.getGender() == MALE){ result.add(student); }}return result;
    }
Copy the code

Using design patterns

In addition to the tips above, we can also use design patterns to avoid writing bad if… Else statement. In this section, we will mention the following design patterns:

  1. The State model
  2. Mediator pattern
  3. The Observer pattern
  4. The Strategy pattern

1. The State model

In code, we often judge the state of some business object to determine what it should do under the current call. For example, we now have a bank interface:

public interface Bank {
    /** * The bank is locked ** /
    void lock(a);
    /** * bank unlock ** /
    void unlock(a);
    /** * alarm ** /
    void doAlarm(a);
}
Copy the code

Let’s take a look at its implementation class

public class BankImpl implements Bank {
    @Override
    public void lock(a) {
        // Save this record
    }

    @Override
    public void unlock(a) {
        if ((BankState.Day == getCurrentState())) {
            // Daytime unlock normal
            // Save only this record
        } else if (BankState.Night == getCurrentState()) {
            // Unlock at night, there may be a problem
            // Save this record and call the policedoAlarm(); }}@Override
    public void doAlarm(a) {
        if ((BankState.Day == getCurrentState())) {
            // Call the police during the day, contact the local police and keep this record
        } else if (BankState.Night == getCurrentState()) {
            // Call the police at night, there may be an accident, not only contact the local police, but also coordinate with nearby security personnel and keep this record}}private BankState getCurrentState(a) {
        returnBankState.Day; }}Copy the code

Obviously, we’re talking about a state:

public enum BankState {
    Day,
    Night
}
Copy the code

In different states, banks may react differently to the same event. This is obviously frustrating, because in a real business scenario, there might be more than two states of a business. For each of these, write an additional if… Else. So, if you follow state mode, you can refactor like this:

public class BankDayImpl implements Bank {
    @Override
    public void lock(a) {
        // Save this record
    }

    @Override
    public void unlock(a) {
        // Daytime unlock normal
        // Save only this record

    }

    @Override
    public void doAlarm(a) {
        // Call the police during the day, contact the local police and keep this record}}Copy the code
public class BankNightImpl implements Bank {
    @Override
    public void lock(a) {
        // Save this record
    }

    @Override
    public void unlock(a) {
        // Unlock at night, there may be a problem
        // Save this record and call the police
        doAlarm();
    }

    @Override
    public void doAlarm(a) {
        // Call the police at night, there may be an accident, not only contact the local police, but also coordinate with nearby security personnel and keep this record}}Copy the code

2. The Mediator pattern

The code in the first section of this article is actually code somewhere in ZStack version 2.0.5, which is used to prevent the user from passing in improper parameters to the Cli and causing the following logic to run improperly. To make it easier to understand, we can simplify the rules and draw a picture for you to understand.

Let’s say this is an “ancient” interface for submitting a scheduled VM restart task (because a good interaction designer would never design an interface like this…). The rules are as follows:

2.1 Scheduler of Simple Type

Simple types of Scheduler, can according to the Interval, RepeatCount, StartTime to customize a task.

2.1.1 when selectingSimpleType of task,Interval.StartTimeThese two parameters are mandatory

2.1.2 when fill outInterval, andStartTimeAt this point, the scheduled task can be submitted

2.1.3 RepeatCountIs an optional parameter

2.2 CrON-type Scheduler

A Scheduler of Cron type that can submit tasks based on Cron expressions.

2.2.1 After the CRON expression is filled in, scheduled tasks can be submitted at this time

So here’s a question for you to think about: If you wanted to write an interface like this, how would you write it? In a Windows class, determine the type of the optional bar above, and then determine whether the submit button is lit based on whether the value in the text box is filled in… That’s kind of the basic logic. There is no mention of checking the boundary values above – these are often scattered among the component instances and communicate with each other to determine what changes they should make. Else code.

2.3 Use arbitrators to improve it

Next, we’ll post some pseudocode to help readers better understand this design pattern

/** * Arbiter's member interface ** /
public interface Colleague {
    /** * Sets the member's arbiter ** /
    void setMediator(Mediator mediator);

    /** * sets whether members are enabled ** /
    void setColleagueEnabled(boolean enabled);
}
Copy the code
/** * arbiter interface ** /
public interface Mediator {
    /** * This method is called when a member's status changes
    void colllectValueChanged(String value);
}
Copy the code
/** * Components that contain textField should implement the interface */
public interface TextField {
    String getText(a);
}
Copy the code
/** * ValueListener receives notification when a component's value changes ** /
public interface ValueListener {
    /** * This interface is called ** / when the member value changes
    void valueChanged(String str);
}

Copy the code

With a few interfaces defined, we start writing concrete classes:

CheckBox used to represent Simple and Cron

public class CheckBox {
    private boolean state;

    public boolean isState(a) {
        return state;
    }

    public void setState(boolean state) {
        this.state = state; }}Copy the code

Button

public class ColleagueButtonField implements Colleague.ValueListener {
    private Mediator mediator;

    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    @Override
    public void setColleagueEnabled(boolean enabled) {
        setEnable(enabled);
    }

    private void setEnable(boolean enable) {
        // Remove the underline when true and allow it to be pressed
    }

    @Override
    public void valueChanged(String str) { mediator.colllectValueChanged(str); }}Copy the code

And a couple of texts

public class ColleagueTextField implements Colleague.ValueListener.TextField {
    private Mediator mediator;
    private String text;

    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    @Override
    public void setColleagueEnabled(boolean enabled) {
        setEnable(enabled);
    }

    private void setEnable(boolean enable) {
        // Remove the underscore when true and allow the value input
    }

    @Override
    public void valueChanged(String str) {
        mediator.colllectValueChanged(str);
    }

    @Override
    public String getText(a) {
        returntext; }}Copy the code

The implementation of the SchedulerValidator SchedulerValidatorImpl is not posted, it is just some validation logic.

Next comes our main class, the window class that knows the global state

public class MainWindows implements Mediator {
    private SchedulerValidator validator = new SchedulerValidatorImpl();
    ColleagueButtonField submitButton, cancelButton;
    ColleagueTextField intervalText, repeatCountText, startTimeText, cronText;
    CheckBox simpleCheckBox, cronCheckBox;


    public void main(a) {
        createColleagues();
    }

    /** * This method is called when a member state changes * the component is initialized with true */
    @Override
    public void colllectValueChanged(String str) {
        if (simpleCheckBox.isState()) {
            cronText.setColleagueEnabled(false);
            simpleChanged();
        } else if (cronCheckBox.isState()) {
            intervalText.setColleagueEnabled(false);
            repeatCountText.setColleagueEnabled(false);
            startTimeText.setColleagueEnabled(false);
            cronChanged();
        } else {
            submitButton.setColleagueEnabled(false);
            intervalText.setColleagueEnabled(false);
            repeatCountText.setColleagueEnabled(false);
            startTimeText.setColleagueEnabled(false);
            cronText.setColleagueEnabled(false); }}private void cronChanged(a) {
        if(! validator.validateCronExpress(cronText.getText())) { submitButton.setColleagueEnabled(false); }}private void simpleChanged(a) {
        if(! validator.validateIntervalBoundary(intervalText.getText()) || ! validator.validateRepeatCountBoundary(repeatCountText.getText()) || ! validator.validateStartTime(startTimeText.getText())) { submitButton.setColleagueEnabled(false); }}private void createColleagues(a) {
        submitButton = new ColleagueButtonField();
        submitButton.setMediator(this);
        cancelButton = new ColleagueButtonField();
        cancelButton.setMediator(this);

        intervalText = new ColleagueTextField();
        intervalText.setMediator(this);
        repeatCountText = new ColleagueTextField();
        repeatCountText.setMediator(this);
        startTimeText = new ColleagueTextField();
        startTimeText.setMediator(this);
        cronText = new ColleagueTextField();
        cronText.setMediator(this);

        simpleCheckBox = new CheckBox();
        cronCheckBox = newCheckBox(); }}Copy the code

In this design pattern, all the judgment of the instance state is left to the discretionarbiterThis instance to judge, rather than to communicate with each other. In the current scenario, the number of instances involved is not very large, but in a complex system, the number of instances involved can become very large. Suppose there are two instances A and B, then there are two lines of communication:

With A,B, and C, there are six lines

  • When there are 4 instances, there will be 12 lines of communication
  • When there are five instances, there are 20 lines of communication
  • And so on…

This is where the benefits of the arbitrator pattern come into play — the code becomes difficult to maintain if the logic is scattered across roles.

3. The Observer pattern

ZStack source code analysis of the design pattern appreciation – troika

In keeping with the theme of this article, the observer pattern does more than just change the if… Else split into its own module. In the case of the ZStack, when the main storage is reconnected, the main storage module may have to tell modules A and B to do something. If the observer mode is not used, the code will be coupled to the main storage module. Else is unlikely.

Improve the previous arbitrator example

The Observer pattern generally communicates in an event-driven manner, so the Observer and Subject are generally loosely coupled — the consumer is not specified when the Subject issues notifications. In the previous example of the mediator pattern, there is a tight coupling between the mediator and the members (that is, they must be aware of each other), so the observer pattern can be considered to improve it.

4. The Strategy pattern

Often in programming, algorithms (policies) are written in concrete methods, which results in the concrete methods being riddled with conditional statements. But Strategy deliberately decouple the algorithm from the rest, defining only the interface and then using the algorithm in a delegate manner. This, however, makes the program more loosely coupled (because you can easily replace the whole algorithm with a delegate), making the whole project stronger.

ZStack source code analysis of the design pattern appreciation – strategy pattern

summary

In this article, the author shares with you several ways to reduce if… Else tips, since these tips have limitations, here are a few ways to avoid writing a bad if… Else design pattern, and use observer pattern to simply improve the arbitrator pattern example.