仓库源文

.. Kenneth Lee 版权所有 2019-2020

:Authors: Kenneth Lee :Version: 1.0

不知为美


今天写一个架构评审意见,需要一个基本逻辑做支持,我写在这里。

架构设计以简单为美。这背后的逻辑有点像所谓的“为道日损”:你做一件复杂的,长远的 事情,中间有很多“意外”,任何一个意外补充进来,就可能和你依赖的逻辑冲突,你初期 的概念越复杂,后面处理这个意外的余地就越小,你就无法响应这个意外,这个事情可能 就做不成了。

这好比你去一个地方,一条路比较远,但每五分钟一辆大巴,前后步行不到3分钟,肯定能 到,这个事情如果比较重要,我就会选择这条路。另一个选择比较近,但要求8点前一定到 渡头,8个小时才有一班船,到了对岸要赶上一班Bus,2个小时有一班。然后还要在下午5 点前过关,否则要等第二天,这条路虽然快,但它的条件多,任何一环告诉你有意外,这 个事情就失败了。所以君子取其厚,不取其薄,虚心实腹,做大的事情就要选择简单的, 意外少的路径。

软件设计一样的,大部分时候,我们没有正儿八经把代码写出来跑一跑,你都不敢肯定你 的设计肯定能正常使用,就算这类应用你试过很多遍,它还会有别的变数:比如用户变需 求?比如用到场景上发现最初理解的的需求不对?——成功需要千百个理由,失败只需要一 个理由。你高考考上清华,需要十年如一日的苦读,但你要失败,只要高考那天得了急性 肠胃炎就可以了。

所以,我们分析需求的时候,可以一路推演下去,把每个模块怎么弄的细节都分析清楚( 当然,这个还受时间的限制),但等我们开始动手写代码的时候(特别是写设计的时候) ,你得把所有非关键系统统统给我抛弃掉,认为你“不知”,你要用最小的依赖实现你的功 能。

比如你有一个设备,经常报错,错多了你尽量希望不要用它,然后让用户选择另一个,选 择的时候除了考虑它错得多不多,还要考虑它别的方面是否比别人好,比如它的时延是否 比较小……你分析的时候会有很多细节。但你编码的时候不能直接把这些细节直接不分大小 地写到逻辑中,否则你后面加逻辑就没法加了,随便怎么加都会碰到别的逻辑链。

你让我看,这个逻辑很简单:我有一些设备,这些设备有一组参数表示它被选择的优势, 这属于设备的属性。我选择设备根据这些参数,用特定的算法来选就好了。这是选的过程 。然后如果我使用的过程中它出了错,我要复位它,怎么复位这种烂事,也和我无关,反 正就是错了,错了我就放弃它,然后我重新选择一个,而“选择”这个问题,就又回到前面 那个算法上了。

这样,在架构的“大逻辑”上,我们只看到“设备属性,设备选择算法,出错重选”这三个逻 辑,至于出错处理,我们只要考虑设备属性和选择算法怎么更新就可以了。而用户,更加 什么都不用知道,他只是知道这个设备不行,不够好,换了一个就好了。

我们当然应该分析细节,但分析完了,你必须对细节进行分解分配,校验你的“架构逻辑” 是否还能处理这个意外,而不是不分层次地把细节暴露到高层逻辑中。如果你总这样处理 新需要,这个软件架构就无法持续维护下去了。

补充1:上面的逻辑写完后,我有机会和我评审的设计的作者面对面讨论一下他的困难,我 发现问题还是出在“人设”这个问题上(参考::doc:如何说谎)。还是用上面的例子来 类比:上面的设备管理中我们有三个对象:设备硬件,驱动,和用户程序。它们是三个身 份:

设备硬件提供物理能力,驱动负责管理硬件能力和用户使用之间的关联,用户程序负责使 用。

现在你设备出了问题,让用户不要使用,它们之间有很多的配合关系。如果丢开他们的这 种身份,直接考虑这个过程可以怎么实施。比如,在设备出错的时候,让用户程序临时等 一下,设备进行复位,复位成功以后,用户程序继续向下跑,这处理起来确实是完美——仅 在这个逻辑链上。但你放弃了他们的身份本身,他们之间的关系变复杂了。这个不利于未 来加逻辑。

基于身份的考量则是这样的:我们保护系统的优先级如下:全系统安全是第一位的,用户 数据是第二位的,业务连续性是第三位的。所以,首先,你硬件出错,我不能因此全系统 崩溃,其次,我不能导致用户程序直接崩溃,首先损失的应该是硬件(这是不可抗力)。 放弃掉赢家脑后,驱动中管理这个硬件的数据结构不能放弃,我必须维持对用户程序的逻 辑,让用户程序知道这个硬件出了问题,知道多大范围的数据是不可信的,尝试隔离可能 不可靠的数据,然后尝试退出或者重启(包括它的数据,否则错误可以传递出去,造成更 大范围的损害)。但用户程序是不可信的,驱动需要保证全系统安全,所以这个设备可以 等待进程退出,但驱动管理的所有硬件不能等待这个过程。驱动需要保证上述逻辑被控制 在有错的设备,以及使用这个设备的用户程序上。这样,高层的角色定位我们就有一个基 本的控制了。

然后我们开始解决每个角色内部的问题,比如用户程序,它收到内核的通知(假设是个信 号,比如SIGIO),需要保存数据,它可以在每轮设备交互后,确定一下中间有没有发生过 这个错误信号,然后在把数据加入可信数据链,如果出错,它放弃这些数据,重新申请设 备就可以了。由于有了前面高层的推演,这一层就不会越过角色定义,直接又去考虑“内核 能否等等”我这种问题了。这样才能控制整个复杂度。避免全系统关联在一起。