前面我们讲了各式各样的不同语言的编程范式,从C语言的泛型,讲到C++的泛型,再讲到函数式的 Map/Reduce/Filter,以及 Pipeline 和 Decorator,还有面向对象的多态通过依赖接口而不是实现的桥接模式、策略模式和代理模式,以及面向对象的IoC,还有JavaScript的原型编程在运行时对对象原型进行修改,以及Go语言的委托模式…… 所有的这一切,不知道你是否看出一些端倪,或是其中的一些共性来了? # 两篇论文 1976年,瑞士计算机科学家,Algol W,Modula,Oberon和Pascal语言的设计师 [Niklaus Emil Wirth](https://en.wikipedia.org/wiki/Niklaus_Wirth)写了一本非常经典的书《[Algorithms + Data Structures = Programs](http://www.ethoberon.ethz.ch/WirthPubl/AD.pdf)》(链接为1985年版) ,即算法 + 数据结构 = 程序。 这本书主要写了算法和数据结构的关系,这本书对计算机科学的影响深远,尤其在计算机科学的教育中。 1979年,英国逻辑学家和计算机科学家 [Robert Kowalski](https://en.wikipedia.org/wiki/Robert_Kowalski) 发表论文 [Algorithm = Logic + Control](https://www.doc.ic.ac.uk/~rak/papers/algorithm%20=%20logic%20+%20control.pdf),并且主要开发“逻辑编程”相关的工作。 Robert Kowalski是一位逻辑学家和计算机科学家,从20世纪70年代末到整个80年代致力于数据库的研究,并在用计算机证明数学定理等当年的重要应用上颇有建树,尤其是在逻辑、控制和算法等方面提出了革命性的理论,极大地影响了数据库、编程语言,直至今日的人工智能。 Robert Kowalski在这篇论文里提到: > An algorithm can be regarded as consisting of a logic component, which specifies the knowledge to be used in solving problems, and a control component, which determines the problem-solving strategies by means of which that knowledge is used. The logic component determines the meaning of the algorithm whereas the control component only affects its efficiency. The efficiency of an algorithm can often be improved by improving the control component without changing the logic of the algorithm. We argue that computer programs would be more often correct and more easily improved and modified if their logic and control aspects were identified and separated in the program text. 翻译过来的意思大概就是: > 任何算法都会有两个部分, 一个是 Logic 部分,这是用来解决实际问题的。另一个是Control部分,这是用来决定用什么策略来解决问题。Logic部分是真正意义上的解决问题的算法,而Control部分只是影响解决这个问题的效率。程序运行的效率问题和程序的逻辑其实是没有关系的。我们认为,如果将 Logic 和 Control 部分有效地分开,那么代码就会变得更容易改进和维护。 注意,最后一句话是重点——**如果将 Logic 和 Control 部分有效地分开,那么代码就会变得更容易改进和维护。** # 编程的本质 两位老先生的两个表达式: - Programs = Algorithms + Data Structures - Algorithm = Logic + Control 第一个表达式倾向于数据结构和算法,它是想把这两个拆分,早期都在走这条路。他们认为,如果数据结构设计得好,算法也会变得简单,而且一个好的通用的算法应该可以用在不同的数据结构上。 第二个表达式则想表达的是数据结构不复杂,复杂的是算法,也就是我们的业务逻辑是复杂的。我们的算法由两个逻辑组成,一个是真正的业务逻辑,另外一种是控制逻辑。程序中有两种代码,一种是真正的业务逻辑代码,另一种代码是控制我们程序的代码,叫控制代码,这根本不是业务逻辑,业务逻辑不关心这个事情。 算法的效率往往可以通过提高控制部分的效率来实现,而无须改变逻辑部分,也就无须改变算法的意义。举个阶乘的例子, X(n)!= X(n) * X(n-1) * X(n-2) * X(n-3)* … * 3 * 2 * 1。逻辑部分用来定义阶乘:1) 1是0的阶乘; 2)如果v是x的阶乘,且u=v*(x+1),那么u是x+1的阶乘。 用这个定义,既可以从上往下地将x+1的阶乘缩小为先计算x的阶乘,再将结果乘以1(recursive,递归),也可以由下而上逐个计算一系列阶乘的结果(iteration,遍历)。 控制部分用来描述如何使用逻辑。最粗略的看法可以认为“控制”是解决问题的策略,而不会改变算法的意义,因为算法的意义是由逻辑决定的。对同一个逻辑,使用不同控制,所得到的算法,本质是等价的,因为它们解决同样的问题,并得到同样的结果。 因此,我们可以通过逻辑分析,来提高算法的效率,保持它的逻辑,而更好地使用这一逻辑。比如,有时用自上而下的控制替代自下而上,能提高效率。而将自上而下的顺序执行改为并行执行,也会提高效率。 总之,通过这两个表达式,我们可以得出: **Program = Logic + Control + Data Structure** 前面讲了这么多的编程范式,或是程序设计的方法。其实,我们都是在围绕着这三件事来做的。比如:
如果你看过那些混乱不堪的代码,你会发现其中最大的问题是我们把这Logic和Control纠缠在一起了,所以会导致代码很混乱,难以维护,Bug很多。绝大多数程序复杂的原因就是这个问题,就如同下面这幅图中表现的情况一样。
# 再来一个简单的示例
这里给一个简单的示例。
下面是一段检查用户表单信息的常见代码,我相信这样的代码你见得多了。
```
function check_form_x() {
var name = $('#name').val();
if (null == name || name.length <= 3) {
return { status : 1, message: 'Invalid name' };
}
var password = $('#password').val();
if (null == password || password.length <= 8) {
return { status : 2, message: 'Invalid password' };
}
var repeat_password = $('#repeat_password').val();
if (repeat_password != password.length) {
return { status : 3, message: 'Password and repeat password mismatch' };
}
var email = $('#email').val();
if (check_email_format(email)) {
return { status : 4, message: 'Invalid email' };
}
...
return { status : 0, message: 'OK' };
}
```
但其实,我们可以做一个DSL+一个DSL的解析器,比如:
```
var meta_create_user = {
form_id : 'create_user',
fields : [
{ id : 'name', type : 'text', min_length : 3 },
{ id : 'password', type : 'password', min_length : 8 },
{ id : 'repeat-password', type : 'password', min_length : 8 },
{ id : 'email', type : 'email' }
]
};
var r = check_form(meta_create_user);
```
这样,DSL的描述是“Logic”,而我们的 `check_form` 则成了“Control”,代码就非常好看了。
# 小结
代码复杂度的原因:
- 业务逻辑的复杂度决定了代码的复杂度;
- 控制逻辑的复杂度 + 业务逻辑的复杂度 ==> 程序代码的混乱不堪;
- 绝大多数程序复杂混乱的根本原因:**业务逻辑与控制逻辑的耦合**。
如何分离control和logic呢?我们可以使用下面的这些技术来解耦。