Home  >  Article  >  Web Front-end  >  Let’s talk about the usage of various complex branch statements in JavaScript

Let’s talk about the usage of various complex branch statements in JavaScript

伊谢尔伦
伊谢尔伦Original
2017-07-18 15:04:251605browse

Where do complex branches come from

First of all, the first question we want to discuss is why there are often so many complex branches in legacy code. These complex branches often do not exist in the first version of the code. Assuming that the designer still has some experience, he should foresee areas that may need to be expanded in the future and reserve abstract interfaces.

But after the code goes through several iterations, especially after several adjustments to the requirements details, complex branches will appear. Detailed adjustments to requirements are often not reflected in UML, but directly in code. For example, messages were originally divided into two categories: chat messages and system messages. During design, these were naturally designed as two subcategories of the message category. But then one day the requirements are adjusted in detail. Some of the system messages are important and their titles should be displayed in red. At this time, programmers often make the following modifications:
Add an important attribute to the system message class
Add a branch about the important attribute to the corresponding render method to control the title color
Why would the programmer make such a modification? Maybe it's because he didn't realize it should be abstract. Because the requirement says "some system messages are important", for programmers who have received more training in imperative programming languages, the first thing they may think of is the flag bit - a flag bit can distinguish between important and non-important ones. important. He did not expect that this requirement could be interpreted in another way, "System messages are divided into two categories: important and unimportant." Interpreting it this way, he knew that the system messages should be abstracted.
Of course it is also possible that the programmer knows that abstraction is possible, but for some reason, he chooses not to do so. A very common situation is that someone forces programmers to sacrifice code quality in exchange for project progress-adding a property and a branch is much simpler than abstract refactoring. If you want to do 10 of this form Modify, is it faster to make 10 branches or 10 abstractions? The difference is obvious.
Of course, if there are too many if elses, some smart people will stand up and say, "Why don't we change it to switch case". In some cases, this can indeed improve code readability, assuming each branch is mutually exclusive. But when the number of switch cases increases, the code will also become unreadable.
What are the disadvantages of complex branches
What are the disadvantages of complex branches? Let me take a section from the old code of Baidu Hi web version as an example.

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;

It is not difficult to understand this code, so I ask a simple question, which branch does the following JSON hit:

{ 
"result": "ok", 
"command": "message", 
"content": { 
"from": "CatChen", 
"content": "Hello!" 
} 
}

You can easily get the correct answer: this JSON hits / * render chat message */ (display chat message) this branch. So I want to know how you made this judgment? First, you have to see if it hits the case "ok": branch, and the result is a hit; then, you have to see if it hits the case "message": branch, and the result is also a hit, so there is no need to look at the case "systemmessage": ; Next, it does not hit the condition in if; and it does not hit the condition in else if, so it hits the else branch.
Do you see the problem? Why can't you look at this else and just say this JSON hits this branch? Because else itself does not contain any condition, it only implies the condition! The condition of each else is the result of the negation and then AND operation of each if and else if before it. In other words, judging the hit of this else is equivalent to a set of complex conditions such as judging the hit:

!(json.content.from == "" && json.content.content == "kicked") && !(json.command == "systemmessage" || json.content.type == "sysmsg")

Then apply the two outer switch cases, and the conditions of this branch are like this:

json.result == "ok" && (json.command == "message" || json.command == "systemmessage") && !(json.content.from == "" && json.content.content == "kicked")
 && !(json.command == "systemmessage" || json.content.type == "sysmsg")

There is repetitive logic in it. After omitting it, it looks like this:

json.result == "ok" && json.command == "message" && !(json.content.from == "" && json.content.content == "kicked") && !(json.content.type == "sysmsg")

How much effort did we spend to derive such a long series of logical operation expressions from the simple four letters else? Come? Moreover, if you don’t look carefully, you really can’t understand what this expression says.
This is where complex branches become difficult to read and manage. Imagine you are facing a switch case with an if else. There are 3 cases in total, and each case has 3 elses. This is enough for you to study - each branch and condition contain all its prefixes. The branch and the preceding branches of all ancestor branches are the result of non-then-AND.


How to avoid complex branches
First of all, complex logical operations are unavoidable. The result of refactoring should be equivalent logic. All we can do is make the code easier to read and manage. Therefore, our focus should be on how to make complex logical operations easy to read and manage.
Abstract into classes or factories
For those who are accustomed to object-oriented design, this may mean breaking up complex logical operations and distributing them into different classes:

switch (json.result) { 
case "ok": 
var factory = commandFactories.getFactory(json.command); 
var command = factory.buildCommand(json); 
command.execute(); 
break; 
}


这看起来不错,至少分支变短了,代码变得容易阅读了。这个 switch case 只管状态码分支,对于 "ok" 这个状态码具体怎么处理,那是其他类管的事情。 getFactory 里面可能有一组分支,专注于创建这条指令应该选择哪一个工厂的选择。同时 buildCommand 可能又有另外一些琐碎的分支,决定如何构建这条指令。
这样做的好处是,分支之间的嵌套关系解除了,每一个分支只要在自己的上下文中保持正确就可以了。举个例子来说, getFactory 现在是一个具名函数,因此这个函数内的分支只要实现 getFactory 这个名字暗示的契约就可以了,无需关注实际调用 getFactory 的上下文。
抽象为模式匹配
另外一种做法,就是把这种复杂逻辑运算转述为模式匹配: 

Network.listen({ 
"result": "ok", 
"command": "message", 
"content": { "from": "", "content": "kicked" } 
}, function(json) { /* disconnect */ }); 
Network.listen([{ 
"result": "ok", 
"command": "message", 
"content": { "type": "sysmsg" } 
}, { 
"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 */ });

现在这样子是不是清晰多了?第一种情况,是被踢下线,必须匹配指定的 from 和 content 值。第二种情况,是显示系统消息,由于系统消息在两个版本的协议中略有不同,所以我们要捕捉两种不同的 JSON ,匹配任意一个都算是命中。第三种情况,是显示聊天消息,由于在老版本协议中系统消息和踢下线指令都属于特殊的聊天消息,为了兼容老版本协议,这两种情况要从显示聊天消息中排除出去,所以就使用了 "$ne" (表示 not equal )这样的后缀进行匹配。
由于 listen 方法是上下文无关的,每一个 listen 都独立声明自己匹配什么样的 JSON ,因此不存在任何隐含逻辑。例如说,要捕捉聊天消息,就必须显式声明排除 from == "" 以及 type == "sysmsg" 这两种情况,这不需要由上下文的 if else 推断得出。
使用模式匹配,可以大大提高代码的可读性和可维护性。由于我们要捕捉的是 JSON ,所以我们就使用 JSON 来描述每一个分支要捕捉什么,这比一个长长的逻辑运算表达式要清晰多了。同时在这个 JSON 上的每一处修改都是独立的,修改一个条件并不影响其他条件。
最后,如何编写一个这样的模式匹配模块,这已经超出了本文的范围。

The above is the detailed content of Let’s talk about the usage of various complex branch statements in JavaScript. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn