欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

理解编程范式与控制权交接的关联

最编程 2024-08-04 13:26:11
...

编程范式

其实所谓架构就是限制,限制源码放在哪里、限制依赖、限制通信的方式,但这些限制比较上层。编程范式是最基础的限制,它限制我们的控制流和数据流:结构化编程限制了控制权的直接转移,面向对象编程限制了控制权的间接转移,函数式编程限制了赋值,相信你看到这里一定一脸懵逼,啥叫控制权的直接转移,啥叫控制权的间接转移,不要着急,后边详细讲解。

这三个编程范式最近的一个也有半个世纪的历史了,半个世纪以来没有提出新的编程范式,以后可能也不会了。因为编程范式的意义在于限制,限制了控制权转移限制了数据赋值,其他也没啥可限制的了。很有意思的是,这三个编程范式提出的时间顺序可能与大家的直觉相反,从前到后的顺序为:函数式编程(1936年)、面向对象编程(1966年)、结构化编程(1968年)。

1.结构化编程

结构化编程证明了人们可以用顺序结构、分支结构、循环结构这三种结构构造出任何程序,并限制了 goto 的使用。遵守结构化编程,工程师就可以像数学家一样对自己的程序进行推理证明,用代码将一些已证明可用的结构串联起来,只要自行证明这些额外代码是确定的,就可以推导出整个程序的正确性。

前面提到结构化编程对控制权的直接转移进行了限制,其实就是限制了 goto 语句。什么叫做控制权的直接转移?就是函数调用或者 goto 语句,代码在原来的流程里不继续执行了,转而去执行别的代码,并且你指明了执行什么代码。为什么要限制 goto 语句?因为 goto 语句的一些用法会导致某个模块无法被递归拆分成更小的、可证明的单元。而采用分解法将大型问题拆分正是结构化编程的核心价值。

其实遵守结构化编程,工程师们也无法像数学家那样证明自己的程序是正确的,只能像物理学家一样,说自己的程序暂时没被证伪(没被找到bug)。数学公式和物理公式的最大区别,就是数学公式可被证明,而物理公式无法被证明,只要目前的实验数据没把它证伪,我们就认为它是正确的。程序也是一样,所有的 test case 都通过了,没发现问题,我们就认为这段程序是正确的。

2.面向对象编程

面向对象编程包括封装、继承和多态,从架构的角度,这里只关注多态。多态让我们更方便、安全地通过函数调用的方式进行组件间通信,它也是依赖反转(让依赖与控制流方向相反)的基础。

在非面向对象的编程语言中,我们如何在互相解耦的组件间实现函数调用?答案是函数指针。比如采用C语言编写的操作系统中,定义了如下的结构体来解耦具体的IO设备, IO 设备的驱动程序只需要把函数指针指到自己的实现就可以了。

struct FILE {
    void (*open)(char* name, int mode);
    void (*close)();
    int (*read)();
    void (*write)(char);
    void (*seek)(long index, int mode);
}

这种通过函数指针进行组件间通信的方式非常脆弱,工程师必须严格按照约定初始化函数指针,并严格地按照约定来调用这些指针,只要一个人没有遵守约定,整个程序都会产生极其难以跟踪和消除的 Bug。所以面向对象编程限制了函数指针的使用,通过接口-实现、抽象类-继承等多态的方式来替代。

前面提到面向对象编程对控制权的间接转移进行了限制,其实就是限制了函数指针的使用。什么叫做控制权的间接转移?就是代码在原来的流程里不继续执行了,转而去执行别的代码,但具体执行了啥代码你也不知道,你只调了个函数指针或者接口。

3.函数式编程

函数式编程有很多种定义很多种特性,这里从架构的角度,只关注它的没有副作用和不修改状态。函数式编程中,函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。前面提到函数式编程对赋值进行了限制,指的就是这个特性。

在架构领域所有的竞争问题、死锁问题、并发问题都是由可变变量导致的。如果有足够大的存储量和计算量,应用程序可以用事件溯源的方式,用完全不可变的函数式编程,只通过事务记录从头计算状态,就避免了前面提到的几个问题。目前要让一个软件系统完全没有可变变量是不现实的,但是我们可以通过将需要修改状态的部分和不需要修改的部分分隔成单独的组件,在不需要修改状态的组件中使用函数式编程,提高系统的稳定性和效率。

综上,没有结构化编程,程序就无法从一块块可证伪的逻辑搭建,没有面向对象编程,跨越组件边界会是一个非常麻烦而危险的过程,而函数式编程,让组件更加高效而稳定。没有编程范式,架构设计将无从谈起。

推荐阅读