再读整洁架构之道(二)编程范式

Tommy Cheese | Jul 5, 2024 min read

再读整洁架构之道(二)

编程范式指的是程序的编写模式,它告诉我们应该在什么时候采用什么样的代码结构。

截止目前,一共出现了三种编程范式:结构化编程范式、面向对象编程范式和函数式编程。

作者认为,每种编程方式不是在给架构设计者的武器库进行扩充,相反,架构师和程序员的武器已经够多了,这三种编程范式是在对他们利用的武器进行限制,这也是为什么他们叫做“范式”。

结构化编程范式

结构化编程对程序控制权的直接转移进行了限制和规范,特别指的是限制了程序中goto的随意使用。

分解程序

Dijkstra希望使用数学推导方法对程序进行推理证明:让程序就成为了一种欧几里得结构,这样可以用一些已经证明的结构串联起新的程序,从而进一步推导整个程序的正确性。

他还发现,如果程序中包含大量的goto,那么程序会变得难以分解,而goto语句的作用完全可以依靠分支和循环来完成。

这样,结构化编程范式表明,程序可以进行降解拆分,一个大型问题拆分为一系列高级函数的组合,而这些高级函数各自又可以继续被拆分为一系列低级函数,如此无限递归。更重要的是,每个被拆分出来的函数也都可以用结构化编程范式来书写。

但是,这种形式化证明的编程方式没有成为主流,科学证明法是目前使用较多的方法。

科学证明法

科学理论和科学定律可以被证伪,但是没有办法被证明,类似的,程序只能测试证伪,不能证明,即“测试只能展示Bug的存在,并不能证明不存在Bug”。因此,利用科学证明法,结构化编程范式就可以促使我们先将一段程序递归降解为一系列可证明的小函数,然后再编写相关的测试来试图证明这些函数是错误的。如果这些测试无法证伪这些函数,那么我们就可以认为这些函数是足够正确的,进而推导整个程序是正确的。

面向对象编程范式

面向对象编程对程序控制权的间接转移进行了限制和规范。

具体来说,面向对象编程凡事使用多态特性限制了函数指针的使用。这可以理解为函数指针只能进行有限制的指向,比如java中的多态一般出现在有继承关系的两个类的对象之间。

作者也认为多态是OOP的最大特性,利用多态可以做到依赖反转

依赖反转即引入接口,完全控制系统中所有源代码的依赖关系,而不需要收到系统控制流的制约。

依赖反转保证了我们可以对系统进行插件式的开发,如将web UI和数据库与业务逻辑之间的依赖进行反转,可以保证主业务逻辑与UI和数据库的解耦,这样UI和数据库就成了主业务逻辑的插件。

面向对象编程就是以多态为手段来对源代码中的依赖关系进行控制的能力,这种能力让软件架构师可以构建出某种插件式架构,让高层策略性组件与底层实现性组件相分离,底层组件可以被编译成插件,实现独立于高层组件的开发和部署。

函数式编程范式

函数式编程对程序中的赋值进行了限制和规范。

换句话说,函数式编程语言中变量是不可变的。

作者有以下的观点,这一段话来自原文:

“所有的竞争问题、死锁问题、并发更新问题都是由可变变量导致的。如果变量永远不会被更改,那就不可能产生竞争或者并发更新问题。如果锁状态是不可变的,那就永远不会产生死锁问题"

如果不考虑储存器与处理器的速度限制,那么不可变性是可行的,但实际上我们必须考虑这些因素,因此可变性只能在一定程度上可行,需要让不可变性在一定程度上可行需要完成可变性的隔离

可变性的隔离

可变性隔离的一种常见方式是将应用程序,或者是应用程序的内部服务进行切分,划分为可变的和不可变的两种组件。不可变组件用纯函数的方式来执行任务,期间不更改任何状态。这些不可变的组件将通过与一个或多个非函数式组件(即可变组件)通信的方式来修改变量状态。

一个例子是GIT版本管理工具。它通过移动指针来记录文件的变化:增删改,但实际上文件并没有被真正的修改和删除,只存在增加和检索查询两种情况,这不就是不可变性的一个例子吗。

同时,mysql中的事务管理、事务性内存的工作也都运用了这个思想。

总结

三种编程范式与软件架构具有密切的关系:

  • 多态是我们跨越边界的手段;
  • 函数式编程是我们规范和限制数据存放位置与访问权限的手段;
  • 结构化编程则是个模块的实现基础;

三种编程范式与软件架构的三大关注重点不谋而合:组件独立性(面向对象)、数据管理(函数式)以及功能性(结构)。

再次思考

三种编程范式都对程序员提出了新的限制。每个范式都约束了某种编写代码的方式,没有一个编程范式是在增加新能力。也就是说,编程范式带来的是——什么不应该做。

(第二篇完)

comments powered by Disqus