本节书摘来自华章计算机《需求设计:构建用户想要和需要的产品》一书中的第2章,第2.8节,作者: [英] 克里斯·布里顿(Chris Britton) 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
2.8 这样做真的是工程化的设计吗
笔者提出的六框设计模型,与经典的工程组件分解图并不完全相同。因此,有人会问,这样做真的能像第1章所说的那样,实现工程化的设计吗?这个问题不能简单地用是或否来回答。
在建筑学和机械工程学之中,宏观设计图与组件设计图的绘制方式是相同的,都需要确定一些形状及表面,而且所有的组件都有其尺寸及重量。此外,从顶层设计到底层设计之间的层数,是没有限制的,换句话说,可以把组件分成子组件,再把子组件分成更小的子组件,这种划分可以多次执行。然而,对IT软件开发所做的设计,却不是这样。情境设计、用户界面设计、数据库设计、技术设计和实现设计,都有着各自不同的表示方式,而且整套设计只分为情境、细节与实现这3层。(严格地来说,这并不是一套体系,而更像是一张有向无环图(directed acyclic graph,DAG),但由于我们在设计阶段所投入的时间和精力很多,因此通常会将其分成多个组件,这给人一种体系化的感觉,因此,笔者把它称为设计体系。)
问题在于,从建筑学和机械工程学之中所提取的设计体系这一概念,能否运用在一种与之差异很大的领域里面?
图2-9以水平切片的方式,展示了由情境设计所分解出来的用户界面设计、数据库设计及技术设计。
用户界面设计与数据库设计是紧密合作的关系,但它们之间却没有太多重叠,而技术设计则同时与这两者完全发生重叠。按照传统的组件分解方式,有人可能认为我们能够把任务扩展成画面、逻辑与数据库这三种组件,但问题是,不同的任务之间不仅要共享数据,而且有时还要共用同一个画面。因此,这三种细节设计,其实是把情境设计分成了三张不同的视图,每张视图都体现其中的一个方面,它们都会给实现工作提供信息。具体到我们的例子来看,用户界面的设计方案拟定了一系列画面以及这些画面之间的切换方式,这使得仓库的拣货员及打包员可以据此完成其任务,数据库的设计方案创建了一个数据库,可以给各项任务提供数据存储机制,而技术设计则使得实现者能够据此来确定各项技术的用法,并且明白应该怎样采用这些技术来编写代码,以展示应用程序之中的各个画面,并实现对数据库的访问。大家看,这么做是不是一种工程化的设计呢?
笔者的观点是:一套体系化的设计只要能通过三项检测,就足以说明它能够支持工程化的设计方式。第一项测试,是看该设计体系是否完备(complete)。完备性,意味着顶层设计所指定的内容,能够在实现中得到落实。换句话说,就是我们沿着设计体系向下走的时候,不会丢失信息。对于普通的工程化设计来说,其完备性是很容易判断出来的。高层设计之中的每一处,都可以体现出一个能够直接构建出来的物件(如一片具有某种形状的金属),或是一个有其自身设计方案的组件。而我们的六框设计模型,却并不能明显地体现出它是否具有完备性。为了证明某套设计是完备的,我们必须做一系列的检查,首先要保证每项任务都在用户界面的设计方案之中得到了实现,并确保用户界面设计之中的每一个部分,都编成了相应的代码。此外,还必须检查情境设计之中的每张数据表,是否都在数据库的schema中得到了实现,并且要判断代码里面是否添加了控制机制,以确保用户组的成员只能执行该组所允许的操作。笔者之所以要提起这么多项检查,其目的是想说明:我们这套设计模型的完备性,也是可以检测出来的。
第二项检测,是看该设计体系是否一致(consistency)。我们必须检查三种细节设计之间是否一致,通俗地说,就是看看它们是否合得来。例如,如果用户界面之中的某个动作需要访问数据对象,那么它手边就必须有足够的资料,使得自己能够在数据库中找到这个数据对象。设计之间的一致性,在传统的工程学中也是个问题。例如,设计汽车的时候,你就必须保证变速器(gear box)能够与发动机相兼容。
如果能保证设计的完备性,那就可以确信自己完成了实现工作,如果能同时保证其完备性与一致性,那么还可以确信情境设计之中所做的论断,对于实现来说也依然有效。
测试完备性的时候,是从设计体系的顶层出发,向下深入较为底层的设计之中,而测试一致性的时候,则是横向穿越整个设计体系。还有一些时候,我们希望从设计体系的底层出发,向上查看这个体系。这就是工程化设计的第三项属性:可追溯性(traceability)。可追溯性的意思是说,我们要知道底层设计元素与高层设计元素之间的对应关系。具备了可追溯性,我们就能够回答“这行代码出错会带来什么影响?”之类的问题。从早前的图2-2里,我们可以看到,代码主要是为了支持功能需求或非功能型的需求(框架代码)而编写的。尽管如此,但所有的代码都应该对性能和品质等非功能型的需求负有某些责任。功能型的代码可以追溯到用户界面之中的动作,而用户界面之中的动作又可以追溯到我们对相关任务所做的描述。框架代码追踪起来稍微困难一些。其中某些代码可能是为了给某项系统管理功能提供支持,而另外一些代码则有可能是直接为了给功能型的代码做支撑。通过可追溯性来追寻各种元素所对应的设计需求,这对代码评审是很有帮助的。
笔者在本书前言里面说过,工程设计与地图所展示出的细节,既可以多,也可以少,其细致程度取决于视图的分辨率。以在线地图为例,分辨率高的地图所展示的细节,比分辨率低的地图要多,因此在高分辨率的地图里面能看到的东西,不一定能在低分辨率的地图里面看到。于是,这种地图就不具有完备性,因为新的事物会在放大的过程中逐渐进入视野。此外,由于高分辨率的地图中所展示的东西,不一定能在低分辨率的地图中找到,因此它也不具备可追溯性。
当然,即便一套设计体系是完备、一致且可追溯的,它也依然有可能包含错误。
现在思考一个简单的问题:“什么是程序错误?”这个问题从字面上看,似乎问得有点傻。所谓错误,就是指程序的行为偏离了规范(specification)。某些规范未必会写在纸面上,例如,“程序决不能意外停止”,就是一条没有写在纸面上的需求。一般来说,必须先有一份规范可供参照,然后才能去给错误下一个定义,对于六框模型来讲,情境设计就是它的规范。由此可见,完备性与可追溯性是很重要的。完备性,意味着我们可以检查出这份规范里面的所有内容是否都得到了实现,而可追溯性,则使我们能够把每段代码都与规范中的某项条款联系起来。大家可以这样想:完备性用来确保我们在从上到下深入设计体系的过程中不会丢失信息,而可追溯性则用来确保我们在从下到上攀爬设计体系的过程中,同样不会丢失信息。
当前的大多数项目都没有制定正式的规范,那么程序员怎样才能知道项目里面出现了错误呢?答案是:他们通过编写测试来保证这一点,换句话说,测试本身就成了项目的规范。笔者对此提出两条反对意见:
- 很难保证这些测试确实反映了利益相关者的真正需求。
- 这会促使程序员把需求之中一些难以察觉的微妙之处跳过去(例如,任务间的依赖关系)。
请不要以为这是在否定测试的价值。实际上,有了用户界面设计与情境设计的指导,开发者可以写出健壮程度远超平常的一些测试。笔者只是觉得不应该用测试完全取代情境设计。
第2章说过,应用程序的开发应该做得像一门工程学科。那么有人就会问:形式化方法难道没有用武之地了吗?为什么要把它做得像工程学,而不把它做得像数学呢?这是因为,形式化的方法,通常要求我们以数学集合论的角度来重新表述需求。对于系统有可能出现的每一种状态,我们都要找出该状态下的一些事实。然后,我们还必须把有可能发生的每一种状态切换情况都确定下来,并找出制约这些状态切换行为的规则。形式化方法的问题在于,它是一种对用户不够友好的方法,一般的利益相关者在审阅这种规范的时候,不仅得不到足够的启发,而且还有可能产生困惑。对于技术领域内的一些工作(例如,为缓存控制器芯片制定规范)来说,形式化方法的运用效果是比较好的,但就笔者所知,它在一般的业务应用程序上面,还没有获得成功。我感觉形式化方法将来会大有用场,这可以参看Amazon对TLA+的用法([9]~[10]),他们使用形式化方法来分析一些棘手的问题,例如,当恢复代码之中出现故障时,会发生什么。我们或许可以把任务描述撰写得更加正规(形式化)一些,但要同时确保正规性与可读性,却是一件很困难的事情(有人认为这依然是有可能做到的[11])。笔者觉得这可以当作一项课题来研究。