仓库源文

.. Kenneth Lee 版权所有 2021

:Authors: Kenneth Lee :Version: 1.0 :Date: 2021-09-27 :Status: Released

什么叫做“没有设计成份”


介绍

最近评审一些构架设计的时候,经常不由自主地用了这个评语:“这个设计文档里面没有设 计的成份”。

所以,我就想着要深入定义一下这个概念,什么叫“有效设计成份”。

首先,我们需要讨论一下为什么需要设计。我经常举从深圳去北京这个例子。你决定要从 深圳去一趟北京,是需要在脑子里进行一些逻辑思考的:坐飞机还是高铁?要带什么?去 住几天?等等。这些就是设计。它是“需要”的,否则你在中间过程中肯定要出各种各样的 问题,比如到了机场没有航班,买了机票去机场误点,到机场已经是半夜没有公交,甚至 到了北京要见的客户去了深圳之类的。所以,“设计”是一种“必须”,不是可有可无的东西 。

反过来,如果你只是下楼到对面小卖部买包烟(举例子而已,我个人极其讨厌抽烟:)) ,你就不需要这种“理智”的思考。你可以穿个大裤衩,捧着半个西瓜,慢悠悠踱下去,一 摸口袋,“我靠,忘了带手机了,老板,先佘一包红塔山,明天还你”……

如果你写一个“设计”文档,说你怎么穿这个大裤衩,怎么捧这个西瓜,西瓜汁怎么流到你 的嘴角,这确实“正确”地形容了你买烟的过程,但你说这是“设计”,我是不认的。

这样的文档,就叫“没有设计的成份”。简单地说,没有理智思考,我自己慢悠悠踱下去就 能搞定的事情,你还要把这个过程写一个“数字孪生”在纸面上,还自称“这有利于后面的人 更好地买烟”,我肯定觉得你神经病。

有些人甚至把这个穿大裤衩的“优秀实践”写成规范要求所有买烟的人遵守,还振振有词说 :“不穿大裤衩难道裸体出门?你有没有羞耻心啊?”,或者“难道平时在家里也需要西装革 履?这是不是太不人道了?”。甚至说,“你们自己说到底穿什么?你们摘一个,我也不是 不民主,但咱们得把规矩立起来不是?”

这叫抓不住重点。

理解抽象

上面是一个具像,这是比较简单的,我用一个例子来给你说明我要形容的“那个东西”。这 每个具像,我强调其中的重点是什么,需要用很多的具像进行交集运算,你才会体会到我 强调的那个重点是什么。这些削掉那些我不关心的信息的过程,就是抽象。

我们从前面那个具像映射到开发代码中的一个具像,加深读者对抽象的理解:比如,写一 个函数,计算13427+26437等于多少。这就是一种“具像”。这个函数很好编(直接返回 39864就行),这个其实也是一个抽象(数字表达什么意思这本身就是一种抽象),但对于 我们马上要加大的范围,这个相对来说,就是一种具像。

好,现在让我们加大我们对算法的使用范围,我们用x表示被加数,y表示加数,两者都是 64位整数,我要求给出一个算法,计算x+y等于多少。这相对前面那个要求来说,就是抽象 ,它在64位整数的表达范围内,无论取哪个值,一样成立。

现在让我进一步抽象:x,y表示某类计算机中可以表达的所有数据类型(整形,浮点,向 量等等),无论是什么字长,请提供算法,计算x+y等于多少。

我们还可以继续来:x, y表示高等数学中可以表达的所有数值(任意长度,包括实数,超 越数,虚数和向量),x和y拥有共同的上域,op表示一种可以和x,y所在的上域构成一个 半群的运算,请提供算法,计算x op y的结果。

……

这也是一个抽象问题的具像。

所以,理解这些一个个例子容易,但真正要抓到设计这个问题的本质,是不容易的。中国 古典文化中那些“君子”,看不起“小人”的资本就是君子之(抓住)道,而小人之器,所以 劳心者治人,而劳力者治于人。我们今天提倡平等,不小看任何人,但由此我们也应该看 到,至少在“决策”这个角度,能抓住抽象这件事是一种“竞争优势”。我们不能认为它可以 被轻易得到的。

我先要说明白这个问题,是提醒读者我们在讨论一个不容易的问题,你别在那里给我掰那 些入门级的代码怎么写的问题,我们在谈的是“很多代码”怎么写的问题。

设计抽象

所以,设计其实是一种抽象。是我们在我们要做的那件事中,抽取了部分的属性,确认我 们在具体做那件事的时候,需要坚持的约束是什么的那些约束本身。所以,设计不是做那 件事的信息本身,而是被正确挑选的信息。挑选的信息不正确,这个设计就失去了意义, 它就“不包含有效设计成分”。

那么,我们抽取哪部分的属性才是合理的呢?第一个要求:它必须是一个能导向我们的目 标的逻辑结构。我们做整件事,为它付出劳动,不就是为了达成目标吗?你去一趟北京, 目标是什么?参加会议?说服供应商?旅游?定居?明天一早就参加会议,你还挑什么航 班,飞机类型啊,赶紧看看剩下那些飞机哪个能保证今天之内飞的,赶紧走。这个约束才是 第一位的,其他那几个飞机类型什么的,去死!

这才是设计。

我举这个具像,可能你会觉得自己“早知道了”。那请回去看看你们的设计文档,你们是不 是首先讨论这个问题?还是人家让你去北京,你马上就开始讨论机票怎么买的问题了?你 觉得在实际中这是个怎么做的问题?这是个经济利益的问题啊。马上开始订票,你会做得 很快,但做错了,选择“航班,飞机类型”这些作为约束,责任在谁身上?“他说要去北京的 ,没说要明天到啊”。你看,都撇清了:不是我的错。

做一个特性,要落地哪些版本?我们手上有多少版本?这些版本后面怎么升级?三个客户, 我们先保哪个?哪些版本需要先上?我们做这个特性是为了阻击特定对手,还是作为我们 长远的竞争力?我们这个版本,哪个是长远的,哪个只是用于特定市场域和特定生命周期 的?……

这些问题,讨论任何一个,都是成本啊。所以,最好别讨论了,最后锅砸谁头上谁倒霉。 这才是不做这个设计的本质,哪里是不会做这个设计呢?抽象是要成本的啊。计算 13427+26437哪里不比计算半群简单啊。抽象是对很多很多的具像分别地做具体设计啊。是 提早让所有人面对我们脏的一面啊。所以,你再回去看看你的设计文档,在哪里面对这些 脏东西?你们除了把好的一面吹出花来,哪里在面对这些脏东西呢?我们提早做“理智”的 设计,就是为了面对这些脏东西啊。那些花,就叫“没有设计成份”。

抽象的逻辑也是逻辑,也是“因为所以”,所以要装样子,具像的逻辑也可以装成抽象的样 子,反正代码还没有成型,你不动脑思考,那个抽象代表不了具像,谁都不知道。我评审 的很多架构设计文档,常常经过很多人的评审,甚至已经Pass了的,但我随便挑一个例子 来问特定的“具像”是什么样的,常常就回答不上来,其实这些人哪里有做设计啊,他们就 是拿Word或者UML工具写代码而已。然后你问他为什么后面升级不了,修改不了,人力降不 下来?呵呵,他们自己都不知道。他们建了一间房子,里面全是细节,没有任何规律,已 经成了这个样子了,你问他们改一个开关结果会怎么样,他们自己都不知道,讨论这个问 题有意义吗?从一开始他们就没有控制这个规律啊。

逻辑闭包

好了,这样我们就讨论到“控制规律”这个问题了。什么是“规律”?这其实是个“主观”的形 容,是人脑对复杂的信息集合的一种“总结”。

每天都有太阳在天上,在天上什么位置?一号在头顶不动,二号在东西方横跳,三号不出 来,四号……这就是“没有规律”,你看我给你描述的文字的多少就知道了,越多的文字意味 着越多无法合并的信息,越多无法合并的信息,就是“没有规律”。

有规律的信息特别简单,“太阳每天从东边升起,在西边落下”。这就是“规律”。

所以,设计是什么呢?设计是控制这种规律。你100人的开发团队,这里增加一些代码,那 里增加一些代码,你怎么知道这些代码会如何发展?

但那么多的代码,我们怎么控制它呢?架构设计领域的优秀实践就是“View”,所谓“视图”。 什么是“视图”呢?其实就是一个抽象:比如我单独从概念角度讨论一下这个功能怎么做: 你们不是有很多计算任务吗?这样,我这里有很多的计算单元,你们的每个计算任务我称 为一个Task,每个Task我给一个属性,叫Runtime,我给你们每个人一个Runtime Quota, 我们按这个Quota来给你分配计算单元的时间和算钱……

你看,这就叫“概念空间建模”。为了说明这些Runtime,Quota,Task的关系,我会画一个 类图来表达它。但重要是是这个类图吗?重要的是这个建模,这个逻辑本身是不是合理啊 。

比如我来挑战一下前面这个建模:你给我一个Runtime,是用时间来计算,但我的计算用的 都是标量,我用的不是向量,我的耗电不足他们那些用向量的Task的十分之一,你凭什么 收我一样的钱?

看见没有?我们评估一个概念模型(Logic View),评估的是它在建模概念的时候,是否 能更好解决问题,我们甚至可能要分别建两个模型,分别讨论这两个问题。所以,哪个建 模放在前面,那个放在后面,让时间优先于功耗,还是让功耗优先于时间,这个才是问题 的关键。我们在为未来搭建细节的时候建立高层逻辑,选定正确的方向,不是让你用模型 去描述代码啊。

模型最后当然都是要落地的,但落地的代码和模型可不见得有一一对应的关系。甚至我们 都不保证所有模型都能完美落地,大部分能落地,就谢天谢地了。但你难道想告诉我,你 不建这些模型,你直接开始写代码,就能保证最后达成目标?100多人各自设计写代码,最 后就能放在一起?

所以,这里我们就能看到我们对设计文档的第二个要求:它必须由一系列的逻辑闭包组成 (逻辑闭包的概念我在这里详细解释了:\ :doc:逻辑闭包\ ),每个逻辑闭包必须是指 向目标的一部分。

这里其实包含了三个子要求,首先,证明每个选择(或者约束)的逻辑空间本身要足够小 ,你不能写个300页的文档,这里有一点点这个意思,那里有一点点那个意思。你必须在一 个一两页的篇幅中,证明一个连续的逻辑。这个空间里必须只有我们关心的有限的属性。 证明有限的概念。否则,老实说,你自己估计就不信你这个选择。如果你理解不了这个抽 象,你就具像地想一想函数的设计:你能看懂一个三万行,前后关系跨月几千行的逻辑吗 ?你控制不了这样的函数是否能成立。你同样也控制不了逻辑跨越几十数百页的设计文档。

第二个子要求就是前面已经提到的,这个逻辑空间必须最终证明我们预设的目标。它要不 用于证明目标,要不是证明目标的其中一个证据的一个独立空间。

第三每个逻辑空间(闭包)内部是一次“综合”。换句话说,它必须表征某种“全集”,而不 是部分的情况。还是用具像来理解:你比如要在一个PCIe的协议层上做一个CC协议,这个 协议要和其他内存协议,锁,路由等机制一同描述,你不能单独描述自己这个协议,别人 怎么样你完全不管。这样没法知道你这个方式是可以成立的还是不能成立的。这同样没有 设计的成分在里面。

所以,整个设计,其实是个非常严密的逻辑树,树根就是我们的目标,第一层是一组证明 这个目标的视图,下一层是证明这些视图中的子证据的其他视图。每个加入的东西都是有 关系的,不是在代码中随便挑一点信息摆上来,甚至它的很多信息,和代码没有一一对应 关系的(典型的比如状态机,内存分布,线程组合,和可靠性模型,都不是和代码一一对 应的)。

所以,本质上视图就是逻辑闭包。但视图这个词语是给专业架构师写的,发明者估计没有 想过有些人是连基本的逻辑技能都不具备的。比如4+1视图,为什么要分多个独立的建模角 度呢?你去解题,只抽象概念模型,比如前面这个Runtime的模型,只是在用的人和实现的 人建立一个讨论问题的基础,控制了功能提供的逻辑,但到底要交多少个市场,多少版本, 哪些模块可以共用,那些模块不能?那个模型完全不管。那我就得把这些属性正交地提出 来,我们单独讨论这个问题:有多少版本,多少模块,模块的版本怎么定义?模块组合后 的版本如何定义?所以,这么个问题,你会发现画出来仍是一个类图:因为所有的建模, 它本质都是集合和集合之间的关系啊。

我强调逻辑闭包,是更强调它的逻辑性。你必须独立把相关的属性都给我放到一个有限的 空间中,我们可以在这个空间中,反复把玩我们手上的条件和我们要达成的目标。比如, 前面这个Runtime和功耗的问题,我把条件属性单独抽出来,我有如下信息:

Task的运行时间,Task消耗的功耗,用户愿意花的钱,Task的完成时间,我消耗的钱。

我要得到一个结论:按什么方式算钱是最合理的?

我可以这样建模:对我来说,成本是电费,对用户来说,成本是什么时候计算完。这个生 意能做下去,功耗的计算是第一位的。所以,还是把功耗算第一位,在这个基础上来算用 户的利益,我们分两类应用来卖:带向量的和不带向量的,带向量的价钱高,不带价钱低 ,在这个类别中我们再按runtime来卖……

这里有很多预判不见得我们所有人都同意的,比如我们调研市场的人可能认为,大部分用 户都是两者都用到了,只好买我们的向量服务,但这个价钱又太高,不如我们分HPC用户和 Web用户这种卖法,都提供向量指令,只是后者的Profile低一点……

所以,我说,每个架构设计都是独一无二的,都是在一层层建模,摸技术,摸市场,不断 从未知走向已知,但没有高层设计,你肯定都不会考虑这些问题。

所以,我们回到最初的问题:什么设计文档叫没有设计呢?就是那些不包含任何逻辑闭包 ,不针对问题建立逻辑闭包,只装样子在文档中堆各种自称的视图,这里说“要这样”,那 里突然冒出来一个流程图,每个独立的空间中都看不到根,看不到范围,看不到权衡,也 看不到权衡的机会。这是另一个种形式的“写代码”,还是经不起编译,运行不了的代码。

这种设计文档,就叫“不包含有效设计成份的”。有空写这种浪费时间的东西,你不如一开 始就写代码得了。

其他

我们由此也可以说明为什么代码量说明不了开发任务的工作量。因为代码大部分时间是逻 辑设计,如果这个设计是高内聚的,那么我们建模的成本就仅限于这个闭包的一部分,这 时代码量和工作量基本是线性关系,如果这个设计是扇出的,那么,我们的建模成本就是 所有扇出模块的逻辑,这时工作量和代码量就不再具有线性关系了。

附录

这里我不定期记录一些“非设计成分”的设计描述。