.. Kenneth Lee 版权所有 2019-2020
:Authors: Kenneth Lee :Version: 1.0
设计逻辑和代码逻辑
本文是对这两个文档的总结:
:doc:`状态机退出方法`
:doc:`不为天下先2`
目的是基于这两个文档的一些场景,进一步讨论一下什么样的逻辑属于设计文档,什么样 的逻辑属于代码逻辑。
我以前就写过一些讨论,谈到“代码不能代替设计文档”,或者“这样的逻辑不属于设计文档 ,它应该属于编码”。但我没有更好的逻辑来更直接地说明这两个结论的原因。但有了这两 个推演,我想我有更好的逻辑来描述这两个问题了。
在上面这个状态机退出方法的讨论中,我给出了三个独立的逻辑:
状态机
把刺激通道化,排队化(比如通过锁或者排队设计)
状态退出过程保护
我认为,这些东西,就是“设计逻辑”。比如你定义系统有5个状态,在每个情形下做什么, 然后看有没有状态的处理是不对的。
这种东西靠看代码是看不出来的(除非你对着代码重新画一次),在代码升级、修改的时 候不看着这个状态机,你代码写得对不对,你也是不能肯定的,它非常烧脑。
这种逻辑就属于设计文档。
反之,如果你写,在abc_op1()的时候,先上A锁,然后修改x变量,然后给T发一个信号, 然后修改状态到sx2,然后解A锁。这个就不属于设计文档的逻辑了,因为这种东西和代码 的逻辑是重叠的,在设计文档中写好了,搬到代码去都一样,拿个Word,Latex之类的东西 写出来,还不如直接写到代码中呢,你写成文档,还需要同步,这完全吃饱了撑的,这就 属于浪费时间了。
同样的,你写对刺激排队的策略,你说你把什么行为看做是对状态机的刺激,他们有哪些 线程(包括中断等)可以把刺激送进来,你用什么锁来保证进去以后相关的执行过程可以 排队。这个逻辑就属于文档。但你讨论每个刺激函数怎么写,这个就不是设计了。这是编 码,就算不写在.c,.go中,它们依然是代码。
就算你进行抽象,比如abc_op1..3的行为是这样这样……,abc_op4..5的行为是那 样那样。这仍属于编码。因为他们并不是独立于代码的建模。关键是这些逻辑写在文档中 ,没有一个明确的逻辑链可以验证它对不对。你既没有说这些opX是你的操作的全集,也没 有说他们属于哪个线程,也没有和状态机的维护关联在一起。这里就没有独立的逻辑链可 以建。这个逻辑独立到文档中的意义在哪里?它的逻辑对还是不对?不看代码都不会知道 。既然如此,把它写出来,作为代码之外的独立推演,有什么用?
其他的,我们总共有多少模块,模块间关系是什么。有多少版本,版本间什么关系。整个 API系列是怎么样的,能否通过全部列出来,进行一个全局Review,看看是不是丢了一个维 护接口导致API空间不自恰(比如有创建没有释放,有保存没有恢复等)……这些逻辑,都属 于设计文档。有时,流程确实也可以属于文档,只是那个流程非常关键,需要被独立抽取 出来,避免和代码中其他逻辑混在一起无法辨认,等等。
设计文档是补充编码之外的独立逻辑,常常还是在更高一层抽象,判断全局的东西是否发 生的缺失,不自恰,这一类问题的。
正如我们在《不为天下先2》中提到的,架构(高层)设计的目的不是下层设计的预演,它 工作在另一层逻辑上,它是预期下一层设计是要加入新的逻辑的,但它为下一层设计设定 另一层逻辑的限制,保证下一层设计可以聚焦,我们推演完了状态机。下一层只要考虑我 现在是从什么跳到什么上,不需要再担心这样跳法会不会出什么问题。推演完锁的原则, 下一层设计只需要考虑设定的锁的原则,什么情况用什么锁,就可以保证不会发生死锁, 或者不会在某个流程中少用了锁。每个逻辑属于什么层次的设计,其实大部分时候是有明 显的分界的,它们通常通过逻辑链的关联度进行分层隔离,比如某个逻辑要素不在和任何 人相关,它就会被独立出来,它就会属于那一层,这样代码才有立体一说。否则就又都压 平了。
补充:提供一个例子:Linux内核mm/filemap.c最前面,有一大段关于锁的策略的描述。这 就是设计逻辑——如果代码就可以表示所有逻辑,这里这个注释是为了干什么?