仓库源文

.. Kenneth Lee 版权所有 2020-2024

:Authors: Kenneth Lee :Version: 2.0 :Date: 2024-06-04 :Status: Released

新手设计文档典型错误


这里记录一些最近发现的新手的典型设计错误,没有什么新东西,就是感觉这些例子很典 型,不记录一下浪费了。我尝试去找了几篇过去写的总结,看看能否嵌到那些逻辑中,但 好像不行,所以还是新写一个文档。

第一个问题,之前的文档不断说的:保持你的逻辑链,每件要说事情都有因果。这里不再 解释,但我给出一些实际发生的例子:

  1. 引用一个从来没有提过,而且不是通用概念的概念。这种通常发生在“笔记式”文档中, 作者没有指望别人能看懂,只是记录自己的思路。这样写文档也是有益的,我们写的第 一版本通常也会这样写。但其实这样写是有问题的,因为我们隐隐约约引入的模棱两可 的概念,很可能包含了我们没有研究清楚的逻辑在其中,正经写设计文档时候躲这些概 念,往往就是留下破绽的表现。

  2. 引入一个不产生构架作用的概念,这种是前一个问题的翻版。比如调查硬件的接口,硬 件提到一个概念,但这个概念对你的软件的任何设计都不产生影响。其实这种问题很多 时候不是它看到的样子,很多时候这个概念其实是对你的软件语义有影响的,只是你误 会了这个概念的含义,所以会在文档中表现出没有影响。

  3. 填空。文档模板中有个标题,比如“读者”,就直接写一堆人上去,至于这个“读者”有什 么用,不管。标题上有个“输入”,就把用户接口放上去,这个地方是否需要定义用户接 口,原模板中这个地方是否预期你写用户接口,都不去关心。还有写是自己起的标题, 但里面不做任何介绍,上来就是一张表,列出某种信息,但这种信息有什么用呢?不说 。这又是某种意义上的“笔记”。笔记有什么问题,参考1.

第二个问题,多余设计。比如,都已经设计到一个c++的类了,还要设计里面每个函数怎么 做,这已经是可以在编码阶段做好的事情了,在设计中表述一次,在编码的时候表述一次 ,后面改的时候两边一起改吗?——这就不是一个合格的,“不产生多余重复逻辑”的程序员 的作风,对吧?

第三个问题,把平台当做模块,比如有人写一个PCIE的驱动,把PCIE子系统作为一个组成 部件画到模块图里面。这其实没有必要,因为PCIE驱动怎么写,这是固定的,并不值得单 独作为一个对象考量,否则你要不要把printk的提供模块作为一个模块?把kmalloc作为一 个模块?把set_timer作为一个模块?把dma_map作为一个模块?我们是要建模,不是要“严 格表述代码实现”。

第四个问题,为写而写的设计决策。比如要求你写“遵循的标准”,你写“PCIe 5.0”,或者“ 本软件遵循Linux 4.19以上内核对PCI总线驱动的接口和构架要求”。这种话对你的设计和 编码有影响吗?没有,它就是废话。你的软件要在多个平台上都可以运行,你决定你的软 件的接口都要满足C++11的某个子集的定义,这是你需要关注的“标准”,你的代码本来目的 是上传Linux主线,说什么“遵循4.19以上内核……”什么的,这你怎么执行?为说而说不是设 计。

第五个问题,用力过猛。“本模块上传到qemu社区,位置为 qemu/hw/virtio/virtio-scsi-platform.c……”,听起来斩钉截铁,雷厉风行。但你的模块 放哪里有多重要?这都是和上游Maintainer Bargain的结果,设计阶段的逻辑不是它的控 制要素,你能左右它吗?你要用逻辑说话,而不是用“雷厉风行”这个“样子”说话。

第六个问题,关注实现,忘了用户接口。比如做一个数据收集,上来就开始设计数据从哪 里获取,从哪里汇聚,从哪里压缩。但你怎么判断这些行为是否正确呢?那是要推演完你 提供的功能是否能帮助用户解决它的问题的啊。你首先要判断的是:用户碰到什么问题才 会用你的收集工具,然后是你提供的接口是否能帮助它得到需要的数据。这时你才会知道 你该收集什么,过滤什么,这个设计才有意义。

第七个问题,设计自己不能控制的东西,比如决定每年发两个维护版本之类的。你说你出 维护版本是什么原因?显然是你出了Bug或者安全漏洞,这个东西是你可以控制的吗?出了 安全漏洞你敢不马上修?这时你要设计的是你按什么规矩出版本,而不是决定你有多少个 版本,或者什么时候出版本。

第八个问题,状态机。有人会因为我提状态机的重要性而强行做一个状态机。但我强调它 的重要性,是强调你正确使用它的重要性,不是你有个状态机就叫重视状态机的。你的状 态机根本没有中间稳态,所有行为都是设置了某个flag而决定做什么,这种情况你设计状 态机有什么用呢?更不要说很多人的状态机并没有考虑在每个状态上可以收到的激励的全 集,这有状态机和没有状态机又有什么区别?

第九个问题,指代不清。我们还是那句话,什么“创建”,“通知”,“发送”,都是抽象语言 。应用软件就只懂“进程”,“信号”,“调用”这些概念,硬件接口就只有io读写,doorbell ,中断,DMA映射这些概念。你要创建抽象语言必须用基本的概念来抽象,不要在复杂系统 中直接使用这些概念来表述功能,否则根本不知道你在说什么。

综合起来,我们简单这样总结:一个设计文档,如果你做了某个决策,但这个东西的控制 要素不在你的设计逻辑链上,那么这个设计一定是错误的。

补充1

最近有另一个心得,把最后的总结更新一下:

我发现很多设计评审,我作为评审者和设计者的分歧常常来自:设计者关注的是“我写了什 么,这个写的东西‘对不对’”,而我关注的首先是“这个设计少了什么”。只有过了这个“逻 辑上没有少什么”,我才会进入细节看这里的逻辑每个对不对。大部分讨论的时间其实浪费 在这里。

补充2

这是在2024年补充的一个心得。我发现很多人写设计文档的时候“端着”,他们认为写设计 文档是一件很正经严肃的事情,关于个人的荣辱,所以写起来小心翼翼,深怕丢了面子。 这反而导致文档质量不高,或者工程效果不好。

写设计文档是很正经严肃这个事情不错,但“正经”和“严肃”都是表相,而不是目标。目标 是实现工程目的。“正经”和“严肃”表达的是个人态度和隐含的别人的观感,这个和工程目 的是不重合的,太关注正经和严肃,会让我们失去目标。比如,现实中就是看到很多人文 档迟迟写不出来,或者写出来了巧言令色,避开了设计重点。特别是,在我看到的例子中, 很多人常常避开了设计重点,但他还是直接去做细节设计或者编码去了,导致设计被忽略 了。

所以我通常这样给这些工程师形容如何写设计文档:你要用“你的哨所正在被人炮击,而你 要尽快用三言两语说完你的状况,位置和目的,发回给后方,让后方火力支援你”这个态 度来写设计文档。抱着这个心态,你就不会再在乎你的描述不好看,某些两难设计导致你 让人觉得你水平不高,或者你不敢下结论……设计本来就是不好看的,工程设计大部分时候 都是在几个烂选择中选择一个不那么烂的而已,或者为了某个设计目的,不得不在很多本 来还挺好看的设计上进行妥协,这些没有几个好看的。你“端着”,你就只能不提这个事情, 谈些无关紧要的要素,这样你就避开了真正的设计问题,讲些大路货的东西了。

等你写完了“炮火中的报告”,你躲过这一炮击波了,你回去可以慢慢润色你过去的文档, 你还可以在写回忆录的时候美化一下你的报告,那时的目标才是你的个人荣誉,但设计是 打仗,是为了胜利必须立即做出的选择,没有这样的心态,是做不好设计的。