3.293. 什么是架构设计V4

最近在引导一个涉及软硬件协同设计的项目,收集了一组经验,再次重建一下我的架构设计理论模型。(这个版本还没有整理完,先写着)

又写一个版本的架构设计定义。

构架设计本质上是控制名称空间。为此我们需要理解一下名和道的关系。世界万物,存在在那里就存在在那里了,对它的任何一种所谓“描述”,本质上是我们脑子里制造的一个“数字孪生”,我们希望用这个数字孪生在一定程度上预测现实世界的现实。但这个“数字孪生”从来不是现实世界本身。

在《道德经》中,这里提到的世界万物,就是“道”,而我们在脑子中,语言间,对道的描述,就是“名”。所以,实际上,我们现在说的“世界”,“万物”,“数字孪生”,这些名字,都是“名”,这个名确实在说那个“道”,但它们并非就是那个道。它们只是道的其中一个抽象,一个投影,一个角度。

我们必须用名去描述这个世界,因为我们的脑容量是有限的,我们没有能力用道的所有信息去讨论世界。一个班60个人的成绩我们都只能用平均分,中位数,最高分……这些概念去讨论它,不要说研究一个几十万,几百万,几千万行的软件。所以,道曰大,大曰逝,逝曰远。道到了我们的脑子中,就会丢失细节,变成一个名。

我们用名去描述我们看到的世界,所以我们总有种错觉,觉得我们的名描述的是唯一的,真实的那个世界,或者至少可以说是这个世界的一部分。但其实我们描述的只是世界某个部分的一个抽象。我们的抽象和别人的抽象也是不同的。

但我们判断我们如何走向下一步,是基于我们制造的这个抽象来进行的,我们的抽象控制不了这个世界,但它控制了我们如何改造这个世界。所以,我们如何命名我们的世界,决定了我们如何改造这个世界,从而一定程度上控制着相关的“世界”未来的那个样子。

不少人会产生一个误解,觉得信息都在里面了,只要不断补充信息,就能够得到一个未来的系统。这就是为什么很多人宁愿马上编码,再去补设计文档,而不用用一个设计去控制它们的编码。因为他们觉得这样“务实”,他们认为高层设计只是编码的重复,只是为了让某些“领导”明白他们干了什么才需要的东西,他们那些细节的if...else...while才是“专业”的东西。有了那些东西,才有“抽象”。

举一个例子,我们曾经设计一个指令,比如叫compress,它对指定的寄存器中的数据进行一个压缩的操作,这条指令需要的数据很多,靠固定长度的指令是肯定不行的,为此我们需要延长它的长度。但我们的指令解码器只能解定长的32位指令。这时我们有两种方法解释这条指令,一种是如果指令解码器解到一个32位的compress指令,我们认为之后的96位都是这个指令的一部分。第二种方法是解码器认为自己解了4条指令,第一条表示启动解码,后面的表示设定参数。

如果我们认为能运行就好,两种方法对我们来说其实都无所谓。但其实后者的构架代价是很高的,因为架构是个穷举的,发展性的问题,指令发射是原子的,指令每次读32个自己,全部读完才会发射出去,这条指令和其他指令的取值直接,是没有穿插关系的,互相是原子的。但把指令分成启动压缩和设置参数两个行为,两者之间是可以插入无数其他指令的,如果描述这个状态机?这就是高层建模。如果我们不控制这一层,程序确实也能在一些流程下正常运行,但一旦出问题,这个系统到底还行不行,你是不知道的。

在这个过程中,我们加入了额外的劳动,建立了一个高层逻辑,然后在高层逻辑中控制了我们代码的行为,从而让我们的系统在高层呈现了特定的规律,这样我们的程序就可以控制了。

所以有图纸我们可以把大楼改几十层,没有图纸就堆土,你三层都不一定能盖起来。难道图纸是钢筋混凝土的一部分吗?它只是改变了钢筋混凝土的组织形式而已。

你甚至无法问:我堆的土,为什么就盖不了几十层楼高?这个问题无法回答,因为你的系统不呈现规律,而“名”的系统,就只能说那些有规律的东西,没有规律的东西你连说都说不了(所谓名可名,非常名),你个闹嚷嚷的课堂,有学生在抽烟,有学生在谈恋爱,有学生在放爆竹,你问这些学生正在做什么,是否有人能考100分,这没法回答,因为没有可以回答的条件。

同样,你的代码收到一个要打印的需求就打印,收到一个要写文件的需求就写文件,收到一个要排序的功能就排序,然后你问这个软件能否实现一个迁移的功能——对不起,没人知道,因为我们不知道它有什么规律。我们甚至不能说它一定不行。

想像一下,一个精巧的算法,各种可能性都考虑到了,写了下来,有100万行的代码,现在其中有一个地方出现一个内存越界,可能随机把其中一个变量的值修改了,你怎么把它找出来?或者你的CPU出了问题了,某个加法的结果,随机可能是一个随机值,你有什么办法把这个问题找出来?

系统一旦失去了让我们进行数字孪生判断的依据,我们就失去了对它的控制。所以,架构设计是控制我们如何定义控制这个系统的名称系统,我们如何命名一个系统,决定如何抽象它,是架构设计的全部。架构设计不是任何一行代码,架构设计表示所有的代码。这就是架构设计角度的名和道的关系。

而在实践上,架构设计是人的意图,编码和细节设计是机器的意图。这两个边界很模糊,但在模糊的角度上,这个分界线清晰存在。

我们又举一个例子。最近遇到一个问题,有人做一个原子操作的测试方案,他在一组线程里面,用原子函数设置同一个变量,然后读出来,判断变量是否被原子更新了。为了让这个变量的读写互相之间没有影响,他又用了一个spinlock,把这两个原子操作保护起来。

这就很奇怪了,明明测试的是原子操作的行为,你用spinlock把它保护起来,那这个被测试的函数无论是否完成了原子操作,不都是成功的吗?

这种情况很常见,“测试原子操作是否符合预期”,这是我们人的意图。而先“调用atomic_set(), 再调用atomic_get(),为了可以判断atomic_set和atomic_get()是一样的,需要把两者保护起来……”这是机器的意图。如果你抛开人的意图,钻到机器的意图中了,失去人的意图。

我举这个例子的时候,为了让你明白,在描述的时候已经建立了一个名称空间,把问题按我的名字分类给你说出来,所以你会觉得这很明白啊,很简单啊,怎么会分辨不出来?但一旦你自己去面对那个复杂的局面的时候,你就很可能分辨不了了。

你做一个复杂系统,比如用qemu模拟一个CPU的运行,qemu翻译程序,把Guest的代码翻译程序执行代码,这是一个角度,被翻译的代码更新被模拟的CPU的执行,这是第二个维度,被模拟的CPU运行操作系统的调度代码,这是第三个维度,被调度的代码从一个线程切换到另一个线程,这是第四个维度……这些不同的角度,描述的是同一个事实(“道”)。

现在你告诉我“运行Guest中的switch_to函数”,在这些不同的维度中,分别是什么意思?

这就是架构设计中,分解不同的概念空间,在概念上保证同一件事情,在所有维度中,概念都是自恰的作用,只有我们保证了概念的自恰,我们在每个空间上的逻辑都是可以发展的,这样,这个软件的高楼,就有机会一层层往上盖,否则就必然盖到第二层就倒下来。

而他们甚至无法告诉你,为啥这东西会倒下来。

todo:具体的方法。