仓库源文

.. Kenneth Lee 版权所有 2018-2020

:Authors: Kenneth Lee :Version: 1.0

架构设计的大忌:我没错


本文给这个文档:

    :doc:`架构设计入门知识`

写一个补充,但因为它和该文档只产生一个“关联”,所以就不适合放到那个文档中,而应 该作为“下一层”设计,所以我放在这里。在阅读的角度,我建议看完前面这个上层逻辑, 再来看这个细节逻辑。

我评审过非常多的架构设计和其他设计文档,我会经常发现,这些文档洋洋洒洒写了数百 页,但有效内容不足1%。这个很伤人,但情况就是这样:路线错了,知识越多越反动:) ,本文就讨论一下这个“路线”的问题。

有一种设计文档的的“介绍”章节是这样写的:

    | 本文是XXX产品的架构设计,用于指导XXX产品的软件,
    | 硬件设计工程师进行后续模块设计和详细设计。
    | XXX产品是因应YYY市场的需要而设计的一款富有竞争力的产品,
    | 它提供高带宽,低时延的ZZZ业务的处理能力,领先竞争对手30%的性能……

这个描述有没有错的呢?说不上有什么错。但作为读者,我看这个东西,我拿到了什么信 息呢?我就拿到一个信息:这是XXX产品的架构设计。

信息有效率低到令人发指。

用这种方法来写了,它在架构目标这一章就会开始列一大堆的经过客户或者市场部门认可 的“需求”,然后就是各种数据结构,流程之类的东西了。

这些东西,统统都“没有错”,但从架构设计的角度来说,这些都是“垃圾”,没有任何构架 设计的成分在里面。

我们为什么在细节设计之外,要进行“架构设计”?因为我们怕我们做细节设计的时候方向 错了。架构设计必须对准这个目标,如果不能对准这个目标,这件事情就是“路线错了”。 如果我们做架构设计上来就是细节的、正确的“数据结构”,“运行流程”,那直接做细节设 计就可以了,为什么要做架构设计?或者说,要写数据结构,为什么要用Word来写?直接 写成代码不是更好?

所以,我们在架构设计文档的最前面给定了“设计目标”,主要目的是:我们要规避细节设 计中会发生的“偏离”问题。

比如,我们做一个长期发展的产品,我们要平衡三年内特性实施的分布,避免后面的特性 加不进来,也避免提前做了不能发挥作用的功能,我们要对特性的实现顺序做一次建模。 这个就叫“架构目标”,不做这个设计,我们在实施一个版本的时候,可能就没有办法判断 某个功能是否应该放到这个版本里面。也无法保证我们会给第二年留出空间。我们发现我 们的整个产品的设计中有这个问题要解决了,我们把这个作为一个设计目标放出来,后面 我们就会进行一个“开发视图”的设计,我们会分解出多个版本,然后把特性落在这些版本 中。

又比如,我们做一个多核系统中的设备驱动,用户的多个程序可能会对它发出请求,设备 多个中断也会给不同的核报中断,其他模块也会调用这个驱动的回调。这么多个运行上下 文,同时对我们的程序发出请求,这个驱动的数据结构需要分成多少份让这些上下文不会 冲突?在不得不冲突的地方要加什么锁,或者设定什么样的访问原则保证这些上下文既可 以保证效率,又不会访问冲突?这些东西就不是编码的时候可以想清楚的,这也是一个建 模,可能我们需要一个“处理视图”来对它进行归纳,指导我们的“细节”设计了。

至于设计两个模块如何通讯,你不做,细节设计都会做的,这要你跳出来做什么“架构设计 ”啊?你做出这种设计就已经“错”了。然后你给我说“我说的都没有错”,这毫无意义。

所以,在你没有任何细节设计前,你都可以进行“架构设计目标的设计”的。而且这是你做 架构设计的唯一方法:你是要提前找到,你进行细节设计的时候,如果不进行整体规划, 后面肯定会做错的设计目标出来。后面你深入进入细节不会错的一些“高层设计目标”,对 于架构设计毫无意义。如果你的设计目标离开了这个原则,那这个设计文档写了多少东西 ,它都已经偏离“架构设计”这个目标,那它的有效性就是0。

我经常让我们的工程师写完这个部分(架构设计目标)就开始拿出来评审,但也有不少人 死活不肯拿出来,他们非要等后面的设计都设计得差不多了,甚至代码都写得差不多了, 才拿出来。这样我指出问题的时候,他们就可以拿出这些提前准备的、我不知道的“知识” 来打我的脸:你看,你不知道吧,这个地方吧喇叭啦……他们是完全不懂架构师的判断模型— —我根本不关心你们这些细节的,我只看你高层抽象呈现的行为是否能“支持”目标的实现, 你写一千页的细节,如果它不能呈现在我达成目标的高层逻辑链上,你每句话都是对的, 对我来说都没有意义。你越过“高层设计目标”的正确性去做细节设计,也许可以绑架我, 你绑架不了“客户不给你埋单”这个事实的。

“架构设计”就是“有可能有错”的设计,就是要对“未知的细节”进行预判的设计,没有这个 前提,你可以写无数“我没错”的逻辑出来,但这些东西一钱不值。比如前面提到的多个执 行上下文的处理视图建模。你要没有错,你可以写出这样的设计(我见过好多次了):

    | 每个模块对和其他模块共享的数据,需要使用锁进行保护,
    | 避免发生数据冲突。在使用多个锁的时候,要用相同的顺序上锁和释放锁,
    | 以免产生死锁……

这些说法有错吗?当然“没错”,但纯粹脱裤子放屁,对齐一下我们前面这个目标,你“控制 ”了细节设计了吗?保证了“细节设计”确实被保证了性能问题了吗?建了可以被理解的模型 了吗?这纯粹瞎扯蛋么。

然后这些家伙就会开始给我看:你看我这里有一段流程设计,在这里有一个锁,然后呢, 这里还有一段流程设计,还有一个锁……“Kenneth您不够深入民间,了解我们的细节,其实 你说的问题我们都想过”——收起你的神通吧,你想用信息淹没我而已,你这些东西已经是细 节了,你这么牛,事后逻辑冲突的时候不要找我啊,把系统弄成一堆乱麻的时候又跑过来 说“现在这么紧急的情况下,我们就不要谈过去的那些问题了,你是架构师,赶紧想想办法 吧……”我想你老母,让你把水倒盆里的时候你非倒地上,到了地上你回来找我问怎么收回来 ?——老子只知道怎么把你埋进去,不知道怎么把水收回来。不控制熵增,增完就不能回头 ,就这么简单。

架构设计希望我们最终没有错,但不是让架构设计本身没有错,它在冒险,通过它的冒险 ,避免细节冒更大的限。你安排一个三年的开发策略,第二年的策略肯定是要根据第一年 的结果去调整的,你要第二年没有错,就会写那些写了等于没写的“我没错”的废话,这个 结果就是不做准备。终极没有错的架构设计就是细节设计,就是让架构设计没有了作用。 这样的结果就是完全在细节设计上冒险,也就是整个产品冒更大的险,最终就是在“我没错 ”这个白莲花的名头下,质本洁来还洁去,终究污淖陷渠沟。

所以,你要做架构设计,就要破妄,破这个“我没错”的妄,如果你非要保证“我没错”,我 的意见就是——不用你做这个设计。

补充1

如果你要解决这个问题,暂时不知道走什么路,我认为一个基本的方法是“不敢为主而为客 ”:你先保证你写的每个字,都不是对模板的“填空”,而是问一次:我写下这句话,对读者 提供了一个什么信息,解决了一个什么问题。如果不能提供有效信息,解决不了任何对方 解决不了的问题,你就把它删了。

补充2(20200805)

今天评审了一份设计文档,看到另一种例子:这个文档在第一章抄了很长一段我上一级设 计的原文出来。可能作者觉得“这样你就说不了我什么了吧?”。问题是,我的原文的逻辑 ,并非证明你这个级别的逻辑的因素啊。

这也许是一个很好的“鱼不可脱于渊”的例子,逻辑和概念能成立的原因是因为外在的依赖 ,离开了原来的逻辑链,它就不再成立了。用抄写的方式来保证架构设计没有错,是把逻 辑变成了死的“名”,它也没有原来的作用了。

在这个同一份文档中,作者在“范围”这一章中,说“本文的设计仅包含对软件S的v1, v2, v3对硬件A, B, C的支持”,这句话看起来也“没有错”。但从设计的角度,它仍是错的。因 为架构设计更关心的是你的方向错了没有。那么我们自然就要问:为什么你的范围是这个 ?你不能看到“范围”,就给一个范围,忘掉你的目标,如果你站在你最初的目标和限制上 ,你的思考可能是:我们现在要支持的市场是这样这样的,v1, v2, v3每天有退出生命周 期,所以他们肯定要支持的,新的特性可以坐在v3中,而未来的版本我们只是需要留下余 地,所以可以先不在定义范围内,而A,B,C同理,但他们上面还可以插卡,这些插卡后的 设备是否也需要支持?这些东西都考虑上去了,这个“范围”才是一种定义。架构设计的每 个章节,都是在“设计”,都是在发现逻辑漏洞,如果站在“没说错”的角度来说,它就没有 稳定架构,避免未来犯错的这个特点了。