代码腐化似乎注定的

  • 最初:没有谁是不想好好写的。都有一个宏伟的规划,这次一定
  • 途中:Code Review 如同“堂吉诃德”一般,根本架不住大批量大批量的修改
  • 放弃:躺平了,下次一定

如此循环往复。然而腐化了之后,是无法起死回生的。

  • 食品防腐是 low tech 的事情,但是中毒身亡之后起死回生是天顶星技术
  • 新冠疫苗已经被人类掌握,但是逆转免疫风暴造成的多脏器衰竭仍然是天顶星技术

虽然很多人醉心于遗留代码改造之道。笔者也从事铲屎业务很多年,仍未掌握此项技术。还是让代码一直保持在未腐化的状态更简单一些。

那么代码如何防腐呢?不靠 Code Review 又靠什么呢?

信息隐藏

如果我们要给 vscode 添加"列出单元测试",以及"执行单元测试"的功能,例如实现下图所示的功能:

java 防腐层是什么 防腐层全称 代码防腐层_编程语言

是不需要给 vscode 提交 pull request 的,可以通过新建 Git 仓库以插件的方式给 vscode 扩展功能。

用 Git 仓库的依赖关系图来看

java 防腐层是什么 防腐层全称 代码防腐层_java_02

通过这样的依赖关系,我们就把插件的实现细节,也就是其私有的信息(比如 ts server,mocha这些)给隐藏起来了。vscode 不可能反向依赖去知道 mocha-test-explorer 使用了 mocha,同僚插件 typescript-language-feature 也不会对 mocha-test-explorer 的 mocha 产生耦合关系。这种做法叫信息隐藏。

更通用的说,这是一种 主板 + 插件 的结构。

java 防腐层是什么 防腐层全称 代码防腐层_git_03

这种结构和常见的“聚合 API”是不同的

java 防腐层是什么 防腐层全称 代码防腐层_编程语言_04

如果我们在插件中持有私有的数据,插件自己再怎么开接口,也无法把信息主动暴露出去。因为主板不依赖插件,插件之间也不互相依赖。除非主板提供接口,由插件来实现才能暴露信息。什么信息隐藏,什么信息暴露,完全由主板决定。

java 防腐层是什么 防腐层全称 代码防腐层_java_05

如果是上图所示的“聚合 API”风格的依赖关系。服务自身持有的数据就未必是私有的了,很有可能退化成代持的关系。比如服务如果把没有任何额外逻辑的 CRUD 接口都暴露出去了,这个服务就退化成了 database proxy 了。这样的信息是否隐藏,完全取决于“每个”服务对自己接口的设计。而不能像上图所示,把所有的裁量权集中到主板手上。

太理想了吧!这玩意能写业务?

如果我们要实现下面这样的离散型 UI

java 防腐层是什么 防腐层全称 代码防腐层_编程语言_06

要拆成主板和插件两部分并不难,比如主板可以是这样的

java 防腐层是什么 防腐层全称 代码防腐层_git_07

插件自然是把上面的坑给填上。

如果我们要实现下面这样的混合型 UI

java 防腐层是什么 防腐层全称 代码防腐层_编程语言_08

与离散型 UI 不同。这样的界面没有明显的槽可以开出来。一个区块可能是由多种源数据综合计算出来的。这样的情况,主板可以是这样的:

java 防腐层是什么 防腐层全称 代码防腐层_编程语言_09

然后插件就是不断地填充这个数据结构

java 防腐层是什么 防腐层全称 代码防腐层_java 防腐层是什么 防腐层全称_10

除了只读的界面渲染,还包括响应用户输入和表单提交的写操作。比如下面这样的离散型流程:

java 防腐层是什么 防腐层全称 代码防腐层_git_11

显然这样的业务类型非常适合事件驱动。我们让下单完成去触发 OrderCreated 事件,由其他感兴趣的 Git 仓库去订阅这个事件。插件和主板是这样的一种依赖关系:

java 防腐层是什么 防腐层全称 代码防腐层_java_12

但不是所有的流程都是这么简单的离散型。比如下面这样的混合型流程:

java 防腐层是什么 防腐层全称 代码防腐层_编程语言_13

可以把混合型流程拆分成多个子流程的复合

java 防腐层是什么 防腐层全称 代码防腐层_编程语言_14

如果不想插件与插件之间产生直接的依赖关系,但是又要进行流程复合,那只能把一些接口给下沉到主板里。比如这样的 Git 仓库依赖关系

java 防腐层是什么 防腐层全称 代码防腐层_设计模式_15

通过以上4个例子,我们可以看到实际的业务需求是可以用类似 vscode 的插件架构来实现的。这么别扭的写业务是为了啥?

代码防腐

java 防腐层是什么 防腐层全称 代码防腐层_java_16

1971 年 David Parnas 就已经写成了软件工程的开山名著《On the Criteria to be Used in Decomposing Systems into Modules》。业内的前辈们已经意识到了按流程图来拆分模块肯定是错误的(it is almost always incorrect to begin the decomposition of a system into modules on the basis of a flowchart)。然而50年过去了,我们主流的模块拆分的思路仍然是“函数套函数”,“中台套中台”的想法。为什么?

这是国外一个开发者调查中受访者的从业时间的分布图

java 防腐层是什么 防腐层全称 代码防腐层_编程语言_17

国内的开发者分布会比这个更偏年轻得多。这说明了新人还在不断地涌入这个行业。如果指望一个项目上所有的人都能遵守很高的标准,能够独立对“好”和“坏”的设计做正确的判断,这是不现实的。并不是说软件工程教育不重要,教育仍然是要做的。但是仅仅靠教育来提高软件工程的质量是不现实的。

java 防腐层是什么 防腐层全称 代码防腐层_编程语言_18

如上图所示的“信息隐藏”的做法,其实质是为了“代码防腐”。在这样的依赖关系下,插件的 Git 仓库是不会造成全局的影响的。所有的重大决策权都收敛到了主板手里,插件无法自己决定接口是什么以及如何与其他插件集成。这样我们就可以放心地分配新人去写插件,而不用担心插件中的设计选择造成大面积的代码腐化