Preface

今天拿着《从 bal 谈谈软件系统设计》去找锋哥“检视”,他都没看几句,就开始跟我扯了。哈哈。

趁着我还记得记录一下。

设计的受众

大哥上来就说,这个东西的受众是谁,你打算写什么东西在里面:你是想跟别人介绍 bal 有什么呢?还是说描述 bal 设计成这个样子的原因呢?还是想分享从头设计一个系统的思路,给别人启发,然后拿 bal 来做例子呢?

  • 如果是给跟我水平差不多的人、或者新员工讲,那么我只需要描述好系统里面有什么元素,这些元素之间的关系是什么,以至于为什么是这样,并不需要描述很严密的推演和思考过程。因为这个东西已经成型了,我们只需要去阐述已有东西存在的合理性就行。

  • 如果是真是我要设计一个软件,掏出一个方案让我等级更高的人评审,那么描述的内容侧重点就不一样了,此时的文档更需要去描述设计的思考过程。

设计方案的元素

软件的设计方案大抵由这么三个部分组成:

  • 假设
  • 推导
  • 结论

假设

假设是约束、也是约定。

约束 constraint 的意思是外部条件对软件的限制,说明了**“软件只能或者不能怎么做”**。

考虑一下从零开始研发一个产品的场景,进行软件设计时我们首先要考虑诸如业务形态、产品形态、部署形态/环境这种最大方向的问题,从而给软件进行“定性”。

举例:如果软件会被部署在多个 CPU 上,不同 CPU 上的软件是需要协同工作的,那么它就必须是分布式的。

约定 contract 表达的是这些条件是稳定的,是可以被当我软件设计的地平线,承认“软件可以做什么”。就好比 C++ 的标准,白纸黑字说明白的条款,我是可以信赖的。

举例:要做一个模块管理的功能,我们需要选择一个具体的数值类型来表示模块的 id,那这个时候如果认为部署的实例中,模块的数量不会太多,比如说不会超过 255 个,那么一个 uint8_t 足以描述这个信息。假如某天因为部署人员误操作部署了 256 个模块,那么软件是没有义务去保证模块管理功能再能正常工作的。

约束和约定其实是一个意思,但是我觉得稍微区分一下角度可以更好的去理解软件作为一个整体,它跟外部的环境其实是一个“权利和义务”的微妙关系。

那么,我说:软件要在满足约束限制的情况下,充分利用约定的带来的自由。

🤯咦 你这是什么 C++ 心法吗?

推导

有了假设之后,我们就可以进行推导了。

推导就是从假设出发,进行相关的演绎描述,类似于做数学题的答题过程:已知 X 定理(假设),所以 Y1,所以 Y2。

举个例子:

假设:软件系统是分布式的,软件可能被部署在多个进程或者说多个 CPU 上。

推导 1:模块之间必然不能仅通过指针来进行相互访问甚至说都不应该先考虑指针

推导 2:模块之间必定是通过通信来交互的

推导 3:还需要一个概念,来描述“能不能通过指针相互访问”的约束(至于要不要显式地定义为“进程”,这里就不再展开了,但是既然这么说,那肯定是不建议的:D)

推导 4:…

其实推导是整个设计过程中最有意思的一个部分,你可以基于假设,一步步地构建你心中的系统。在你不停地“自圆其说”的过程中感受到自己创造力的迸发。

推导过程其实也分 DFS 和 BFS。

假设系统被拆出一个领域 A,你既可以先把 A 里面的 a1, a2, a3… 都想清楚,当然也可以把领域 B, C, D… 都定义出来,再逐个击破。

大哥说:这个嘛,主要就看心情和本事了。如果你对某个领域比较熟悉,觉得肯定不是问题,那就可以先跳过嘛。但要是你心情好,思路连贯,那先把这个领域设计完,也可以~

结论

推导的过程就是为了得出结论。

结论也很简单,就是选择了什么方案、每个领域/子系统的职责边界、以及它们之间如何协作。

如果我们在做系统设计方案的话,这个主要是明确整体的架构,提供开发的大方向。

当然最关键的地方还是 API 和模块本身的定位吧,其实总觉得 API 就是边界就是契约,定好了边界,从系统的纬度来看,其实设计基本就完成了,因为从系统这个高度去看,必然是不关心实现的。

如果是小到模块/类的这种设计结论,实际开发流程中可能会提一下具体的接口和大致的算法逻辑?

因为已经太具象化了,所以也就顺便提一嘴,但是方案评审的关键产出绝对不是那几个 API 和成员变量的容器类型选型!

设计方案的评审

在向别人描述一个完整的设计方案的时候,大致内容应该有:

  1. (背景)为了解决 A1,A2… 问题;
  2. (假设)我认为 B1,B2…;
  3. (推导)所以会有 C1,C2…;
  4. (结论)所以我认为/选择 D1,D2…;

因此,一个设计方案被评审的时候,这四项内容都是可以被挑战的点。

通常来说背景可能会稍微宽泛一点,属于是上桌的人的共识,比如说实现一个 A1 功能、达成 A2 目标,这个是客观的,相对明确的。

需求都没搞懂搞啥设计啊喂

从假设开始,个人的因素就会开始起显著作用,评审时经常获得的同行评价就是假设“不全面”“不合理”等。

受限于设计人员的开发经历、对业务的理解,很多假设在别人眼里就是站不住脚的,这可太正常了。当然也有可能是因为大家对同一个事情的理解和预期不一致,也会产生必要的一些讨论和争执。

比如上面关于模块数量的例子,获取不同部署实体之间需要交换各自的模块信息,那么虽然单个部署实体中模块总数不会超过 255,但是整个系统来看,部署实体需要维护的模块总数是会超过 255 的。设计人员没有考虑到这个场景/约束,因此上面选型的 uint8_t 可能就不符合要求了。

这可太有趣了,我又能从别人那学到什么新奇的想法。

轮到推导了,大家对假设达成共识以后,现在就更像是题干和限制已经描述清楚,对题的演绎过程进行批判。

推导的过程常常伴随着对于既定现象的观察和定量分析,作为一个事实依据的补充来引出下一个观点。

推导的关键是要确保每一步推导都有充分的论据支持,结论之间具有清晰的逻辑关联。这个阶段强调的是推理的合理性、渐进性,大家认同的是 1 + 1 = 2,那你的下一句不能是“地球绕着太阳转”。

推导过程中也会出现分歧,比如考虑到技术可行性、实现和维护成本、后续演进方向等,大伙对于同一件要完成的事情,也会有不同的思考。

我真的见过不少人说话前言不搭后语,两个事情之间有没有因果关系,硬是通过关联词“虽然但是”“因为所以”强词夺理进行描述。

比如说:因为这里执行慢,所以要用多线程。

这种如此直白又不经过脑子的推导,还是不要有的好。

🤔

不要“理所当然”。

不要过度追求完美。

不要主观臆断。

不要经验主义。

如果推导过程顺利,那么最后的结论大家应该也是基本一致的。

现在可以开工啦~

结论

写得真烂真乱啊

设计是一门艺术,而方案的讨论其实就是每个人交换思考过程的一个脑袋按摩罢了。

这东西有标准答案吗?没有。

那怎么校验一个设计是否成功呢?等到第一个无法招架的需求来了之后,自然就证明当初的这个设计有点力不从心了。