跳到主要内容

编程范式中的启发

编程范式 (paradigm) 指的是程序编写的模式。直到今天,也只有三种编程范式,他们是结构化编程面向对象编程以及函数式编程。他们分别限制了 goto 语句、函数指针和赋值语句的使用。而这些限制这些与软件架构的关系相当密切,可以给架构设计带来启发。

例如

结构化编程是各模块的算法实现基础,面向对象的多态是跨越架构边界的手段,函数式编程时规范和限制数据存放位置与访问权限的手段

这与软件架构关注的三大重点不谋而合:功能性组件独立性以及数据管理

结构化编程

使用if/elsedo/while代替goto,对程序控制权的直接转移进行了限制和规范


结构化编程的起源于对代码正确性的形式化证明。在数学上可以证明:通过既定的结构化编程 (if/else/do/while等),可以确保软件是可以被拆分成基本单元(分支结构与循环结构),进而就可以通过证明基本单元的正确性来证明整个软件是正确的。而goto之所以不提倡使用,是因为其会导致软件无法被拆分,使软件的正确性无法得到证明。

备注

从使用角度看,goto会导致代码可读性变差,且已被证明可被结构化编程语句代替,并且不影响性能,因此也不应该被使用。

单元测试

虽然现在实践中不会去证明每个函数的正确性,但是基于可被拆分的特性,可以通过为每个函数编写测试,验证软件正确性无法被证伪。就像科学研究一样,得出某段程序已足够实现当前目标的结论。

结构化编程的核心思想就是将软件功能性降解拆分为一系列高级函数、低级函数...,进而方便测试,证明其正确性。在架构设计中同样要保证软件在更高层级上,如模块、组件、服务等都是是正确的,或者从实践角度说,确保其无法被证伪,就需要:

启发 1

将类似结构化编程的限制方法应用在更高的层级上

面向对象编程

通过类的定义限制用户对函数指针的使用,对程序控制权的间接转移进行了规范和限制


面向对象是一种编程思想,它可以给系统带来很多特性,如封装、继承和多态,而其中最为重要的就是多态

面向对象的语言

即使是“没有支持面向对象”的 C 语言,也可以实现面向对象的编程,但这种显示的实现是危险的,其依赖于一系列需要人为严格遵守的约定(按约定格式初始化,调用函数指针)。而现有的“支持面向对象的”语言,主要的改进是通过语言的设计,将这些约定由编译器实现,消除了这种危险性。

在安全和便利的多态支持出现之前,软件通过一层层调用运行,高级函数需要调用低级函数,就必须要知道低级函数的实现,代码层面的依赖不可避免地要跟随程序的控制流。导致在软件架构上别无选择,依赖关系必须与控制流相同。

而多态(即同一个函数接口,背后的函数可能有不同的实现)出现后,HighLevel通过调用接口,间接调用LowLevel中的函数。通过这种特性,上层函数不再依赖于底层实现,只依赖于一个事先约定好的Interface,而 Lowlevel 则反向依赖于Interface(继承关系)。我们可以惊奇地发现依赖与控制流的关系反转了!即依赖反转

通过这种方式,架构师可以不受控制流约束,随意调整源代码依赖关系。使得底层模块变为上层模块的插件,各个模块都能被独立看护,互不依赖。对某个模块的修改不会影响其他模块的逻辑。实现模块独立部署独立开发

启发 2

使用多态的手段对源代码中的依赖关系进行控制,构建出一种插件式结构,实现各模块的独立开发与部署

函数式编程

某个符号的值是永远不变的,对程序中的赋值进行了限制和规范


函数式编程中的函数,表达的是类似数学中的函数,有两个特性:

像数学中的函数一样
  1. 输入不变,输出就不变
  2. 不会改变其他状态

函数式编程最大的意义在于,其变量是不可变的,所以在这样的函数中永远不会产生死锁问题。其本质上就是把软件中的可变性和不可变性分离,而对应到架构中,也应尽可能地将组件分为可变组件和不可变组件,隔离可变性并统一管理,提高系统的稳定性。

启发 3

因为不可变组件是稳定的,所以应尽可能将处理逻辑都归于不可变组件中,可变组件越少越好

例如

一个极端的例子就是 git,只储存修改记录,不储存状态。需要某一状态时,通过记录溯源。将程序的 CRUD(增查改删)限制为 CR(增查)

总结


在编程范式的每一次发展中,其能力都没有被扩充,而是不断提出了新的限制。

  • 结构化编程是对程序控制权的直接转移的限制,保证了程序的可证明性
  • 面向对象编程是对程序控制权的间接转移的限制,保证了模块的独立性
  • 函数式编程是对程序中赋值操作的限制,保证了系统的稳定性

而软件也都无一例外是由顺序结构、分支结构、循环结构和间接转移这几种行为组合而成,无可增加,也缺一不可。因此也编程范式中的限制也应使用于架构设计中。