好的开始是成功的一半。
--谚语
所谓鲁棒性分析是这样一种方法:通过分析用例规约中的事件流,识别出实现用例规定的功能所需的主要对象及其职责,形成以职责模型为主的初步设计。
--温昱,《软件架构设计》
ADMEMS方法的Conceptual Architecture阶段包含3个步骤:
第1步,初步设计。
第2步,高层分割。
第3步,考虑非功能需求。
本章讲解如何根据关键功能,借助鲁棒图进行初步设计。
8.1 初步设计对复杂系统的意义
初步设计并不总是必须的--架构师只有在设计复杂系统时才需要它。
另外,"复杂"与否还和"熟悉"程度有关。一个"很小"的系统涉及你未接触过的领域,你会觉得它挺复杂;一个"较大"的系统,但你有很具体的经验,你依然会觉得它"Just so so"。
初步设计的目标简单而明确:那就是发现职责。初步设计无须展开架构设计细节,否则就背上了"包袱",这是复杂系统架构设计起步时的大忌。正如"初步设计"这个名字所暗示的,它只是狭义的架构设计的"第一枪"--之前的Pre-architecture阶段并未对"系统"做任何"切分"。
"初步设计"这个名字还暗示我们,后续的架构设计工作必然以之为基础。具体而言,初步设计识别出了职责,后续的高层分割方案才有依据,因为每个"高层分割单元"都是职责的承载体,而分割的目的也恰恰在于规划高层职责模型。
ADMEMS方法强调"关键需求决定架构"的策略,"基于关键功能,进行初步设计"就是一个具体体现。如第3章3.4.1节所述,系统的每个功能都是由一条"职责协作链"完成的;而初步设计的具体思路正是"通过为功能规划职责协作链来发现职责"(如图8-1所示)。
-------------------------------------------------------------------------------------------------------------------
8.2 鲁棒图简介
ADMEMS方法推荐以鲁棒图来辅助初步设计。那么,什么是鲁棒图呢?
8.2.1 鲁棒图的3种元素
鲁棒图包含3种元素(如图8-2所示),它们分别是边界对象、控制对象、实体对象:
边界对象对模拟外部环境和未来系统之间的交互进行建模。边界对象负责接收外部输入,处理内部内容的解释,并表达或传递相应的结果。
控制对象对行为进行封装,描述用例中事件流的控制行为。
实体对象对信息进行描述,它往往来自领域概念,和领域模型中的对象有良好的对应关系。
海象不是象,如此命名是因为"类比思维"在人的头脑中是根深蒂固的。关于鲁棒图3元素的"类比",自然是MVC。在图8-3中,我们做了更全面地对比,我们发现鲁棒图3元素和MVC还是有着不小的差异的。
由图可以看出,鲁棒图3元素和MVC的主要不同在于:
View仅涵盖了"用户界面"元素的抽象,而鲁棒图的边界对象全面涵盖了三种交互,即本系统和外部"人"的交互、本系统和外部"系统"的交互、本系统和外部"设备"的交互。
数据访问逻辑是Controller吗?不是。控制对象广泛涵盖了应用逻辑、业务逻辑、数据访问逻辑的抽象,而MVC的Controller主要对应于应用逻辑。
MVC的Model对应于经典的业务逻辑部分,而鲁棒图的实体对象更像"数据"的代名词--用实体对象建模的数据既可以是持久化的,也可以仅存在于内存中,并不像有的实践者理解的那样直接就等同于持久化对象。
--------------------------------------------------------------------------------------------------------------
8.2.2 鲁棒图一例
为了实现销户的功能,银行工作人员要访问3个"边界对象":
活期账户销户界面。
磁条读取设备。
打印设备。
"销户"是一个"控制对象",和"计算利息"一起进行销户功能的逻辑控制。
其中,"计算利息"对"活期账户"、"利息率"、"利息税率"这3个"实体对象"进行读取操作。
而"销户"负责读出"客户资料"……最终销户的完成意味着写入"活期账户"和"销户流水"信息。
8.2.4 为什么叫"鲁棒"图
也许你会问:"为什么叫'鲁棒'图?它和'鲁棒性'有什么关系?"
答案是:词汇相同,含义不同。
软件系统的"鲁棒性(Robustness)"也经常被翻译成"健壮性",同时它和"容错性(Fault Tolerance)"含义相同。具体而言,鲁棒性指当如下情况发生时依然具有正确运行功能的能力:非法输入数据、软硬件单元出现故障、未预料到的操作情况。例如,若机器死机,"本字处理软件"下次启动应能恢复死机前5分钟的编辑内容。再例如,"本3D渲染引擎"遇到图形参数丢失的情况时,应能够以默认值方式呈现,从而将程序崩溃的危险度减小为渲染不正常的危险度。
而"鲁棒图(Robustness Diagram)"的作用有两点,除了初步设计之外,就是检查用例规约是否正确和完善。"鲁棒图"正是因为第2点作用而得名的--所以严格来讲"鲁棒图(Robustness Diagram)"所指的不是"鲁棒性(Robustness)"。
从Doug Rosenberg在《用例驱动的UML对象建模应用》的描述中,也可得到上述结论:
在ICONIX过程中,鲁棒分析扮演了多个必不可少的角色。通过鲁棒分析,您将改进用例文本和静态模型。
有助于确保用例文本的正确性,且没有指定不合理或不可能的系统行为(基于要使用的一组对象),从而提供了健康性检查(Sanity Check)。这种改进使用例文本的特性从纯粹的用户手册角度变为对象模型上下文中的使用描述。
有助于确保用例考虑到了所有必需的分支流程,从而提供了完整性和正确性检查。经验表明,为实现这种目标,并编写出遵循某些定义良好的指南的文本,而在绘制鲁棒图上花费的时间,将在绘制时序图时3~4倍地节省下来。
有利于发现对象,这一点很重要,因为在域建模期间肯定会遗漏一些对象。您还可以发现对象命名冲突的情况,从而避免进一步造成严重的问题。另外,鲁棒分析有利于确保我们在绘制时序图之前确定大部分实体类和边界类。
如前所述,它缩小了分析和详细设计之间的鸿沟,从而完成了初步设计。
--------------------------------------------------------------------------------------
8.2.5 定位
在本书中,关于鲁棒图最重要的一点是:它是初步设计技术。
不要再困惑于类似"鲁棒图是分析技术,还是设计技术"这样的问题了。大家只须记住两个公式:
需求分析 ≠ 系统分析
系统分析 ≈ 初步设计
关于"分析"与"设计"的区分,邵维忠教授和杨芙清院士在《面向对象的系统设计》一书中早已做过精彩阐释:
用"做什么"和"怎么做"来区分分析与设计,是从结构化方法沿袭过来的一种观点。但即使在结构化方法中这种说法也很勉强……
在"做什么"和"怎么做"的问题上为什么会出现上述矛盾?究其根源,在于人们对软件工程中"分析"这个术语的含义有着不同的理解--有时把它作为需求分析(Requirements Analysis)的简称,有时是指系统分析(Systems Analysis),有时则作为需求分析和系统分析的总称。
需求分析是软件工程学中的经典术语之一,名副其实的含义应是对用户需求进行分析,旨在产生一份明确、规范的需求定义。从这个意义上讲,"分析是解决做什么而不是解决怎么做"是无可挑剔的。
但迄今为止,在人们所提出的各种分析方法(包括结构化分析和面向对象分析)中,真正属于需求分析的内容所占的分量并不太大;更多的内容是给出一种系统建模方法(包括一种表示法和相应的建模过程指导),告诉分析员如何建立一个能够满足(由需求定义所描述的)用户需求的系统模型。分析员大量的工作是对系统的应用领域进行调查研究,并抽象地表示这个系统。确切地讲,这些工作应该叫做系统分析,而不是需求分析。它既是对"做什么"问题的进一步明确,也在相当程度上涉及"怎么做"的问题。
忽略分析、需求分析和系统分析这些术语的不同含义,并在讨论中将它们随意替换,是造成上述矛盾的根源。
至于实践者为什么常将"需求分析"和"系统分析"混淆,这背后有着重要的现实原因。实际的工程化实践中,需求捕获、需求分析、系统分析不是完全孤立进行的。相反,它们往往是相互伴随、交叉进行的。需求工作伊始,无疑更多地是进行需求捕获工作,相伴进行的需求分析工作占的比例偏少;但随着掌握的需求信息越来越多,我们须要开展的对需求的分析和整理工作也越来越多了;而此时,伴随着对问题的分析,自然而然地会在高层次提出相应的应对策略……这,恰就是系统分析工作。《软件架构设计》一书中有如下阐述:
需求捕获是获取知识的过程,知识从无到有,从少到多。需求采集者必须理解用户所从事的工作,并且了解用户和客户希望软件系统在哪些方面帮助他们。
需求分析是挖掘和整理知识的过程,它在已掌握知识的基础上进行。毕竟,初步捕获到的需求信息往往处于不同层次,也有一些主观甚至不正确的信息。而经过必要的需求分析工作之后,需求更加系统、更加有条理、更加全面。
那么系统分析呢?如果说,需求分析致力于搞清楚软件系统要"做什么"的话,那么系统分析已经开始涉及"怎么做"的问题了。《系统分析》一书中写道:
简单地说,系统分析的意义如下:"系统分析是针对系统所要面临的问题,搜集相关的资料,以了解产生问题的原因所在,进而提出解决问题的方法与可行的逻辑方案,以满足系统的需求,实现预定的目标。"
需求捕获、需求分析,以及系统分析之间的关系我们必须理解透彻,否则会影响工作的有效进行。图8-5概括了三者之间的关系。
再次强调,鲁棒图已经"打开"了"系统"这个"黑盒子",将它划分成很多不同的职责,所以它是"设计技术"。
-----------------------------------------------------------------------------------------------------------
基于鲁棒图进行初步设计的10条经验
那么,如何借助鲁棒图进行初步设计呢?
ADMEMS方法归纳了鲁棒图建模的10条经验要点(其中50%是ADMEMS方法的原创经验,另一半来自业界其他专家),分别覆盖语法、思维、技巧、注意事项等4个方面(如图8-6所示),帮助一线架构师快速提升初步设计的能力。
下面将逐一讲解鲁棒图建模的10条经验。
8.3.1 遵守建模规则
图8-7展示了鲁棒图的建模规则。Doug Rosenberg在《UML用例驱动对象建模》中写道:
通过以下4条语句,可以理解该图的本质:
1) 参与者只能与边界对象交谈。
2) 边界对象只能与控制对象和参与者交谈。
3) 实体对象也只能与控制对象交谈。
4) 控制对象既能与边界对象交谈,也能与控制对象交谈,但不能与参与者交谈。
遵循3种元素的发现思路
图8-9说明了发现鲁棒图3种元素的思维方式。
用例(Use Case)=N个场景(Scenario)。每个场景的实现都是一连串的职责进行协作的结果。所以,初步设计可以通过"研究用例执行的不同场景,发现场景背后应该有哪些不同的职责"来完成。
量建模能解决鲁棒图建模卡壳的问题;从大处讲,这种方式适用于所有种类的UML图建模实践。
例如,类似WinZip、WinRar这样的压缩工具大家都用过。请一起来为其中的"压缩"功能进行基于鲁棒图的初步设计。
首先,识别最明显的职责。对,就是你自己认为最明显的那几个职责--不要认为设计和建模有严格的标准答案。如图8-10所示,你认为压缩就是把原文件变成压缩包的处理过程,于是识别出了3个职责:
原文件。
压缩包。
压缩器(负责压缩处理)。
接下来,开始考虑职责间的关系,并发现新职责。压缩器读取原文件,最终生成压缩包--嗯,这里可以将打包器独立出来,它是受了压缩器的委托而工作的。哦,还有字典……如图8-11所示。
继续同样的思维方式(别忘了用例规约定义的各种场景是你的"输入",而且,没有文档化的《用例规约》都没关系,你的头脑中有吗?)。图8-12的鲁棒图中间成果,又引入了压缩配置,它影响着压缩器的工作方式,例如加密压缩、分卷压缩或其他。
……最终的鲁棒图如图8-13所示。压缩功能还要支持显示压缩进度,以及随时取消进行了一半的压缩工作,所以,你又识别出了压缩行进界面和监听器等职责。
模型之于人,就像马匹之于人一样--它是工具。如果你不知怎样真正将"模型"为自己所用,反而被"建模"所累(经典的"人骑马、马骑人"的问题),请你问自己一个问题:
我是不是被太多的假设限制了思维?
或许,工具本身根本没有这样限制我!
8.3.8 勿关注细节
初步设计不应关注细节。例如,回顾前面图8-4所示的"销户"的鲁棒图:
对每个对象只标识对象名,都未识别其属性和方法。
"活期账户销户界面",具体可能是对话框、Web页面、字符终端界面,但鲁棒图中没有关心这些细节问题。
"客户资料"等实体对象须要持久化吗?不关心,更不关心用Table还是用File或其他方式持久化。
没有标识控制流的严格顺序。
过分关心UI,会陷入诸如有几个窗口,是不是有一个专门的结果显示页面等诸多细节之中,初步设计就没法做了。
别忘了,初步设计的目标是发现职责。初步设计无须展开架构设计细节,否则就背上了"包袱",这是复杂系统架构设计起步时的大忌
---------------------------------------------------------------------------------