3.309. 不要把细节特征当作设计

我观察到很多设计新手会把代码的细节特征当作设计。这种情况是这样发生的:新手刚刚拿到一个需求,他其实并没有把握这个需求要怎么实现,他只是大概有一些方向性的选择。但对于其他的限制,他可能并不那么确定。比如他可能并不肯定要用的那些库是否能提供足够的API让他完成功能,也不见得知道那个库的线程模型是否肯定和他想象一样……所以,他并没有马上写出比如“API肯定是如何如何的”这种断言的信心。

于是,他就会先写一些代码,顺着那个“大致的方向”验证一下,会试用一下那些API,把没有信心的特性都试用一下,等等。等这些代码写出来了,他们就开始有信心了,于是,他们开始仿照别人的“设计案例”或者“设计模板”,把这个验证代码的API,目录结构,对象关系之类的东西,写成一份或者多份文档,声称:“这是我的设计”。

但这些不是设计。设计是用来控制细节的,不是用来被细节控制的。这就好比我们规划从深圳出差去北京,那么考虑坐飞机还是高铁,打的去机场还是坐公交去机场,行李中带几件换洗的衣服,这些都是设计。但你提前走了一趟,说过海德大道的时候要等17秒,拉行李的时候要用左手,要在天虹门口等的士来,这些确实也是做这件事情的特征……但这些显然不是设计。

这个问题放在一些具体的生活上非常荒诞,但在设计上,就不见得好分辨了。比如我说,某个模块的对象创建API,接口必须是::

Object create(int n_object, float init_value, ErrorReporter e);

你能看出这是细节的特征还是规划(或者说设计)的特征吗?

这两者的核心区别就是这个特征是否是被上层需求驱动的。行李中带几件衣服,这是被“要出差几天”这个高层逻辑所驱动的,所以它可以指导你“找什么衣服,决定用多大的行李箱……”这些细节的设计,所以“行李中带三套衣服”这是一个高层设计,但“其中一套衣服是红色的”就不是设计,它只是刚好在细节上你有一套红色的衣服而已,它不是被高层逻辑所驱动的。

但这不是定式,如果高层逻辑中有“客户喜欢红色”这个条件,那么“带一套红色衣服”就是设计了。设计必须具体问题具体分析,不能套某种固定的模板。

就上面说的create API来说,这个名字是特征,还是它的返回值是特征,还是包含的参数是特征,还是参数的类型是特征?我们不知道,我们要找这个特征的目的才能判断。

比如可以认为数据关系是特征,这个接口我们这样设计:

我们通过:

Objects create(n_object, init_value, ...);

创建对象,对象中可以包含多个成员,每个成员都各自的初值,这样我们就可以立即把成组的成员立即(有初值了)投入运行。这个设计中,我们有确定的目标,我们关心我们需要给定成员的数量和初值,返回的是一个对象的集合。

这个设计满足了目标,同时限定了细节:比如必须有n_object,init_value这两个参数,而类型,其他参数以及报错方式,都可以在细节设计的时候根据具体情况再选择。

这样我们就有了一个很好的判断标准,决定什么是设计了:当你设定的约束,来自目标和“绝对真理”的组合,用于约束进一步的设计,那么,它就是设计。否则,它可能仅仅就是“细节的特征”。

备注

说起来,这个判断标准已经比较具有确定性了,但使用者还是要清楚这里的难度。因为人类语言本来就具有不确定性的,就好比之前我们反复说的维特根斯坦的理论,我们传递信息的时候总有Can be said clearly和Must be passed over in insilence的部分。无论你写得再细致,你总会包含一些基于双方的经验,只能“你知我知”的部分。好比上面的描述,我们默认认为这个create函数,换一个create_objects的名字没有什么问题。这并不“严密”,但我们必须有这样的容忍,最多是到我们意识到双方的理解有冲突的时候再去澄清它。

设计总是让人难受的。因为它反直觉,并且和后续工作存在天然的利益冲突。比如在前面的例子中,我们要求create必须成组提供对象,这可能是被高层设计的某个要求决定的。如果实现成单个的对象,可能更容易,性能也更高。但即使如此,也没有用,因为我们就要成组提供对象。这个事情,不干就偏离目标了,干了,下层设计者不爽,从而导致要求执行这个约束的上层设计者也得罪人。但要对着目标去,这就是必须的。这个道理就好像我们都不想起床去上班,但理智总是和“执行的快乐程度”是逆行的。如果没有这个“必须赚钱养家”的需求驱动下的“必须准时上班”的设计,我们不会起床去上班。这是设计这个动作的目的(让我们理智地执行细节)。我们就是不能“宠着”我们的执行。

所以,设计必然是一件不讨好的事情。不要期望设计会让下层设计者或者实现者高兴。甚至对于设计者和实现者都是你自己的情况下,不要指望自己的设计会让自己开发的时候更高兴。

所以,如果你设计了一组约束,这组约束你不说,细节设计或者实现的时候也会这么执行,那么这个设计肯定就失败了,因为有没有这个设计,结果都是一样的。

最后看一个例子。最近评审了一个指令集编址空间安排的设计。作者把空间分成了多个分区,分别用于常用指令,加速器指令和向量指令。然后巧妙地安排了几个位置的bit在不同的情形下可以判断指令所处的空间。这个东西我就认为不是设计。因为找不到它的设计目标:我们当然需要对指令集空间进行规划安排,但为什么需要分成三个分区?这个是被什么目标驱动的?这里没有。这里直接出结论:分了三个空间。所以这个达成了什么收益?没有这个收益来驱动(遇到障碍必须坚持,否则就会失去这个利益),后面具体设计的时候空间不够了,你会不会修改?一旦修改,我其他基于这个“三个分区”的设计是不是都需要推翻重新来?

所以你看,设计是有利益驱动的,它的投入是能赚钱的,我们花这个时间不是为了完成某种仪式,而是为了把这些利益抠出来,然后保证我们保证这个约束得以满足的所有条件,都被用作下一层的设计的约束。如果设计没有起到这个作用,设计失败了。

所以,我们并不反对为了验证某些结论,提前进行编码,但验证是验证,验证的代码并不是设计,设计还是需要设计的。