仓库源文

.. Kenneth Lee 版权所有 2016-2021

:Authors: Kenneth Lee :Version: 1.2

大型软件架构设计


前一篇说了原理,软件架构本质上是绘制一幅复杂素描所打的草稿,我还说,如果你罩得 住,可以不需要这个草稿。

但这只是“理论上”,我们写软件,基本上不是在写只有几千行的代码的小程序,而是写数 千万行的大型程序。大曰逝,逝曰远,远曰反。一件事情变大以后,原来近在眼前的事情 看到的策略,方法,都会反过来。我举简单程序的例子,是反向化简模型,所谓“执古之道 以御今之有。能知古始, 是谓道纪”,是让读者从原始的推演中重新理解我们面对复杂问 题时的应该把握的判断模型,是给你讲“道纪”。所以,看我的表述,你不能看结论,要看 Pattern。我的结论在不同的情形下是不断会变的,我要讲的是Pattern,不是强调那个结 论。

我们很多工程师在没有经验的时候,想架构问题都会基于逻辑来想。但如果没有经验,这 常常是错误的。 我前面举了个例子,我刚入行的时候(其实现在很多人还有这个误区), 当我们发现需求做错了,实现和设计不一致的时候,我们都会做出一个判断:这是需求文 档写得不够严谨,设计文档不接地气。这个判断方向基本上是缘木求鱼。我是认为干了好 几年,还是抱这种心思的工程师,基本上架构设计水平也就到此为止了。因为你始终无法 坚持要“守弱”这个基本原则,你不相信事实,而去相信你的理想了。你写了几年的程序, 一直以来需求文档都不够严谨,设计文档一直不接地气,甚至从来没有改进过,你还不断 相信,你有一天能写出“严谨”的文档?你完全被你的个人想象蒙蔽了眼睛,从来没有好好 看过这个世界啊。

.. note::

说明一下,这里讨论的问题都是基于你是一个严肃开发者的角度来说的。如果你是个严 肃的开发者,当初需求分析你分析不出问题,那就说明你当时的水平,客观条件,就只 能做到那个程度。这里不是在讨论你怎么教训那些和你玩“我没错”游戏的下属的。

这个问题到底是什么?是工作量啊。整个软件,其实就是一组逻辑,每行软件代码,都是 针对不同角度的的逻辑判断。而我们在控制这个软件的所有逻辑被建成以前。我们的逻辑 量永远都是不足的啊,如果足了,你根本就不需要设计(包括架构设计)了,你已经拥有 你的代码了。所以,构架设计是要在逻辑量不足(逻辑不严谨)的情况下对未来进行预判 和控制。而你指望建设好所有的逻辑来解决这个问题?这么明显的逻辑死循环你都没有注 意到吗?那你已经忘掉架构设计本来的工作是什么了。

就好比一个领导者,他的工作本应是告诉大家想什么方向走,保证团队能走目的地。你的 工作照理说是研究情报,确认对整个团队影响最大的情报,在模凌两可的情况下强行选定 方向等等。但这些事情他不去干,而跑去给团队做饭,去充当斥侯探路之类的,看起来很“ 亲民”,和团队打成一片,但他自己的本职工作呢?

领导者的本职工作是隐形的,是“无名”的,做好了饭,拿到了情报,这些都是有形的,选 好方向,让饭做好了,让情报发挥作用,这些行动,并不直接可见,领导者想拿“名”,团 队就会失去领导,团队就会失败,团队失败了,领导者也就无所谓名了,整个团队都会被 历史淘汰。这就是为什么圣人无私才能成其私,放弃掉你的名,你才让整个团队拥有名, 拥有整个团队的名,你才能有你的名。这就是作为领导者的大格局。

而架构师,就是设计团队的设计领导者。

补充讨论

今天(2021年5月26日),有读者和我讨论这样一个问题:::

    (原文提到)“而我们在控制这个软件的所有逻辑被建成以前。我们的逻辑 量永
    远都是不足的啊,如果足了,你根本就不需要设计(包括架构设计)了,你已经
    拥有 你的代码了。所以,构架设计是要在逻辑量不足(逻辑不严谨)的情况下对
    未来进行预判和控制。”

    逻辑量不用足到最终代码这一步,但是要足到需求和场景分析清晰全面这一步吧

    我自己答一下这个问题,看理解的是否正确?

    因为我还没有架构过大型项目(平时做的项目代码量都在100k以内)。所以我这
    儿的“架构”其实是包含了设计的。李老师说的架构应该是一个项目最顶层的设计
    。真正的架构师,应该从整体上把握项目的约束与边界,这是战略上。把需要分
    析,场景分析这些留给设计阶段,由开发人员来做。这是战术上的。架构师只关
    注战略,取其上德。开发人员做需求场景分析,详细设计,取其下德。

这个问题在这个点上很容易误解,我基于这个机会尝试补充一些讨论,看能否消除这个误 解。

我经常用《道德经》来解释架构设计的一些观点,主要是前者描述的恰恰是后者最容易引起 误会的问题:在信息不足的情况下决策。

我们从最简单的一个问题来理解这个问题,比如说,我现在给你一个需求,定义成函数就 是这样的:

.. code-block:: python

def search(key, strings): "在一个字符串列表中找出包含key的第一个字符串,否则返回None" ... return string

这种命题我们程序员非常熟悉,我们最擅长就是解决这种问题了。我把这称为一个“严谨的 逻辑闭包”。逻辑闭包的概念参考这里:\ :doc:逻辑闭包\ )。严谨的概念参考这里: :doc:逻辑如水\ 。

在严谨的逻辑闭包中,输入和输出是清晰的,所以,我们不用考虑其他问题,在这个逻辑 空间里面,就可以单独判断我们的算法是正确的,还是错误的。我们很喜欢这种逻辑闭包, 因为在这个世界里,我们可以心无旁鹜地聚焦在逻辑判断上。

但正如我们在前面\ :doc:逻辑如水\ 中提到的那样,逻辑闭包是一个“名”的世界,它能 够成立,是因为我们抛弃了其他细节和属性。比如对于Python,字符串列表是有唯一的定 义的,但现实世界的字符串仅有这一个定义吗?

你活在这个逻辑空间里,你觉得“我没错”,但现实世界的字符串可能是承载在语音中的, 你再“我没错”,也解决不了现实的问题。甚至现实中的key和strings都没有一个唯一的对 应实体,要选择的也不是第一个字符串,这个问题这样定义就是错的。

但我们的思考只能在逻辑上,我们只能在逻辑闭包中作思考,我们到底应该取信什么部分 的属性,作为我们的逻辑闭包的名称空间呢?这是《道德经》和架构设计研究的问题。它 的一大理论就是你必须基于“病病”来思考问题:我知道我哪里不知道,所以我不选择不知 道的东西来作为决策的基础。这就是所谓的:知不知上,不知知病,夫为病病,所以不病。 这个表述完全就是严谨的程序员写作的手法。

基于这样的思考模型,你就必须知道,你的每个决策都是在一个或严谨,或不那么严谨的 逻辑闭包中完成的。你不可能在一个逻辑闭包中包含所有的信息。越高层的设计,其实你 是用一个越不严谨的抽象去建模的。你必须承认你在进行高层设计的时候,是有很多“不知 ”的,即使这个细节设计本身也是你自己写的。

比如说你做一个操作系统,你第一层建模,最多就分出驱动,OS和进程这个分割,然后进 行一些基本的逻辑建模:进程和OS的区别是什么,分界在哪里,有些什么属性等等。但你 不可能在这个层次的逻辑闭包中,把进程切换时需要保存哪些上下文考虑在内。

所以,在我们讨论“架构设计怎么做”这一层建模中,我们是缺了很多信息的,我们没有指望 指出“所有的架构设计都需要分三层”或者所有的架构设计都需要先严格做到某种程度的需求 分析。这个结论在没有其他细节补充进来的时候,是一个“不知”,是个“病”,我们要“病病 ”,不去定义这个问题的结论。但我们有我们知的东西,这个知就是:我们不可能在一个逻 辑闭包中把所有问题考虑在内,所以我们的决策必须考虑到细节设计的自由度,否则细节 设计就无法设计,整个设计就会失败。所以,即使我们没有严格的逻辑推导,我们也要给 细节设计留自由度。这个策略本身,是严谨的。

.. note::

再给一个真实的例子:我们做过一个IOMMU的设计(设备的MMU)。在设计阶段,我们在 考虑是否要做CPU TLB Invalidate自动广播给IOMMU的功能(CPU TLB Invalidate是自 动广播给其他CPU的,所以如果需要,也可以广播给IOMMU)。业内是有方案做了这个功 能的,但也有人没有做。现在硬件团队给我提供一个经验是:做这个东西性能不高,不 如软件主动去Invalidate IOMMU的TLB。这时我有两个选择:

  1. 深入分析细节,决定选那条路

  2. 采信硬件的直觉,先做下去再说

    这种问题,现在决策的信息就不足,研究下去呢,其实就是做设计了。这种情况下,把 这个结论作为设计决策这个闭包的输入条件,就会导致我们这个阶段其实没有进行高层 决策,其实就是在带病设计了。这种情况下,我们就需要基于这个信息就是“不知”来进 行决策。

所以要求高层设计本身严谨,就是一个错误的选择。你越是“严谨”去理解我讨论的结论, 你就越在收缩我的原抽象的范围,就离我原来想得到的结论越远。