if… Else is a must for all high-level programming languages, but real code often has too much if… The else.
Although the if… Else is required, but abuse if… Else can do a lot of damage to the readability and maintainability of your code, and thus to the entire software system.
There are a lot of new technologies and concepts in software development, but if… Else the basic form of the program hasn’t changed much. Use good if… Else is of great significance not only for the present but also for the future.
Today we’re going to look at how to “kill” if in code… Else, return the code just to be clean.
If… Too much else
1. Problem performance
if… Else Excessive code can be abstracted into the following code.
Only five logical branches are listed, but in practice you can see 10, 20, or more logical branches in a method. In addition, the if… Too much else is usually associated with two other problems: complex logical expressions and if… Else Too deeply nested. The latter two questions will be introduced in the following two sections.
This section begins with the discussion of if… Else too much.
if (condition1) {
} else if (condition2) {
} else if (condition3) {
} else if (condition4) {
} else {
}Copy the code
Usually, the if… Else Too many methods, usually not readable and extensible.
From a software design perspective, there is too much if… Else usually means that the code violates the single responsibility principle and the open close principle.
In real projects, requirements are constantly changing and new requirements are constantly emerging. Therefore, software system scalability is very important. And solve the if… The most important aspect of the else overload problem is to make your code more extensible.
2. How to solve it
Let’s see how to solve if… Else too much problem. Here are some solutions.
- Table driven
- Chain of Responsibility model
- Annotation driven
- event-driven
- Finite state machine
- Optional
- Assert
- polymorphism
Method 1: table driver
introduce
Fixed if… for logical expression patterns. Else code, which uses some kind of mapping to represent logical expressions as tables; Then use the table lookup method to find the corresponding processing function of an input, and use this processing function to perform operations.
Applicable scenario
Logical expression pattern fixed if… else
Implementation and Examples
if (param.equals(value1)) {
doAction1(someParams);
} else if (param.equals(value2)) {
doAction2(someParams);
} else if (param.equals(value3)) {
doAction3(someParams);
}
// ...Copy the code
Reconfigurable for
Map<? , Function<? > action> actionMappings = new HashMap<>(); // Where is the generic? // When init actionMappings. Put (value1, (someParams) -> {doAction1(someParams)}); actionMappings.put(value2, (someParams) -> { doAction2(someParams)}); actionMappings.put(value3, (someParams) -> { doAction3(someParams)}); ActionMappings. Get (param). Apply (someParams);Copy the code
The above example uses Lambda and Functional Interface for Java 8, which is not covered here.
Table mappings can be centralized or decentralized, with each processing class registering itself. It can also be expressed in a configuration file. In short, there are many forms.
There are also some problems where the conditional expression is not as simple as in the above example, but with a little variation, table driven can also be used. Here is an example of tax calculation from Programming Abas:
If income <= 2200 TAX = 0 else if income <= 2700 tax = 0.14 * (income-2200) else if income <= 3200 tax = 70 + 0.15 * (income-2700) else if income <= 3700 tax = 145 + 0.16 * (income-3200)...... Else tax = 53090 + 0.7 * (income - 102200)Copy the code
For the above code, in fact, only need to extract the tax calculation formula, extract the standard of each gear to a table, plus a cycle. I won’t give you the refactored code, but you can figure it out.
Method two: responsibility chain model
introduce
When the if… Conditional expressions in the else are flexible, and when the data in the condition cannot be abstracted into a table and judged in a uniform way, the judgment of the condition should be given to each functional component. These components are connected in chain form to form a complete function.
Applicable scenario
Conditional expressions are flexible and changeable, and there is no unified form.
Implementation and Examples
The pattern of responsibility chain can be seen in the implementation of Filter and Interceptor functions of open source framework. Here are some common usage patterns:
Refactoring:
public void handle(request) { if (handlerA.canHandle(request)) { handlerA.handleRequest(request); } else if (handlerB.canHandle(request)) { handlerB.handleRequest(request); } else if (handlerC.canHandle(request)) { handlerC.handleRequest(request); }}Copy the code
After the refactoring:
public void handle(request) {
handlerA.handleRequest(request);
}
public abstract class Handler {
protected Handler next;
public abstract void handleRequest(Request request);
public void setNext(Handler next) { this.next = next; }
}
public class HandlerA extends Handler {
public void handleRequest(Request request) {
if (canHandle(request)) doHandle(request);
else if (next != null) next.handleRequest(request);
}
}Copy the code
Of course, the pre-refactoring code in the example does some class and method extraction refactoring for clarity. In reality, it’s more of a tiled code implementation.
Note: Control mode of responsibility chain
There will be some different forms of responsibility chain mode in the concrete implementation process. From the point of view of chain call control, it can be divided into external control and internal control.
External controls are not flexible, but they reduce the difficulty of implementation. An implementation at one point in the chain of responsibility does not have to consider calls to the next because external control is unified. But normal external controls don’t make nested calls either. If you have nested calls and you want to have external control over the invocation of the chain of responsibilities, it can be a little more complicated to implement. For details, see the Spring Web Interceptor mechanism.
Internal control is more flexible, with the implementation deciding whether the next link in the chain needs to be called. But if the invocation control pattern is fixed, such an implementation is inconvenient for consumers.
There are many variations in design patterns that require flexibility.
Method 3: Annotation-driven
introduce
Define the conditions under which a method is executed through Java annotations (or similar mechanisms in other languages). At program execution, the method is decided whether to call it by comparing the conditions defined in the participating annotations to whether they match. Concrete implementation can be achieved by table drive or chain of responsibility.
Applicable scenario
It is suitable for scenarios with many branches and high requirements for program expansibility and ease of use. Usually a core function in a system that is constantly encountering new requirements.
Implementation and Examples
You can see the use of this pattern in many frameworks, such as the common Spring MVC. Because these frameworks are so common, and demos are so ubiquitous, I’m not going to cover the demo code here.
The focus of this pattern is implementation. Existing frameworks are designed to implement domain-specific functionality, such as MVC. Therefore, if the business system adopts this mode, it needs to realize relevant core functions by itself. It mainly involves techniques such as reflection, chain of responsibility, etc. I’m not going to show you how to do that.
Method 4: event driven
introduce
By correlating different event types and corresponding processing mechanisms, complex logic can be realized and decoupled at the same time.
Applicable scenario
In theory, event-driven can be considered a type of table driver, but in practice, event-driven differs from the table driver mentioned earlier in several ways. To be specific:
Table drivers are usually one-to-one; Event-driven is usually one-to-many;
In table drivers, triggering and execution are usually strongly dependent; In event driven, triggering and execution are weakly dependent
It is the difference between the two that leads to the difference of the two applicable scenarios. Specifically, event-driven can be used to trigger functions such as inventory, logistics, credits and so on, such as order payment completion.
Implementation and Examples
In terms of implementation, standalone practice drivers can be implemented by Guava, Spring and other frameworks. Distributed messages are generally implemented through various message queues. But because the main discussion here is about eliminating if… Else, so it’s mainly for the standalone problem domain. Because of the technical implications, this pattern code is not demonstrated.
Method 5: Finite state machines
introduce
Finite-state machines are often referred to as state machines (the concept of an infinite state machine can be ignored). Here’s a definition from Wikipedia:
A finite-state machine (FSM), or FSM for short, is a mathematical model that represents a finite number of states and the transfer and action between these states.
In fact, the state machine can also be regarded as a table driven, in fact, the combination of current state and events and a corresponding relationship between the handler. Of course, there is also a state transition after a successful processing.
Applicable scenario
Just because there is an emphasis on statelessness in Internet back-end services doesn’t mean you can’t use a state machine design. In fact, in many scenarios, such as protocol stack, order processing functions, the state machine has its natural advantages. Because there is a natural flow of state and state in these scenarios.
Implementation and Examples
To implement the state machine design, a corresponding framework is required, which needs to implement at least one state machine definition function, as well as the call routing function for. State machine definitions can be DSL or annotated. The principle is not complex, master annotations, reflection and other functions of the students should be easy to achieve.
Reference Technology:
-
The Apache Mina Framework, while lagging behind Netty in the IO framework realm, provides the functionality of a State Machine. Mina.apache.org/mina-projec…
-
Spring State Machine Spring component is numerous, one does not show hill not roric State Machine frame, the Spring State Machine projects. Spring. IO/Spring – stat… DSL and annotations are defined in two ways.
The above framework only serves as a reference. If specific projects are involved, the core functions of the state machine need to be realized according to the business characteristics.
Optional
introduce
Part of Java code if… Else is caused by a non-null check. Therefore, lowering the if… Else can also lower the overall if… Number of else’s.
Java introduced the Optional class beginning in 8 to represent objects that might be empty. This class provides a number of methods for related operations that can be used to eliminate if… The else. The open source framework Guava and the Scala language provide similar functionality.
Usage scenarios
There are more if… The else.
Implementation and Examples
Traditional writing:
String str = "Hello World!" ; if (str ! = null) { System.out.println(str); } else { System.out.println("Null"); }Copy the code
After using Optional:
Optional<String> strOptional = Optional.of("Hello World!" ); strOptional.ifPresentOrElse(System.out::println, () -> System.out.println("Null"));Copy the code
There are a lot of Optional methods that I’m not going to cover here. But be careful not to use the get() and isPresent() methods, which would be different from the traditional if… The else.
Extension: Kotlin Null Safety
Kotlin has a feature called Null Safety:
bob? .department?.head?.name
For a chained call, in Kotlin you can call? Avoid null pointer exceptions. If one of the rings is null, the entire chain expression is null.
Method 7: Assert mode
introduce
The previous approach is suitable for resolving if… Else, a similar scenario with all kinds of parameter validation, like the string is not empty and so on. Many framework libraries, such as Spring and Apache Commons, provide tools to implement this common functionality. So you don’t have to write your own if… The else.
Validate class in the Apache Commons Lang: commons.apache.org/proper/comm…
Spring Assert class: docs. Spring. IO /spring-fram…
Usage scenarios
Usually used for checking various parameters
Extension: Bean Validation
Similar to the previous method, I introduce Assert mode and introduce Bean Validation, a technique that has a similar effect. Bean Validation is one of the Java EE specifications. Bean Validation defines Validation criteria in the form of annotations on Java beans and then validates them uniformly through the framework. Can also play a role in reducing if… Else.
Method 8: Polymorphism
introduce
Using object-oriented polymorphism can also play a role in eliminating if… Else. In this book code refactoring, have introduced: refactoring.com/catalog/rep…
Usage scenarios
The example given in the link is too simple to show that polymorphic elimination of if… Else specific scenario. In general, when multiple methods in a class have an if… If (else); if (else); The else.
Also, using polymorphism does not completely eliminate if… The else. But will the if… Else merge moves to the object creation phase. If.. in the creation phase , we can use the method described earlier.
** 3, summary **
This section introduces if… Else problems caused by too much, and corresponding solutions. In addition to the methods described in this section, there are several other methods.
The book Refactoring and Patterns, for example, introduces three methods: “Replacing conditional logic with Strategy,” “replacing state-changing conditional statements with State,” and “replacing conditional schedulers with Command.” The “Command pattern” is basically the same idea as the “table-driven” approach in this article.
The other two methods are not repeated here because they are explained in detail in the book Refactoring and Patterns.
When to use which method depends on the type of problem you are facing. Some of the scenarios described above are just suggestions, more for developers to think about themselves.
If… Else Too deeply nested
1. Problem performance
if… Else is usually not the most serious problem. Some code if… Else if… Else is deeply nested and complex, making the code very unreadable and difficult to maintain.
if (condition1) { action1(); if (condition2) { action2(); if (condition3) { action3(); if (condition4) { action4(); }}}}Copy the code
if… Else nesting too deeply can seriously affect the readability of your code. Of course, there are also the two problems mentioned in the previous section.
2. How to solve it
The methods described in the previous section can also be used to solve the problems in this section, so the above methods will not be repeated in this section. This section highlights some methods that do not reduce if… Else number, but improves code readability:
- Extraction method
- Who’s statement
Method 1: extraction method
introduce
The extraction method is a means of code refactoring. Definition is easy to understand; it is the extraction of a piece of code into a separate defined method. Borrow refactoring.com/catalog/ext… Definition in:
Applicable scenario
if… Else Heavily nested code, usually poorly readable. Therefore, small adjustments should be made to improve the readability of the code before large refactoring. The extraction method is the most commonly used adjustment method.
Implementation and Examples
Refactoring:
public void add(Object element) {
if (!readOnly) {
int newSize = size + 1;
if (newSize > elements.length) {
Object[] newElements = new Object[elements.length + 10];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
elements = newElements
}
elements[size++] = element;
}
}Copy the code
After the refactoring:
public void add(Object element) {
if (!readOnly) {
int newSize = size + 1;
if (newSize > elements.length) {
Object[] newElements = new Object[elements.length + 10];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
elements = newElements
}
elements[size++] = element;
}
}Copy the code
Method two: wei statement
introduce
In code refactoring, a method called “use who statement to replace nested conditional statement” refactoring.com/catalog/rep…
Look directly at the code:
public void add(Object element) {
if (!readOnly) {
int newSize = size + 1;
if (newSize > elements.length) {
Object[] newElements = new Object[elements.length + 10];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
elements = newElements
}
elements[size++] = element;
}
}Copy the code
After the reconstruction
double getPayAmount() {
if (_isDead) return deadAmount();
if (_isSeparated) return separatedAmount();
if (_isRetired) return retiredAmount();
return normalPayAmount();
}Copy the code
Usage scenarios
When you see a method in which a block of code is blocked by an if… Else full control is usually done with a guard statement.
If… Else expressions are too complex
1. Problem performance
if… The third problem caused by else comes from overly complex conditional expressions. Condition 1, condition 2, condition 3, and condition 4 are true and false, respectively. Condition 1, condition 2, condition 3, and condition 4 are true and false.
if ((condition1 && condition2 ) || ((condition2 || condition3) && condition4)) {
}Copy the code
I don’t think anyone wants to do that. The question is, what does this whole pile of expressions mean? The point is that when you don’t know what an expression means, no one wants to infer its results.
So, complex expressions are not necessarily wrong. But it’s bad that the expression is hard to understand.
2. How to solve it
For the if… Else expression complex problem, mainly by code refactoring extraction method, move method and other means to solve. Because these methods are covered in Code Refactoring, they won’t be repeated here.
conclusion
This article introduces 10 ways (12 including extensions) to eliminate and simplify if… Else method. There are also ways to eliminate if… through policy patterns, state patterns, etc. Else is also covered in refactoring and Patterns.
As the preface says, if… Else is an important part of the code, but excessive and unnecessary use of if… Else can negatively affect the readability and extensibility of the code, which in turn affects the entire software system.
“Kill” the if… Else ability reflects the programmer’s comprehensive application ability of software reconstruction, design pattern, object-oriented design, architecture pattern, data structure and other technologies, and reflects the programmer’s internal work. Use if… wisely. Else, you can’t have no design, you can’t have over design.
The comprehensive and reasonable use of these technologies requires programmers to constantly explore and summarize in their work.
Original link: www.cnblogs.com/eric-shao/p… Wenyuan network, only for the use of learning, such as infringement, contact deletion.
I have collected quality technical articles and experience summary in my public account “Java Circle”.
In order to facilitate your learning, I have compiled a set of learning materials, covering Java virtual machine, Spring framework, Java threads, data structures, design patterns and so on, free for students who love Java! More learning communication group, more communication problems can be faster progress ~