3.242. 2020年又写的一个什么是架构设计的定义

3.242.1. 介绍

本文是在和一位兄弟沟通某个特性的架构设计策略前写的前置逻辑。现在看到的版本是沟通之后更新过,然后还和更多的团队沟通后修改的版本,可以作为我2020年底对一年架构工作的总结吧。

3.242.2. 基本定义

软件架构这个东西,针对不同的目标,有不同的关注点,但从比较基础的语义来说,是我们要定义一组较为粗糙或者说抽象的逻辑,让我们可以确认通过这组逻辑目标是可以达成的。做一个具象化的比喻:

比如我们要从深圳去北京,我们可以建立这样一个逻辑:

  1. 先在网上定机票

  2. 提前2个小时打车到深圳机场

  3. 坐飞机到北京首都机场

  4. 通过导航软件决定坐某条公共交通线路到北京的酒店

我们执行这个逻辑是不是一定能到北京呢?不一定,因为这依赖很多细节,比如路上堵车,天气不好飞机不能起飞,或者干脆没有去北京的机票之类的。但你说你没有这个逻辑,出门就向北走,能不能到得了北京呢?那大概率是不行的。

设计前面这个逻辑的过程,就是架构设计。

从这个例子中,我们看到了架构设计的几个特点:

  1. 它不是事无巨细,什么都设计,它更不是细节本身(只有实施,才是细节本身)

  2. 虽然它粗糙,但它的逻辑仍是连续的,是可以达成目标的。

  3. 它对执行具有限制作用,正是因为这个限制作用,才使我们在细节设计或者执行的时候简化了目标

这说起来很简单,只是因为例子简单,实际上一旦执行,很多人都会犯错。最常见的一个错误就是所谓的“我没错”。但凡“我没错”的设计,基本上都是有错的。因为架构设计首先是一种设计,所有选择中的一个选取。去北京可以坐飞机,也可以坐高铁,你要选其中一个,放弃另一个,放弃另一个是没有严格理由的,只是你不能两个都选而已。你要保证“我没错”,就必须两个都选,这就没有在做架构设计这件事情。我们有些工程师,只要市场部,领导,或者兄弟部门不签字,就不肯选择,说到底都是这种“我没错”的思路在作怪。希望每个选择都是有严格的“理由”的,却不知道这恰恰是最没有理由的选择。

“我没错”的另一个表现是脱离逻辑链,避开逻辑链去谈“正确的信息”,以图用信息填补没有设计的缺陷。比如,今天之内有12班航班飞北京,其中6班要中途转机,他们的机型分别是巴拉巴拉,这些机型的故障率用柱状图表述如下……。这看着都没有错,还很专业的样子,但其实根本和我们要做的设计逻辑毫无关系。你说他有问题,他还给你辩解说“难道飞机安全不重要吗?”。如果不用这么简单的例子对比,我们可能很难想象这种事情在架构讨论中发生的频度会如此之高。比如在一个网卡的高层设计讨论中,人们会上来就讨论MAC包的格式,而不管这个网卡设计的根本目的是什么,能否不设计……这种事情居然会层出不穷。

前面这个脱离逻辑链问题一个更加隐秘的体现是高层逻辑和下层逻辑不分。还是用前面这个坐飞机的例子来类比。打的这个选择,可能有不同的细节可以选择,比如可以是打出租车,也可以是用快车,专车之类的服务,这个确实也是我们必然要做的选择,但这个逻辑的变化,不改变我们其他任何逻辑,这个东西就属于下一层的设计。我们不应该把它提到本层里面来。这很重要,因为我们在架构设计阶段,主要目的不是为了把整个产品做出来,而是保证高层逻辑合理了,我们可以专心根据高层逻辑的来做下层逻辑的设计,所以我们必须全部心思反复权衡高层逻辑是不是最优的,有没有优化的余地,有没有多余的约束。高层逻辑引入的约束越少,我们做下层设计的自由度越大。但我们还需要保证逻辑链是成立的,所以引入约束就不可避免。我们是在这个两难之中选一个平衡。这时你把下层设计暴露到高层设计中,这个东西就影响我们对这个逻辑链的优化。也许只有一两个的时候我们还能脑补把它忽略掉,但这种东西稍一多,人脑的处理能力有限,它就影响我们对问题的判断了。

更关键的问题是,这样做很多时候,是为了掩饰高层逻辑链不严密。
而架构设计的目的就是为了让高层逻辑严密。高层逻辑可以粗糙,
不表示它可以不严密。我们可以不细化坐快车还是专车,但我们不能
不考虑例如“最近公司不让报销打的费用”这个要素,无论你打什么车,
也不论你打车的时候对司机的语气好不好,如果你不想自己付钱,你
就不能选打的这个方案。如果你选择其他公交路线,你的计划,时间
都要改变,这才是高层设计要关心的问题。

所以,构架设计如此关注所谓的“关联”。当一个逻辑修改的时候,它会影响其他逻辑,我们就说他们有关联,有多个关联的东西,才值得放在同一层的逻辑里面讨论。

比如下面这个设计:

../_images/%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E5%85%B3%E8%81%94.svg

在例1中,配置文件和每个模块都有关联,我们对配置文件的要求就很高,在高层逻辑里,我们必须很小心推演一下配置文件应该用什么方案比较好。

而在例2中,配置文件只和testbench发生关联,再转化为testbench对sysinit和testcase的要求。这时,配置文件的格式在高层设计中就不是个大不了的问题了。因为只要我们推演好testbench和sysinit/testcase两个模块的关系,这个约束是自然会发生的,配置文件格式怎么变化,都改变不了这个高层逻辑,我们就可以把这个逻辑留给testbench的内部分析去做。我们先分析好testcase, testbench和sysinit之间的关系,以及它们是不是可以实现我们的设计目标,有没有更好的方法,这才是最重要的。否则,你就会陷入格式这个细节问题上,丢弃了高层逻辑的设计工作了。换句话说,你的构架设计,没有起到保护细节设计的作用,变成细节设计本身了。

当然,我们分析testcase-testbench-sysinit这个高层关系的时候,会涉及到这个testbench是否可以被实现,实现风险是否大的问题。我们有可能会需要分析一下配置文件应该怎么做的问题。但这个分析的目的仍是为了证明我们高层设计的可行性,它和我们放下心来,觉得高层设计已经通了,专门去讨论在高层逻辑的约束下,下一层设计怎么做这个问题无关。

3.242.3. 外延

3.242.3.1. 架构的信息本质

上面这个定义,拉高一个层次去思考会很有趣,可以让我们进一步贴近架构设计的本质。

架构设计是设计一组约束(限制)。那么,什么东西会成为下一层的约束,什么东西需要成为下一层的约束呢?毕竟如果我们最后把软件开发出来,每个约束都是约束,并没有什么不同,为什么有些约束就会被我们作为高层约束来引入,必须被我们作为下一层的约束来引入呢?

我认为,这和人思考模型有关。我在这里讨论过这个问题(说明:本文的链接,都不在本层逻辑链中,读者可以在读完全文后,有兴趣的时候再去看):

没有人介入控制的时候,外部事物是直接被它的物理属性所左右的,呈现出一个“没有什么规律”的感觉。好比雪花落入大地,堆高了会掉下来,平了会被新的雪花破坏变得不平。而当智慧生物(包括人)介入控制的时候,智慧生物需要进行信息加工,把一个外在的,包含无数信息的外部对象,形式化到它的脑子中(这有点像Digital Twin的概念)。这是个高成本的行为,所以智慧生物需要降低被控制对象的信息熵,所以它不能处理全部细节,它必须抽象这些细节。所以我们不能处理“全班成绩”这个信息,我们必须处理“平均分”,“中位数”这样的信息,更好的结果是:“全班的成绩都一样”,或者“这些分数构成一个等差数列”……这样的世界更容易被“智慧”所控制。所以,蜂巢会挂到树上,人类的房子会变得规则。智慧生物必须让无规律的信息主动变得有规律,有规律它就不需要一个一个个体去控制它,他们可以用统一的方法去控制它,这节省了大脑的资源,更复杂的控制只需要是这些Pattern的重复(也就是保证低熵)就可以了,这个问题我在这里讨论过:

我们制造高层限制,就是为了人为地降低信息熵。我们无意识地从深圳走路去北京,在路上遇到问题解决问题,这整个过程不受控,你无法对这个过程思考什么,人脑没法建立一个逻辑去向这个方向努力。但你拆成“打的去机场”,“坐飞机去北京”,这就可控了。我们遇到细节障碍的时候,有一个就近的目的去管理我们的逻辑。

这个高一层的逻辑结构,就是人脑人为制造的架子,就是架构设计:

../_images/%E6%9E%B6%E6%9E%84.svg

它的目的就是让我们在做细节逻辑的时候,有一个依附,在发展的时候,有一个决定如何变化,能否变化的基础。所以,架构设计确实就是个架子,它是整个设计的逻辑的架子。我们做这个架子的时候,“看不见”它里面的细节。

我们把部分的逻辑链整个放在一个大的,人为维护的“抽象逻辑”中,我们就可以整体控制这个系统。我们所有的软件都做成软件包,然后给软件包以“安装”这个属性,那么无论是驱动,Office,音乐播放器,还是编译器,都可以被“安装”了。

但为了“安装”这个抽象出来的属性,我们就需要对被抽象的底层逻辑进行约束,人为地让它呈现这个属性,否则,每个软件有自己的逻辑,不统一到这个抽象下,那么我们就无法统一控制它,只要这个系统复杂度一高,它就不可控制了。

所以,一开始不保证高层逻辑链按一个方向去构建,到了后期,系统已经成了混沌系统,这时再要控制什么,就没有任何办法了。

所以,细节逻辑是天道,高层逻辑是人道。不符合天道,事情不会发生,不符合人道,事情不可控制,目标失去活性。

目标本来就是因为人而存在的。否则万事皆为天地,我们什么都不需要想,不需要做。

高层逻辑是为人而存在的,所以高层设计的目标就是两个:

  1. 让天道(细节逻辑)有机会变简单

  2. 实现目标

细节逻辑在架构阶段是个“不知”,我们预期我们逐步细化我们的高层逻辑的时候,会不断碰到新的“知”,这些新的知,会导致我们的高层逻辑不成立。我们希望我们的高层逻辑可以为这些细节留下自由度,让它能导向我们的目标,同时仍不和细节逻辑冲突。所以,我们建立逻辑链,让它指向我们的目标,这是肯定的。而对于每个抽象,我们怎么保证它有最大的机会响应新的“知”呢?

我觉得主要有两个策略:

  1. 经验:类似抽象的成功结果的信心。比如我曾经打的去机场,这个事情我做过几十次,尽管没有人能保证这次一定行,但它的可能性更大。这里“打的去机场”是个抽象,包括各种细节的一个总结,这一次准备打的去机场是相同的抽象,它成功的可能性也很大。

    这是种信心问题,类似的方法在部分情况就不见得有信心。比如“很多人成了富翁”,“我也是个人”,“所以我应该也会成为富翁”。这你有信心,我可没有。所以这是个度的问题,但这种比较常常可以让我们对细节更有信心。比如,我们可以有这样的判断模型:既然某项目的自组织网络的选举协议可以实现,那么我们在我们的数据中心中实现自组网的可能性就存在。这同样是个信心问题,但有这种类比比没有这种类比信心更足。

  2. 利益:没有人加入控制,系统就是无序的发展过程。加入人的控制,它就有可能走向我们希望控制的减熵方向。所以,在经验的基础上,利益需要在我们执行的过程中,一步步被送入系统中,保证它仍可以有动力,否则最终它就会回到混沌发展的状态。

经验没有什么好说的,我们这里重点谈利益。

利益是人力投入到系统控制中的动力。

有人希望控制软件的安装,他们投钱给我们去做一个“发行版”,我们基于这个逻辑链去建立一个控制,这个就是可靠的。你个人喜欢用C++,你建立一个控制,看怎么让所有模块都用C++编译,这就是不可靠的。因为你付不起这钱。这样,你的高层逻辑就不会有人力去帮助你支撑。

所以,高层逻辑的“目标”是很好确立的,就是谁为这个目标付款。

这个道理看来很简单,但到了实际情况,还是很多人会犯错。比如我们反复讨论要统一VF,要支持ACPI,但我们却没有人愿意去深究统一VF,支持ACPI反映出用户的什么直接利益。这种情况下,我们的逻辑链就是断裂的。我们的高层逻辑控制不住整个利益的逻辑链,我们所有的细节设计上的努力,就无人埋单。这样做出来的设计,就不会有竞争力。

没有这个高层逻辑,我根本不知道怎么去评审你的细节逻辑,因为细节逻辑不知道自己努力的方向是什么。连坐飞机还是高铁去北京都没有确定,你和我讨论是打出租车好还是快车好,这完全没有意义。你先要证明你去北京到底能得到什么收益,基于这个决定了是坐飞机还是坐高铁,我们才能放心跟你讨论打的的要求。

同样,你的网卡MAC在MAC层进行抽象还是在PHY层进行抽象的逻辑没有确定,你跟我谈Bonding的时候MAC地址应该怎么设,我也无法判断你的逻辑。我们的目标首先按必须是满足了什么人的利益要求,无论是有钱的股东,还是潜在的客户将为此埋单,这是第一层的逻辑,第一层逻辑,无论如何也不会是直接是某个技术细节的。就算是某个打老板财大气粗非要说我就是要做一个“叫作MAC”的东西,我们都可以分析出他可以持续投钱的动力在哪里,否则我们只要直接把一张废纸叫它“MAC”就行了。

3.242.3.2. 细节逻辑和架构逻辑

从这样信息的角度来理解架构,就很容易解释为什么在架构设计层次,很多设计我们要说它“看不见”。

你一个大系统,要加一个功能,看到一个具体情况,就去解决它的具体问题,这总能形成一个逻辑链,如果在实施的时候,遇到某个具体的情况不能实施,就根据需要换一个方向,很多时候也能满足你的要求。但按这种方式运行一段时间,里面的各种逻辑链就是没有统一的规律的,因为它们都被细节所左右,并不呈现“人之道”,无法被人脑建模,那我们也就只能具体问题具体分析,一旦具体问题具体分析,我们也没有办法进行规划,调度,解决更大更难的问题。散沙建不成房子,散兵打不赢战争。

所以,如果不在我们上层逻辑控制之下的某个特定的细节,我们不认可它是个规律,因为一旦遇到障碍了,我们随时是可以变化的,那个变化,恰恰就是我们架构可以为发展建立的自由度。

所以,很多时候,你不能告诉我“XXX难道不是XXX这样的”,我不在乎它是不是这样的,我关心的是那个被我作为稳固架子的自由度,而不关心那些可以用来处理变化的细节逻辑。

架构这么难理解,《道德经》一类的战略和哲学的逻辑这么难理解,本质就在这个地方,它没有具象,它要求你在“抽象”的层次上看到规律,而且基于这个规律来建立你的设计逻辑链,一旦你盯着具象,你就失去抽象本身了。

不知道读者是否走过高空玻璃桥?在这种桥上走,如果你盯着眼前的地面,把眼睛聚焦在玻璃的表面上,你会觉得这只不过是一座普通的桥,但如果你把眼睛聚焦点放到玻璃后面的山川河流上,你就会觉得目动神摇,再也不知道如何向前迈步了。

备注

这也解释了为什么在进行设计的时候,我们尽量用抽象语义去贴一个具象的情况。比如我们不会说我们做一个针对SSD的LRU算法(参考Linux内核5.8的特性:https://kernelnewbies.org/Linux_5.8#Better_behavior_in_memory_thrashing_situations),我们会说我们做了一个针对“随机IO设备的LRU算法”,后者实施了一样的逻辑但大大增加我们的覆盖范围,它就更有机会成为一个架构逻辑。而架构逻辑越多系统就越可控。

3.242.3.3. 架构的脾气

这样我们还会有一个有趣的推论:没有了架构,一个软件团队会变成一个外包团队。

很多硬件使能团队就是这样的:

操作系统有人做了,为了操作系统可以维护进程,可以调度虚拟机,可以迁移应用,操作系统团队就是有脾气的:硬件必须实现某某功能,上面的软件必须这样用我。它建立了一个属于自己的高层逻辑链。这个逻辑链必须被保证,否则埋单的用户维护进程,调度虚拟机这个目的就不会得到满足。

硬件也有自己的高层逻辑链,比如投片费用是有限的,可以用的工艺是有条件的,要符合某某要求软件只能如此这般才能用我。要保证能投片,你必须满足我如何高层逻辑。

但使能团队就可能没有逻辑链了:啊,操作系统要我这样啊,我看看细节上能不能满足吧,噢,硬件接口要我这样啊,我看看细节上能不能做到吧。这样,做高层判断的时候,这个使能团队的话是不需要听的,因为你都是就是论事的而已。就好比从深圳到北京的故事中,一个提行李的跟班,坐飞机还是高铁,其实都不太有所谓,到时别提不动行李就行。他不在高层逻辑中,不是高层逻辑的控制要素。

所以,一个设计团队要能持续发展,没有架构是不行的,而要有架构,就需要有自己的利益链,整个高层逻辑中,必须有你在保障的客户利益。而且你的保障逻辑链必须是在所有解决方案中是有竞争力的,否则你会被整体替换。成为资源团队,是把自己的整体替换可能性放到最大。

而全局的控制者,也不会希望这样的团队成为组织主流,因为这样的结果就是整个系统越加的不可控。大部分地方都是不平滑的表面,信息熵极高,系统就不可控了。

3.242.3.4. 顶层架构

根据前面的讨论,架构是分层的,每层以上一层定义的约束和目标为条件建立本层的逻辑链。而最顶层的逻辑链从利益开始。

对于这个抽象,有必要举几个具象让我和读者有接近的认知。

因为“共同认可的商业目标”,并不那么容易有共识的。组织组织一个团队,解决组织没有任务管理这个问题,这个目标很直接,这可以是我们的逻辑链的建模目标。

组织一个团队,开发一款手机。这个目标就不直接了,到底开发的是智能手机,还是传统手机?手机是否需要支持5G,这没有人给你约束,这不表示这不能成为商业目标。架构本来就是要你主动发现约束,是要你的创造力,而不是你的体力。

现在有一个项目终止了,人都没有事干,想办法让他们赢取利润,至少平衡他们的工资成本,这同样可以是一个商业目标。

但开发一个操作系统,分成内核和用户两个特权级。这不是目标,这个逻辑链不能作为顶层架构。你首先要从利益上说明“开发一个操作系统”这件事本身是有逻辑支持的。

这个地方也有一个很有趣的推论:设计本身是信息选择的过程。我们进行一层层向下设计,让抽象变成具象。抽象有很高的自由度,简单建模,最开始的自由度很多,我们建立高层抽象,比如我不关心你如何到机场,你可以在x1的定义域内选,我也不关心你具体坐哪班飞机,你可以在x2的定义域中选,所以高层逻辑是一个函数:

F1(F11(x111, x112...), F12(x121, X122...)...)

其中的xnnn是那个子逻辑的变量,是我们为细节选择不同的路径留下的自由度。设计细化的过程,是每个子函数进行选择的过程,每次细化,都是把变量变成常量的一个“选择”的过程。

所以,高层设计不包含下层设计的信息。所以要求无限细化高层设计,或者要求细节设计可以从高层设计的约束本身上推出,本身就是缘木求鱼。我们能理解人意欲上希望有这样“结论”,但这个“结论”本身是不可能达成了。错误的期望导致错误的行为。

3.242.3.5. 架构的浇灌和成长

架构是上层抽象,是高自由度的范围定义,但最终真正定义它是怎么样的,是它的细节,而细节是不可控的,所以,控制架构的另一方面,是控制细节的环境。

比如说,你做一个系列的芯片,一个新的操作系统,改进一个数据库,在构架逻辑上,你选定了平台,选定了分层,选定了应用环境。那么,具体安排多少级流水线,系统调用的时候是否需要保存当前堆栈,查找的时候是否对meta data上锁,这些就是细节问题了。但你的架构还留着巨大的自由度,这部分的自由度怎么控制呢?架构不能完全控制这些东西,这时我们需要控制的就是需求。你要做一个服务器芯片,你总拿嵌入式芯片的需求去浇灌它,最后做出来的比如是个嵌入式芯片。因为你的细节是想嵌入式芯片走的。同样,你一个关系型数据库,你总用NoSQL的需求去浇灌它,你得到的也必然是为非关系型数据的性能为目标的数据。

所以,架构的细节,不能只靠对比架构的高层逻辑去控制,我们还需要控制它经过的市场。

3.242.3.6. 融入上层架构

所以,我们还要关注一个我们其实总要面对,但在描述中被我们忽略的问题:我们进行架构逻辑的建模,很多时候,是为了让我们的概念空间和我们的环境可以融合。

你做一个操作系统,用户API都不一样,那你就要打算好全部重写基础库了。但这还好,我们至少可以封装一层,不至于需要重写上层的库。但你连线程的概念也没有,Socket的概念也没有,甚至连顺序执行的概念都没有,那你就等着所有应用都自己重新了。

我们做一个系统,不可能把整个世界都推翻了的,你一定是以某种方式严密地和旧世界融合在一起,一个bit都不能有错。但我们也有纠错能力,我们可以通过一些变形,让我们一个bit都没有错地和旧世界关联在一起。但如果我们的概念空间都完全和就世界说不到一起去,那这个东西最终就会有无数我们处理不了的bit,让我们无法和就旧世界共存。这在大部分情况下,这被抛弃的不会是旧世界。

所有,没有高层概念空间的架构没有生存能力。

3.242.3.7. 实事求是

架构设计在很多地方最难的一件事是实事求是。架构设计必须是架构设计,而不能是“做架构设计”。换句话说,架构设计的目标必须是一个商业目标,而不能是架构设计自己。这叫外其身而身存。

架构设计的目标是为了达成那个商业目标,不是获得“这个架构设计不错”这样的评价。所以无论如何我们不应该出现“这样画一个架构视图是否符合架构设计的要求?”这样的问题,架构设计没有要求,架构设计的逻辑都是为架构设计的商业目标服务的,不存在“做架构设计”本身产生的设计约束。我们会有“这样设计架构不好”的评价,但这个评价不是针对架构设计的规矩的,而是针对这样设计导致的商业目标被损害来说的。有时我们还会说“这个架构不符合架构我们的架构经验,因为关联太多了”,这个评价也是针对商业目标无法达成来说的,不是针对架构设计必须有什么规矩这一点来说的。

所以,如果不能实事求是地看待架构设计工作,认为架构设计不是设计之外的一个设计,架构带来的是一个伤害。因为你在商业目标之外引入了额外约束,而我们架构设计的自身目的就是(在达成商业目标的前提下)减少约束。

所以,不强调实事求是这个前提,即使你完全执行我们前面提到的要求,最终它仍不是架构设计。

以前面深圳去北京的例子为例,我们可以坐高铁,也可以坐飞机。要让逻辑链可靠,我们应该调研和比较两者的优劣。但这样让成本提高了,我们不值得提高这个成本呢,我们的架构设计就是“我们直接选择坐飞机”,你要问“为什么不坐高铁?”,我答不上来,但我的选择就是“坐飞机”,问题我也解决了,这是实事求是。不能为了逻辑链完美,非要做各种调研,空耗资源。我也不会故意说“高铁它不稳定,高铁不环保”,这同样不是事实。这个思维模型,就叫“知不知上”。我不知道,但我不认为我就需要知道。这才是实事求是。

我个人更喜欢把这个要求叫“守弱”,因为“实事求是”是一个表扬自己的表述,人们会把自己的期望叫做“实事”,把不希望做的事情叫“不是这样的”。最终还是无法面对现实。

而守弱的要求是,我要主动呈现自己的弱点,所以“这个我也不知道”,“那个我也不知道”,但“我知道这个”,它也支撑我的逻辑链了。所以,追求“守弱”,就是实际意义的实事求是。

3.242.3.8. 关于抽象能力

这里加一小段说明一下:抽象说起来是个简单的能力,但它是需要经验去支撑的。有些读者觉得我这里讲得轻描淡写,好像做个抽象很容易,或者好像听了这里说的理论,就掌握了架构设计的方法。这个我不知道怎么解释,但我想一些实际的例子来说明一下抽象过程本身的复杂性。

前段时间我们有一个模块收到一个需求,这个模块可以接受多个设备驱动的注册,从而对外提供功能,类似这样:

../_images/uacce%E5%A4%9A%E6%B3%A8%E5%86%8C%E6%9E%B6%E6%9E%84.svg

这个框架预期每个设备只对框架注册(以下称为Reg)一次。但有些设备有多个资源,所以他们希望可以注册多个功能上来。但框架模块在注册的时候需要调用设备的一些函数,对设备进行处理,如果一个设备注册多次,设备处理的流程就会被执行多次,这就错了。

所以,框架的维护者准备升级他们的功能,在框架中维护一个资源池,跟踪所有的同设备注册(称为一个ctx,ctx映射一个设备和它的多个reg),如果注册上来的是相同的设备,就在资源池中记住它,后面进行设备处理的时候,就不会重复了。

评审的时候,架构师A评价说,这样在全系统中多制造了一个资源池,因为从全系统的角度,明明是有人知道注册是重复的,这个人,就是设备驱动。它对着一个硬件,可能有针对这个硬件的统一数据的,所以,在里面放一个变量就可以管理这个对应关系了。不需要在框架模块中放一个pool,有pool就必然有查找的功能,这个逻辑转折了。

B反驳说,这做不到啊,驱动注册上来的时候提供的device数据结构是Linux驱动框架定义的,我们不能在里面加成员啊。

你看,对部分人来说,在一个接口上加数据,要不就只能在驱动的抽象(device)上加成员,要不在注册(reg)中加成员。前者不能改,后者以注册为index,无法放和device 1-to-1的mapping。但对A来说,这属于细节,只要在Reg中加一个回调函数,要设备驱动给我返回一个和它1-to-1 mapping的数据结构,这个数据结构就被从我的pool中转放到设备一侧了。

对A来说,这个修改,完全不变化架构,只是存储的放置的地方变化了一下而已,它是包含在A的抽象中的。而对于关于代码细节的B来说,所谓关联,就是对象或者模块中有一个对另一端的指针。这个你说谁对呢?其实没有谁对的,但抽象的层次不同,这个含义就会不同。在这个例子中,我认为A的抽象层次是更好的,因为A维持了一个规律,维持了设备驱动和应用之间的独立性,却没有被一个细节拦住了程序的发展。这个抽象更好,但如果没有这样的抽象经验,这个关系就会被弄复杂了。这个看细节是完全看不出来的,因为A的逻辑就不在那个细节上。

所以,抽象本身也是需要经验支持的。如果你缺乏这种经验,或者你会选择不好的抽象属性,或者你干脆就不知道两个模型其实是相同的。这种经验很难作为固定的知识传递出来,因为它抽象是提取被抽象对象的特征,这种特征无数,说出来本身就是抽象,说不出具体问题时的自由度,最终这个东西就很难进行“传授”,但也许你必须接受,抽象能力不是你学习一个概念就能掌握的东西。我们只能通过反复对一个个真实实例的对齐,才能让这种技巧在不同的人和团队之间传播。

再看一个例子:有一次我们设计一个芯片的接口,为了让软件控制芯片的功能,我们做了一个MMIO空间作为软件的控制接口。芯片设计团队在研究了软件在接口上的需求后,给架构团队反馈说,他们准备内置一片真正的内存去响应这个MMIO空间,所以,他们在描述的时候,就直接把这个MMIO空间称为Buffer。架构师A评价说,如果这个空间是一个Buffer,我写入的数据什么时候生效呢?芯片方B说:“我可以跟踪这个Buffer写入的动作,如果发现发生更改了,我就会做相关动作来让它生效。”

A说:“这不对,首先,如果我们认为这是个MMIO接口,我就会认为我的读写动作是原子的,也就是说,我做了这个写的动作,中间不会有其他机会被打断……请不要打断我……如果你做了什么动作能让它不被打断,那么,这个在软件的眼睛中,这个只是MMIO,而不是Buffer。”

请注意了,在这种讨论中,A那句“请不要打断我”常常是必要的,因为很多时候B马上会反驳“我可以xxxx”,A和B对同一个名字抽象的抽象是不同。

但在这个讨论中,A的抽象是更好的,因为软件和硬件是两个独立的设计团队,它们是两个独立的分析问题的逻辑,所以我们希望软件和硬件的名称空间是相对独立的。这样,两个模块(软件和硬件)之间的接口我们希望尽量简单,把复杂度留到他们里面(但这个也是个平衡,主要是针对性能的平衡)。那么对于这个接口抽象来说,到底:

这个空间是一个MMIO

是个更简单的关联呢?还是

这个空间是一个内存一样的Buffer,但当你访问这个成员以后,
结果会立即生效,访问那个成员的时候,要等到xxxx条件按成
立的时候再生效,……

是个更简单的关联呢?

备注

这个例子其实提醒了一个我们比较容易掌握的要领:注意你定义的各种概念的“空间”边界在哪里,软件的概念需要留在软件的空间中,硬件的概念需要留在硬件的空间中,VM的概念在VM中,Hypervisor的概念属于Hypervisor里。进程认为线程就是连续的(一定程度上),OS调度器就是认为线程是分时的。这是我们进行抽象和概念空间建模的本质。你不能站在上帝视角上进行设计,在细节上你看得越清楚,在抽象上你越看不到。你专注美女皮肤的毛孔,你就看不见这个美女。

在两个部分中间切一刀,谁都能切,但能切得好,是个综合逻辑问题,因为所有关联都是有多维的。一个基于Socket的Client和Server程序运行的角度看,和操作系统的协议更紧密,但我们通常建模不建和操作系统Socket接口的关联,我们建Client和Server的关联,因为操作系统的Socket接口已经很稳定了,我们不会改变它了,而Client和Server是变化的要素,这两者的关联就会突出来。所以,关联并没有确定的方法决定什么必须是关联,关联是一个选择,而这种选择是一种设计艺术。

3.242.4. 建议

前面我又定义了一次架构设计的本质,但其实我想提的建议不是前面的这些。只是我要说一个操作建议,我需要把高层逻辑建稳了,我谈的细节才有根基,否则说了也是白说。

我想提的建议是:我期望的架构设计,很多时候,只需要几页的文档就可以描述清楚(但工作量很可能不是不动脑写两三页文本那么大)。特别是很多特性一级的架构设计,你能搞清楚你的开发视图就够了。我宁愿你搞完这个高层逻辑,有时间马上投下去给细节设计设计一个逻辑,或者赶紧开始写用户手册,也别怕自己只写两三页显得不够高大上,而故意给我弄一大堆的细节出来。你担心一下你后面直接依靠设定的标准,细节会走偏,比你多写点字靠谱得多。把可以变化的细节逻辑变成高层的约束,这会让整个系统的逻辑失去活性,哪里都该不动,那你就不是在做架构了,你是在自缚手脚,你还不如别做这个设计呢。混沌至少还能用,锁死自己那是直接自杀——虽然大部分时候实现团队不会那么蠢,他们会忽略你的设计,但我也浪费时间了呀。