My answer is: If of more than two else, or switch of more than two cases. However Code Is it normal to use if else and switch case in large quantities? Error! If else and switch case with more than two branches should not be hard-coded.
Where does a complex branch come from?
The first question we need to discuss is why there are often so many complicated branches in the legacy code. These complex branches often do not exist in the first version of the Code. If the designer is still somewhat experienced, he should anticipate where expansion may be required in the future, and reserved abstract interfaces.
However, after the code has been iterated by several versions, especially after several requirements and details are adjusted, the complex branch will appear. The detailed adjustment of requirements is often not reflected in UML, but directly reflected in code. For example, the original message is divided into two types: chat message and system message. This is designed as two sub-classes of message class. However, some of the system messages are important and their titles should be displayed in red. Program Members often make the following changes:
Add an important attribute to the system message class.
Add a branch about the important attribute to the render method to control the title Color.
Why do programmers make such changes? It is possible that he didn't realize that he should abstract it. Because the requirement is that "some of the system messages are important ",Programming Language For programmers who have trained a lot, he may first think of a flag, which can distinguish between important and unimportant. He did not expect this requirement to be explained in another way. "system messages are classified into two categories: Important and unimportant 」. In this way, he knows that the system message should be abstracted.
Of course, it is also possible that the programmer knows it can be abstract, but for some reason, he chooses not to do so. A common situation is that someone forces programmers to sacrifice code quality in exchange for project progress speed-adding an attribute and a branch is much simpler than abstract refactoring, if we want to make 10 modifications in this form, is it faster to make 10 branches or 10 abstractions? The difference is obvious.
Of course, if else is too large, some smart people will come up and say, "Let's change to switch case. In some cases, this can indeed improve code readability, assuming that each branch is mutually exclusive. However, when the number of switch cases increases, the Code becomes unreadable.
What are the disadvantages of complex branches?
What are the disadvantages of complex branches? Let me extract an example from the old Code of Baidu Hi web edition. Copy code The Code is as follows: Switch (JSON. Result ){
Case "OK ":
Switch (JSON. Command ){
Case "message ":
Case "systemmessage ":
If (JSON. content. From = ""
& JSON. content. content = "kicked "){
/* Disconnect */
} Else if (JSON. Command = "systemmessage"
| JSON. content. type = "sysmsg "){
/* Render system message */
} Else {
/* Render chat message */
}
Break;
}
Break;
This code is not difficult to understand, so I asked a simple question: Which of the following branches is hit by the JSON:Copy codeThe Code is as follows :{
"Result": "OK ",
"Command": "message ",
"Content ":{
"From": "catchen ",
"Content": "Hello! "
}
}
you can easily get the correct answer: This JSON hit/* render chat message */(display chat message) this branch. So how did you make this decision? First, you should check whether it hits case "OK": Branch, and the result is hit; then, you should check whether it hits case "message": Branch, and the result is hit, so case "systemmessage": You don't need to read it. Next, it does not hit the conditions in IF, and it does not hit the conditions in else if, therefore, it hits the else branch.
can you see the problem? Why can't you say this JSON hit this branch when you look at this else? Because else itself does not contain any conditions, it only implies conditions! The condition of each else is the result of the first non-back and operation on each of the previous if and else if statements. That is to say, determining whether to hit this else is equivalent to determining whether to hit such a complex set of conditions: copy Code the code is as follows :! (JSON. content. From = "" & JSON. content. content = "kicked ")&&! (JSON. Command = "systemmessage" | JSON. content. type = "sysmsg")
Put two switch cases on the outer layer. The condition for this branch is as follows:Copy codeThe Code is as follows: JSON. Result = "OK" & (JSON. Command = "message" | JSON. Command = "systemmessage ")&&! (JSON. content. From = "" & JSON. content. content = "kicked ")&&! (JSON. Command = "systemmessage" | JSON. content. type = "sysmsg ")
Here there is a duplicate logic, which is omitted in this way:Copy codeThe Code is as follows: JSON. Result = "OK" & JSON. Command = "message "&&! (JSON. content. From = "" & JSON. content. content = "kicked ")&&! (JSON. content. type = "sysmsg ")
how much effort have we made to derive such a long string of logical operation expressions from the four simple else letters? Besides, I can't really understand what this expression is about without looking at it carefully.
This is where it is difficult to read and manage complex branches. Imagine you have a switch case and an if else. There are 3 cases in total, and each case has 3 else, which is enough for your research-every branch, the results of the first-not-last-sum operation of all the front branches and the front branches of all the original branches are hidden in the condition.
how to avoid complex branches
first, complex logical operations cannot be avoided. The reconstruction result should be equivalent logic. All we can do is to make the code easier to read and manage. Therefore, we should focus on how to make complex logical operations easy to read and manage.
abstract as a class or factory
for people who are used to object-oriented design, this may mean that complex logical operations are dispersed and distributed to different classes: copy Code the code is as follows: Switch (JSON. result) {
case "OK":
var factory = commandfactories. getfactory (JSON. command);
var command = factory. buildcommand (JSON);
command.exe cute ();
break;
}
This looks good. At least the branch is shorter and the code is easy to read. This switch case only involves the branch of the status code. for how to deal with the "OK" status code, this is a matter of other class management. There may be a group of branches in getfactory, focusing on the selection of the factory to which this command should be created. At the same time, buildcommand may have other trivial branches that decide how to build this command.
the advantage of doing so is that the nested relationship between branches is removed, and each branch must be correct in its own context. For example, getfactory is now a named function. Therefore, the branch in this function only needs to implement the contract implied by the getfactory name. You do not need to pay attention to the context of actually calling getfactory.
abstracted as pattern matching
another approach is to repeat this complex logical operation as pattern matching: copy Code the code is as follows: network. listen ({
"result": "OK",
"command": "message",
"content": {"from ":"", "content": "kicked"}
}, function (JSON) {/* disconnect */});
network. listen ([{
"result": "OK",
"command": "message",
"content": {"type ": "sysmsg" }< BR >},{
"result": "OK",
"command": "systemmessage"
}], function (JSON) {/* Render system message */});
network. listen ({
"result": "OK",
"command": "message",
"content": {"from $ ne ": "", "Type $ ne": "sysmsg"}
}, func tion (JSON) {/* render chat message */});
is this much clearer now? The first case is to be kicked off and must match the specified from and content values. The second case is to display system messages. Because the system messages are slightly different in the two versions of the protocol, we need to capture two different JSON messages. Matching any one of them is a hit. The third scenario is to display chat messages. in earlier versions, both system messages and offline commands belong to special chat messages. To be compatible with earlier versions, the two cases need to be excluded from the display chat message, so the suffix "$ ne" (not equal) is used for matching.
because the listen method is context-independent, each listen independently declares what JSON they match, so there is no implicit logic. For example, to capture chat messages, you must explicitly declare and exclude the from = "" And type = "sysmsg" situations, this does not need to be inferred from the context if Else.
pattern matching greatly improves code readability and maintainability. Because we want to capture JSON, we use JSON to describe what each branch needs to capture, which is much clearer than a long logic expression. At the same time, every modification in this JSON is independent. modifying a condition does not affect other conditions.
finally, how to compile a pattern matching module is beyond the scope of this article.