8. 初步设计

好的开始是成功的一半。 -- 谚语

所谓鲁棒性分析时这样一种方法: 通过分析用例规约中的事件流,识别出实现用例规定的功能所需要的主要对象及其职责,形成以职责模型为主的初步设计 -- 温昱,《软件架构设计》

Conceptual Architecture阶段包含3个步骤:

第1步,初步设计。
第2步,高层分割。
第3步,考虑非功能需求。

8.1. 初步设计对复杂系统的意义

初步设计并不总是必须的 -- 架构师只有在设计复杂系统时才需要它。

另外,“复杂”与否还和“熟悉”程度有关系。一个“很小”的系统涉及你未接触的领域,你会觉得它复杂的;一个“较大”的系统,但你有很具体的经验,你依然会觉得它“Just so so”。

初步设计的目标简单而明确:那就是发现职责 。初步设计无须展开架构设计细节,否则就背上了“包袱”,这是复杂系统架构设计起步时的大忌。正如“初步设计”这个名字所暗示的,它只是狭义的架构设计的“第一枪”--之前的Pre-Architecture阶段并未对“系统”做任何“切分”。

“初步设计”这个名字还暗示我们,后续的架构设计工作必须以之为基础 。具体而言,初步设计识别出了职责,后续的高层分割方案才能有依据,因为每个“高层分割单元”都是职责的承载体,而分割的目的也恰恰在于规划高层职责模型。

ADMEMS方法强调“关键需求决定架构”的策略,“基于关键功能,进行初步设计”就是一个具体体现。

系统的每个功能都是由一条“职责协作链”来完成的;而初步设计的具体思路正是“通过为功能规划职责协作链来发现职责”。

8.2. 鲁棒图简介

ADMEMS推荐以鲁棒图来辅助初步设计。那么,什么是鲁棒图呢?

8.2.1. 鲁棒图的3种元素

鲁棒图包含3中元素,它们分别是边界对象、控制对象、实体对象。

  • 边界对象对模拟外包环境和未来系统之间的交互进行建模。边界对象负责接收外部输入,处理内部内容的解释,并表达或传递相应的结果。
  • 控制对象对行为进行封装,描述用例中事件流的 控制行为。
  • 实体对象对新鲜进行描述,它往往来自领域概念,和领域模型中的对象有良好的对应关系。

因为“类比思维”在人的头脑中是根深蒂固的,关于鲁棒图3元素的“类比”,自然是MVC。我们做了全面的对比,两者之间还是有不小的差异的。

鲁棒3元素和MVC的主要不同在于:

  • View仅涵盖了“用户界面”元素的抽象,而鲁棒图的边界对象全面涵盖了三种交互,即本系统和外部“人”的交互、本系统与外部“系统”的交互、本系统与外部“设备”的交互。
  • 数据访问逻辑Controller吗?不是。控制对象广泛涵盖了应用逻辑、业务逻辑、数据访问逻辑的抽象,而MVC的Controller主要对应于应用逻辑。
  • MVC的Model对应于经典的业务逻辑部分,而鲁棒图的实体对象更像“数据”的代名词--用实体对象建模的数据既可以是持久化的,也可以仅存在内存中,并不像有的实践者理解的那样直接就等同于持久化对象。

8.2.2. 鲁棒性一例

如银行存储系统的“销户”功能的鲁棒图。

为了实现销户的功能,银行工作人员要访问3个边界对象:

  • 活期账号销户界面
  • 磁卡读取设备
  • 打印设备

“销户”是一个“控制对象”,和“计算利息”一起进行销户功能的逻辑控制。

  • 其中,“计算利息”对“活期账户”、“利息率”、“利息税率”这3个“实体对象”进行读取操作。
  • 而“销户”负责读出“客户资料”......最终销户的完成意味着写入“活期账号”和“销户流式”信息。

8.2.3. 历史

鲁棒图(Robustness Diagram)是由Ivar Jacobson于1991年发明的,用以回答“每个用例都需要哪些对象”的问题。

后来的UML并没有将鲁棒图列入到UML标准,而是作为UML版型(stereotype)进行支撑。

对于RUPICONIX等过程,鲁棒图都是重要的支撑技术。当然,这些过程反过来也促进了鲁棒图技术的传播。

8.2.4. 为什么叫“鲁棒”图

那为什么叫鲁棒图?它和鲁棒性有什么关系?

答案是:词汇相同,含义不同。

软件系统的”鲁棒性(Robustness)“也是经常被翻译成”健壮性“,它同时和”容错性(Fault Tolerance)“含义相同。具体而言,鲁棒性指当下发生时依然具有正确运行功能的能力:非法输入数据、软硬件单元出现故障、未料到的操作情况。

鲁棒图的作用有两点:

  • 初步设计
  • 检查用例规约是否正确和完善

”鲁棒图“正是因为第2点作用而得名的--所以,严格来讲”鲁棒图(Robustness Digagram)“所指的不是"鲁棒性(Robustness)"。

Doug Rosenberg《用例驱动的UML对象建模应用》的描述中,也可得到上述结论:

ICONIX过程中,鲁棒分析扮演了多个必不可少的角色。通过鲁棒分析,您将改进用例文本和静态模型。

  • 有助于区别用例文本的正确性,且没有指定不合理或不可能的系统行为(基于要使用的一组对象),从而提供了健康性检查(Sanity Check)。这种改进使用用例文本的特性从纯粹的用户手册角度变成对象模型上下文中的使用描述。
  • 有助于区别用例考虑到所有必须的分支流程,从而提供了完整的正确性检查。经验表明,为实现这种目标,并编写出遵循某些定义良好的指南的问题,而在绘制鲁棒图上花费的时间,将在绘制时序图时3-4倍的节省下来。
  • 有利于发现对象,这一点很重要,因为在建模期间肯定会遗漏一些对象。你还可以发现对象命名冲突的情况,从而避免进一步造成严重的问题。另外,鲁棒分析有利于确保我们在绘制时序图之前确定大部分实体类和边界类。

8.2.5. 定位

不要再困惑于类似”鲁棒图是分析技术,还是设计技术“这样的问题了。大家只需要记住两个公式:

  • 需求分析 ≠ 系统分析
  • 系统分析 ≈ 初步分析

关于”分析“和”设计“的区分,在《面向对象的系统设计》一书中早已做过精彩的阐述:

在”做什么“和”怎么做“来区分分析与设计,是从结构化方法沿袭过来的一种观点,但即使在结构化方法沿袭过来的一种观点,但即使在结构化方法中这种说法也很勉强......

在”做什么“和”怎么做“为什么会出现上述矛盾?究其根源,在于人们对软件工程中”分析“这个术语的含义有着不同的理解--有时把它作为需求分析(Requirements Analysis)的简称,有时又指系统分析(Systems Analysis),有时则作为需求分析和系统分析的总称。

需求分析时软件工程学中的经典术语之一,名副其实的含义应是对用户需求进行分析,旨在产生一份明确、规范的需求定义。从这个意义上讲,”分析是解决做什么而不是解决怎么做 “是无可挑剔的。

但迄今为止,在人们提出的各种分析方法(包括结构化分析和面向对象分析)中,真正属于需求分析的内容所占的分量并不大;更多的内容是给出一种系统建模方法(包括一种表示法和相应的建模过程指导),告诉分析员如何建立一个能够满足(由需求定义所描述的)用户需求的系统模型。分析员大量的工作是对系统的应用领域模型进行调查研究,并抽象表示这个系统。确切的讲,这些工作应该叫做系统分析,而不是需求分析。它既是对“做什么”问题的进一步明确,也在相当程度上设计“怎么做”的问题。

忽略分析、需求分析和系统分析这些术语的不同含义,并在讨论中将它们随意替换,是造成上述矛盾的根源。

至于实践者为什么常将“需求分析”和“系统分析”混淆,这背后有着重要的现实原因。实际的工程化实践中,需求捕获、需求分析、系统分析并不是完全孤立进行的。想法,它们往往是相互伴随、交叉进行的。需求工作伊始,无疑更多是进行需求捕获工作,相伴进行的需求分析工作所占的比例偏少;但随着掌握的需求信息越来越多,我们需要开展对需求的分析和整理工作也越来越多了;而此时,伴随着对问题的分析,自然而然会在高层次提出相应的应对策略......这恰恰就是系统分析工作。

《软件架构设计》一书中有如下阐述:

需求捕获是获取知识的过程,知识从无到有,葱烧到多。需求采集者必须立即用户所充实的工作,并了解用户客户希望软件系统在哪个方面能够帮助他们。

需求分析是挖掘和整理知识的过程,它在已掌握知识的基础上进行。毕竟,初步捕获到的需求信息往往处于不同的层次,也有一些主观深圳不正确的信息。而经过必要的需求分析工作之后,需求更加系统、更加有条理、更加全面。

那么系统分析呢?如果说,需求分析致力于搞清楚软件系统要“做什么”的话,那么系统分析已经开始涉及“怎么做”的问题了。

《系统分析与设计方法》一书中写道:

简单的说,系统分析的意义如下:“系统分析是针对系统所要面临的问题,搜集相关的资料,以了解产生问题的原因所在,进而提出解决问题的方法和可行的逻辑方案,以满足系统的需求,实现预定的目标。”

需求捕获、需求分析,以及系统分析之间的关系,我们必须理解透彻,否则会影响工作的有效进行。

再次强调,鲁棒图已经“打开”了“系统”这个“黑盒子”,将它划分成很多的不同的职责,所以它是“设计技术”。

8.3. 基于鲁棒图进行初步设计的10条经验

ADMEMS方法归纳了鲁棒图建模的10条经验要点,分别覆盖了语法、思维、技巧、注意事项等4个方面。

语法

  • 遵循建模规则
  • 简化建模语言

思维

  • 遵循3种元素的发现思路
  • 增量建模
  • 实体对象 ≠ 持久化对象

技巧

  • 只对关键功能(用例)画鲁棒图
  • 每个鲁棒图有2~5个控制对象

注意

  • 勿关注细节
  • 勿过分关注UI,除非辅助或验证UI设计
  • 鲁棒图 ≠ 用例规约的可视化

8.3.1. 遵循建模规则

Doug Rosenberg在《UML用例驱动对象建模》中写道:

通过以下4条语言,可以理解该图的本质:

  1. 参与者只能与边界对象交谈。
  2. 边界对象只能与控制对象和参与者交谈。
  3. 实体对象也只能与控制对象交谈。
  4. 控制对象既能与边界对象交谈,也能与控制对象交谈,但不能与参与者交谈。

8.3.2. 简化建模语法

在实践中,简化的鲁棒图语法将有利于你集中精力进行初步设计,而不是关注细节。

例如,鲁棒图根本不关心“IF语言”怎么建模。

值得注意的是,业界有些观点(包括一些书)认为鲁棒图是协作图。因此造成了鲁棒图的语法非常复杂,不利于专注于初步设计。

其实,鲁棒图是一种非常特殊的类图。

8.3.3. 遵循3种元素的发现思路

用例(Use Case) = N个场景(Scenario)。每个场景的实现都是一连串的职责进行协作的结果。所以,初步设计可以通过“研究用例执行的不同场景,发现场景背后应该有哪些不同的职责”来完成。

8.3.4. 增量建模

“建模难”,有些人常如此感叹。例如,在画鲁棒图时,许多人以上来就卡在了“搞不清应该有几个界面”的问题上,就会发出“建模难”的感叹。

下面演示“增量建模”这种技巧。从小处讲,增量建模能解决鲁棒图建模卡壳的问题;从大处将,这种方式适合所有种类的UML图建模实践。

例如,类似WinZip、WinRar这样的压缩工具大家都用过。请一起来为其中的“压缩”功能进行基于鲁棒图的初步设计。

首先,识别最明显的职责。对就是你自己认为最显著的几个职责--不要任务设计和建模有严格的标准答案。

  • 源文件
  • 压缩包
  • 压缩器(负责压缩处理)

接下来,开始考虑职责间的关系,并发现新职责。压缩器读取源文件,最终生成压缩包。

这里将打包器独立出来,它是受了压缩器的委托而工作的。还有字典......

继续同样的思维方式,又引入了压缩配置,它影响着压缩器的工作方式,例如加密压缩、分卷压缩或其他。

压缩功能还要显示压缩进度,以及随时取消进行一半的压缩工作。所以,你又识别出了压缩进行界面和监听器等职责。

模型之于人,就像马匹之于人一样--它是工具。如果你不知道怎样真正将“模型”为自己所用,反而为“建模”所累(经典的“人骑马、马骑人”的问题),请问自己一个问题:

  • 我是不是被太多的假设限制了思维?,或许,工具本身根本没有这样的限制我!

8.3.5. 实体对象 ≠ 持久化对象

实体对象涵盖的更广泛,它可以是持久化对象,也可以是内存中的任何对象。

一方面,在实践中,有些系统需要在内存中创建数据的“暂存体”以保持中间状态,当然可以被建模成实体对象。另一方面,有的系统没有持久化数据,但是基于鲁棒图的设计依然可以用,此时难道鲁棒图不包含实体对象吗?显然不对。

因此,实体对象 ≠ 持久化对象,这个正确认识将有助于你的实践。

8.3.6. 只对关键功能(用例)画鲁棒图

基于“关键需求决定架构”的历练,功能需求作为需求的一种类型,在架构设计时不必针对每个功能都画出鲁棒图。

8.3.7. 每个鲁棒图有2~5个控制对象

既然是初步设计,鲁棒图建模时,针对关键功能的每个鲁棒图中的控制对象不必太多太细,5个是常见的上限值。

相反,若实现某个功能的鲁棒图只包含一个控制对象,则是明显的“设计不足”--这个控制对象的名字必然和功能的名词相同,这意味着没有对职责进行真正的切分。

例如,WinZip的压缩功能设计成下面的鲁棒图,几乎没有任何意义。

8.3.8. 勿关注细节

初步设计不应该关注细节。例如,回顾前面所示的“销户”的鲁棒图:

  • 对每个对象只标识对象名,都未识别其属性和方法。
  • “活期账户销户界面”,具体可能是对话框、Web页面、字符终端界面,但鲁棒图中没有关心这些细节问题。
  • “客户资料”等实体对象必须要持久化吗?不关心,更不关心用Table还是File或其他方式持久化。
  • 没有标识控制流的严格顺序。

8.3.9. 勿过分关注UI,除非辅助或验证UI设计

过分关系UI,会陷入诸如有几个窗口,是不是有一个专门的结果显示页面等诸多细节之中,初步设计就没法做了。

别忘了,初步设计的目标是发现职责。初步设计无需展开架构设计细节,否则就背上了“包袱”,这是复杂系统设计起步时的大忌。

8.3.10. 鲁棒图 ≠ 用例规约的可视化

鲁棒图时设计,“系统”已经别切分成不同的职责单元。而用例规约是需求,其中出现的“系统”必定是黑河。所以,两者有本质区别。

8.4. 贯穿案例

接下来考虑贯穿案例PASS系统,如何借助鲁棒图进行初步设计呢?

再次明确一下几点:

  • 初步设计的目标是发现职责,为高层切分奠定基础
  • 初步设计不是必须,但待设计的系统对架构师而言并无太多直接经验时,强烈建议进行初步设计
  • 基于关键功能(而不是所有功能),借助鲁棒图(而不是时序图)进行初步设计

下面,我们一起思考如何针对“实时检查处方”功能进行初步设计--重点体会“增量建模”的自然和强大。

首先,识别最明显的职责 。先识别出最不可或缺的、体现整个功能价值所在的、与“处方检查结果”相关的几个职责。

实时检查处方:先识别最明显的职责

接下来开始考虑职责间的关系,并发现新职责。检查结果是如何产生的呢?检查这个控制对象,读取处方和用户规则信息,最终生成处方检查结果。

实时检查处方:开始考虑职责间的关系,并发现新职责

OK,如此一来,解决了“结果是怎么来的这个问题”。

实时检查处方:开始考虑职责间的关系,并发现新职责实时检查处方:开始考虑职责间的关系,并发现新职责

继续以同样的思维方式解决问题。PASS系统自动检查处方,是由HIS系统中意识工作站的调用触发的,处方信息也是通过某种方式(例如参数或XML文件)从HIS医生工作站获得的。

实时检查处方:开始考虑职责间的关系,并发现新职责

实时检查处方最终的鲁棒图又进一步考虑了“记录违规用药”这一具体功能场景的支持。

实时检查处方:直到模型比较完善

概念架构设计时推荐只对关键功能进行鲁棒图建模。例如,另一关键功能“自动更新用药规则”的鲁棒图设计如下:

自动更新用药规则:基于鲁棒图的初步设计

下一节:复杂性是层次化的。 -- Frederick.P.Brooks,《人月神话》

分析与综合是思维方向相反的过程。一部是先分析后综合,没有分析就不能综合;没有综合的分析,也只有片面的分析。 -- 肖纪美,《梳理人、事、物的纠纷:问题分析方法》

“架构 = 模块 + 接口”的做法,其不足可概括为两点。

第一,忽视了多视图。“模块 + 接口”仅是逻辑架构设计视图的核心内容,而软件系统的架构设计还可能涉及开发视图、运行视图、物理视图、数据视图等多方面的考虑。

第二,忽略了概念架构设计。对规模较大的系统而言,都必须先根据重大风险(包含功能方面、质量方面、约束方面),有针对性的制定包括“高层分割”在内的设计决策,然后才是“模块 + 接口”一级的设计。

那么,如何对软件系统进行“高层分割”呢?这属于Conceptual Architecture阶段第2步的工作。也真是这一章要说的主题。