软件架构设计的思想与模式


◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 1 - 软件架构设计的思想与模式 中科院计算所培训中心中科院计算所培训中心中科院计算所培训中心中科院计算所培训中心 谢新华谢新华谢新华谢新华 前言 在软件组织中,架构师的作用举足轻重。软件的质量很大程度上是由架构设计的质量来决定 的。为了创建高质量的软件产品、增强产品的竞争力,培养高水平的架构师队伍,建立有效的架 构团队,提升架构师队伍的分析与设计能力,成为企业关注的重心。多年来的实践告诉我们,软 件产品仅凭优良的代码是不够的。不少大型软件企业集中了相当多的优秀编码人员,但还是不断 受到需求变更之苦,产品质量下降,不断返工造成成本上升。究其原因,很多情况是由于设计人 员只关注到实现需求,而没有仔细思考整体体系结构设计的思想,更没有关注到开发过程的改变, 会极大的影响架构设计,以及由此引发的产品设计每一阶段重点的变化。因此,在软件架构设计 的时候,我们应该注意以下基本思想: 1,在经典瀑布式模型下,已经形成了一套行之有效的分析和设计过程。但是近年来,由于 项目越来越大、越来越复杂,软件的易变性带来的冲击也越来越令人难以接受,促使业内研究更 加合理的项目过程,敏捷过程就是其中有代表性的新方法,但是,不少企业应用敏捷模型往往导 致项目混乱和难以控制。除了管理上的问题之外,没有注意到敏捷开发的基础是架构驱动也是一 个重要原因,所以,我们必须研究敏捷过程下的架构设计问题。其中,关注点在于开发过程的改 变,会极大的影响架构设计,以及由此引发的产品设计每一阶段重点的变化。 2,规模软件经济的理念,对设计方法和思路提出了完全不同的要求,重用成为重要的主题。 复用的思想,目前正经历从下游到上游延伸的过程,从设计模式延伸到分析模式、业务模式。架 构决不是一个孤立的设计问题,一个好的架构师必须从业务领域到需求分析直至架构设计具有深 刻而且现代的理解,模型驱动的设计与开发是我们必须认真研究的问题。 3,架构优化与应对变更必须对业务的变与不变进行深入分析,之中有两个关注点:一个是 业务流程不变而业务单元可能变,另一个是业务单元不变而业务流程可能变,这是在两个不同粒 度下的思考基点。第一个处理方法是合理应用设计模式而封装变化,第二个处理方法就是应用面 向服务的架构进行业务流程的敏捷性处理,这是从两个不同侧面讨论架构问题,需要我们有深入 而现代的理解。 4,我们不需要泛泛讨论软件架构设计一般方法与过程,而是应该针对上述核心问题和关键 思考点,从系统的角度寻找相应的对策和解决方案。研究架构设计不能仅仅依靠别人已有的产品 架构,而是要吃透这些架构的思维核心,找到每一个困难节点解决问题的办法,根据自己的情况, 依照问题、对策与解决方案的步骤,建立符合自己项目要求的架构策略,达到从方法论的角度、 从质量属性对架构设计影响的角度,多视角、全方位的在理论和实践两方面研究问题,这样才可 能把项目引向成功。 架构设计面对的问题很多,泛泛的介绍所有问题是没有意义的,只有抓住重点深入研究解决 实际问题,才能使架构设计为软件开发全过程提供强大的支撑。在这些年的咨询和指导的经历中, 我们已经为多个知名企业和开发团队提供了分析与设计技能的服务,也进行了很多问题与解决方 案的思考。这些思考大多数都融入本课程的内容中。希望学员在今后架构设计的实践中,大大提 高设计水平,为企业创造更高的可度量价值。 中科院计算所培训中心 谢新华 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 2 - 第一章 现代软件开发过程及架构策略 任何设计方案都与过程有关,过程的改进必然对设计方法的改变产生深远影响。任何设计方案都与过程有关,过程的改进必然对设计方法的改变产生深远影响。任何设计方案都与过程有关,过程的改进必然对设计方法的改变产生深远影响。任何设计方案都与过程有关,过程的改进必然对设计方法的改变产生深远影响。 1.1 软件架构设计师的的知识体系软件架构设计师的的知识体系软件架构设计师的的知识体系软件架构设计师的的知识体系 一一一一、、、、软件架构的定义与问题软件架构的定义与问题软件架构的定义与问题软件架构的定义与问题 软件架构(software archiecture )也称之为软件体系结构,它是一组有关如下要素的重要决 策:软件系统的组织,构成系统的结构化元素,接口和它们相互协作的行为的选择,结构化元素 和行为元素组合成粒度更大的子系统的方式的选择,以及指导这一组织(元素及其接口、协作和 组合方式)的架构风格的选择。 软件架构是对系统整体结构设计的刻划,包括全局组织与控制结构,构件间通讯、同步和数 据访问的协议,设计元素间的功能分配,物理分布,设计元素集成,伸缩性和性能,设计选择等。 我们之所以称之为高级系统架构而不是软件体系结构,是因为我们希望大家已经有了比较多 的架构设计经验,有了多年的实践经验,现在到了把这些实践经验归纳整理,上升到理论高度的 时候了。一个好的架构师必须知识面宽阔、看问题的眼光深邃、处理问题的方法准确,好的架构 不仅仅是考虑技术层面,还要考虑软件过程的特点、项目管理的方式、需求分析的程度、质量保 证体系、以及用户的关注点等等。因此,高质量软件并不能只靠一个节点解决问题,而是需要有 一个全面的解决方案。 重要的是要知道,一个软件系统的质量,很大程度上是由架构设计的质量决定的,所以,研 究架构设计的思想和方法,成为软件设计领域中一个十分引人注目的问题。但是很长时间以来, 人们大都把目光关注在流程、方法、结构原理甚至编码的本身,而不太注意架构设计最本质的东 西,思考的深度也欠深入,结果,很多产品即使设计出来,后期运行中也是问题百出,特别是发 生变更的时候带来了很大的困难。这就给我们提出了一个问题,架构设计的核心思维到底是什 么?什么是好的架构设计呢? 首先,任何软件系统都是以满足需求作为目的,所以,好的架构设计必须以深入全面的需求 分析作为基础,事实上架构设计是没有统一的模式的,任何模式只有针对问题才有意义。作为架 构设计来说,必须对需求分析有足够的理解,这样才能有针对性地解决问题,才可能设计出真正 优秀的产品来。 另一方面,任何架构思想的实现,都依赖于好的项目管理。项目管理必须与架构思想相匹配 才能发挥作用。因此,系统架构师应该仔细研究现代项目管理的思想和方法,吃透其中的精髓, 根据自己的设计思想,提出项目管理的解决方案。反之,一个项目管理方案,不可避免的也会影 响到架构设计的特点。 作为一个架构师来说,上述的讨论引发了三个核心思维,一个是架构设计的源泉来自于需求 分析,第二个是架构设计重心和特点来自于质量需求(非功能性需求),第三个观点是,架构的 实现依赖于好的项目管理。因此,软件架构设计是一个系统工程,它需要系统构架师有很宽的知 识面,从需求分析、架构设计到类设计甚至代码实现一直到项目管理都需要有透彻的理解,这之 间的关系是你中有我我中有你,是不可能截然分开的。必须说明,软件系统设计的方法不是一个 僵化的规则,关键是在实践中实事求是的摸索规律,从而找出符合实际达到要求的设计来。 研究软件构架有利于发现不同系统的高层共性、保证灵活和正确的系统设计、对系统的整体 结构和全局属性进行规范约束、分析、验证和管理。将构架作为系统构造和演化的基础,可以实 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 3 - 现大规模、系统化的软件复用。研究软件架构对于进行高效的软件工程具有非常重要的意义:  通过对软件架构的研究,有利于发现不同系统在较高级别上的共同特性;  获得正确的架构对于进行正确的系统设计非常关键;  对各种软件架构的深入了解,使得软件工程师可以根据一些原则在不同的软件架构之间 作出选择。 二二二二、、、、在信息技术战略规划在信息技术战略规划在信息技术战略规划在信息技术战略规划((((ITSP))))中的软件架构中的软件架构中的软件架构中的软件架构 好的架构设计,必须在信息系统战略规划(ITSP)的大环境下进行设计,才可能设计出真正 优秀而且有价值的系统来,那么什么是信息系统战略规划呢? 信息技术战略规划(ITSP)的核心思想简述如下: 在信息时代知识经济的背景下,正确的结合 IT 规划,整合企业的核心竞争力,在新一轮的 产生、发展中取得更大的市场竞争力是必要的。 信息化的问题首先是企业管理层概念的问题。企业管理层的重视,和对信息化的高度认同是 企业信息化的关键所在。当前国内很多企业管理层很关心资本运作的问题,而对很多国内企业而 言,管理层最关心的是扭亏增盈。信息化建设投入大、周期长、见效慢、风险高,往往不是企业 需要优先解决的问题,导致管理层对信息化的重视程度不够,无法就信息化建设形成共识 企业管理信息化必然带来管理模式的变化,如果对这种变化不适应,有抵触心态,或者仅是 为了形象问题,赶潮流搞信息化。或者由于国家提出信息化带动工业化,信息化成为一种时髦, 信息化工程往往成为企业的形象工程。结果软件架构的设计仅仅是企业目前业务过程的复制,并 不可能给企业带来实实在在的好处。 有些公司缺乏统一完整的 IT 方向,希望上短平快的项目,立竿见影,跳过系统的一些必要 发展阶段,导致系统后继无力,不了了之。由于方向不明确,企业内部充斥着各种各样满足于战 术内容的小体系,并不能给企业带来大的好处。 有些公司对信息化建设的出发点不明确,在各个方案厂商铺天盖地的宣传下,不能很好的把 握业务主线,仅是为了跟随潮流,既浪费了资源,同时也对后继的信息化造成了不良的影响,甚 至直接导致“领导不重视”这样的后果。 如今国家正在大力推广企业信息化。然而人们大多从技术角度来谈论信息化和评价解决方 案,他们往往脱离了企业的实际需要,以技术为本是不能根治企业疾病的。企业依然必须明确自 己的核心竞争力。明确一切的活动和流程都是围绕让核心竞争力升值的过程。IT 规划意识如此, 必须以企业核心、业务为本。再结合公司的实际情况。开发自己需要的系统。战略规划是一套方 法论,用于企业的业务和 IT 的融合以及 IT 自身的规划。必须满足如下要求: 1. 先进性先进性先进性先进性:采用前瞻性、先进成熟的模型、方法、设备、标准、技术方案,使建议的企业信 息方案既能反映当前世界先进水平,满足企业中长期发展规划,又能符合企业当前的发展步调, 保持企业 IT 战略和企业战略的一致性。 2. 开放性开放性开放性开放性:为保证不同产品的协同运行、数据交换、信息共享,建议的系统必须具有良好的 开放性,支持相应的国际标准和协议。 3. 可靠性可靠性可靠性可靠性:建议的系统必须具有较强的容错能力和冗余设备份,整体可靠性高,保证不会因 局部故障而引起整个系统瘫痪。 4. 安全性安全性安全性安全性:建议规划中必须考虑到系统必须具有高度的安全性和保密性,保证系统安全和数 据安全,防止对系统各种形式的非法入侵。 5. 实用性实用性实用性实用性:规划中建议的系统相关必须提供友好的中文界面的规范的行业用语,并具有易管 理、易维护等特点,便于业务人员进行业务处理,便于管理人员维护管理,便于领导层可及时了 解各类统计分析信息。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 4 - 6. 可扩充性可扩充性可扩充性可扩充性:规划不仅要满足现有的业务需要,而且还应满足未来的业务发展,必须在应用、 结构、容量、通信能力、处理能力等方面具有较强的扩充性及进行产品升级换代的可能性。 为了实现这样的规划,我们必须注意到,软件设计既是面对程序的技术,又是聚焦于人的艺 术,成功的软件产品来自于合理的设计,而什么是合理的设计呢? 一个软件架构师最重要的问题,就是他所设计的产品必须是满足企业战略规划的需求,能够 帮助企业解决实际问题的,因此一个合理的设计,首先要想的是: Who :为谁设计? What :要解决用户的什么问题? Why :为什么要解决这些用户问题? 这是一个被称之为 3W 的架构师核心思维,如果这个问题没搞清楚,就很快的投入程序编写, 那这样的软件在市场上是不可能获得成功的。 Who? What? Why? 这三个问题看似简单,但实际上落实起来是非常困难的。我们经常会看 到一些产品,看似想的面面俱到,功能强大,甚至和企业目前的业务吻合得非常好,但为什么最 终没有给企业带来实实在在的好处相反带来麻烦呢?一个专家感觉非常得意、技术上非常先进的 系统,企业的使用者未见得感觉满意,这些情况在我的实践中屡见不鲜,即使一些知名的公司在 设计的产品,往往都不能很好地把握,这足以证明我们必须下功夫来面对它。 那么,我们该怎么来做呢? 为谁设计,表达的是我们必须认真研究企业的业务领域,研究企业本身的工作特点,通过虚 心请教和深入研究,使我们对于企业本身的业务有深刻的理解,最后形成针对这个企业的实事求 是的解决方案。 要解决用户的什么问题,表达的是我们必须把企业存在的问题提取出来,分析研究哪些问题 是可以用信息化技术来解决的,采用信息化技术以后,企业的业务过程需要做什么样的更改,以 及这些更改会带给企业什么样的正面和负面的影响。仅仅用计算机来复制企业现有业务过程是不 可取的,关键是要做到因为信息系统的使用,使企业业务方式发生革命性的变化,使信息系统成 为企业业务不可分割的一部分,而不仅仅是辅助工具。 为什么要解决这些用户问题,表达的是如何帮助企业产生可度量的价值,而这些价值是在研 究企业目前存在的问题的基础上产生的,没有这些价值的产生,软件系统的投资是没有意义的。 价值不可度量,企业领导者是不可能积极的支持信息化的。还要注意我们的设计必须便于用户使 用,减少维护和培训的资源消耗,而且制作生产工艺尽可能简单,这就是设计之本。 任何项目都是由项目的陈述开始的,在陈述的过程中比较难解决的问题是表达可度量的价 值,所谓可度量价值实际上是一个预估,只是说由于加入了信息系统,解决了过去存在的问题, 从预估的角度业务水平可能提升的一个度量数据。但并不等于说我管理依然是混乱的,只要有了 这个信息系统,什么事情都不要做就可以达到这个水平了,这实际上是不切实际的幻想。所谓信 息系统战略规划的本质,并不是说信息系统可以包打天下,而是说在整体规划下的信息系统,提 升了我们的组织管理水平,减少了不必要的环节,提高了效率,通过全方位的努力,在可预测的 未来,确实可以提升整体的经济效益,而且这个预测经过努力是可以达到的。 1.2 从线性模型到迭代模型从线性模型到迭代模型从线性模型到迭代模型从线性模型到迭代模型 研究软件企业架构,不可避免的需要研究软件开发过程的有关问题,因为正是由于现代项目 管理与过程管理的巨大变化,才对架构设计提出了新的要求,造成了现代架构设计与经典概要设 计方法上根本的不同。所以,我们必须要仔细研究一下现代软件开发过程有什么变化呢? 一一一一、、、、经典软件开发过程模型经典软件开发过程模型经典软件开发过程模型经典软件开发过程模型 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 5 - 软件开发过程描述的是软件构造、部署还有维护的一种方法,早在 20 世纪 70 年代,人们为 了改变软件开发混乱、随意、无秩序的状态,提出了经典的瀑布式软件开发过程。在这样的模型 下,人们发展了经典的项目管理方法,提出了诸如 PERT(计划评审技巧)、甘特图以及早期实 现软件预估的一系列项目管理原则。 在经典项目过程中软件分析占了软件设计很大一部分工作量,用户、市场、分析、设计,是 整个软件设计中密不可分的几个部分。模型要求在任何设计和实现工作之前,尽可能的推敲,把 需求完全定义清楚,并把它稳定下来,并且实际开发前冻结需求。在概要设计阶段主要需要建立 系统高层模型,建立系统和子系统的框架以及基于服务的层等。在详细设计阶段,可以精细的把 业务需求转换为系统模型。然后在实现诸如编码、测试、系统集成以及发布等下游模型。经典瀑 布式过程的大致情况如下图所示。 二二二二、、、、经典项目管理导致失败的原因经典项目管理导致失败的原因经典项目管理导致失败的原因经典项目管理导致失败的原因 经过 20 年的应用,人们发现经典项目过程存在很多问题,这迫使人们研究它存在的问题, 以期找到解决方案。一个项目成功的关键是有良好的规划,产品在初期规划的时候,应该说明产 品应该具备哪些能力?在什么时间完成?需要哪些资源?需要多少资源?这些信息有助于我们 通过项目规划减少风险、降低产品目标的不确定性、为更好的决策提供支持,这是构建良好的项 目管理必备的信息。但是,经典项目管理最困难的问题是项目难以规划,做出来的计划往往会出 错。于是开发小组往往走向两个极端:要么是不做规划,这样的小组根本无法回答“你们什么时 候能完成?”这样的问题。要么是在计划中投入大量的精力,直到自己确信计划是正确的,但这 种“正确”往往是自欺其人的,这种计划可能更全面,但不意味着更准确或更有用。因为软件的 特点不确定性是始终存在的。 下图显示了 Boehm 所考虑的不确定性在顺序开发过程不同点的范围,这个图称之为不确定 性锥形。图上表明,在项目定义阶段,对进度估计的偏差可能达到 60% ~160% ,也就是说一个 预期 20 周完成的项目,实际花费可能在 12 ~32 周之间。而在写下需求之后,估计的偏差可能还 在±15% ,这时的 20 周预期可能实际上可能会花 17 ~23 周。这么大的预估偏差根本就无法实施 有效的项目管理。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 6 - 尽管经典项目管理把费用、质量(功能目标)、时间设定为最重要的三角约束,遗憾的是很 多人的研究都告诉我们,传统方法不一定会得出满意的答案,下面是很多人做的研究结果:  大约 2/3 的项目会显著超出费用预算(Lederer and Prasad 1992 )。  产品中 64% 的功能很少或者从不会被使用(Johnson 2002 )。  一般项目花费的时间会超出进度表 100% (Stndish 2002 )。 这就迫使我们仔细研究项目失败的原因,归纳起来可以有 5 个原因。 1,,,,基于活动而不是基于功能进行规划基于活动而不是基于功能进行规划基于活动而不是基于功能进行规划基于活动而不是基于功能进行规划 传统项目管理方法的一个关键问题是,它们强调的是各项活动的完成而不是对功能的交付。 以传统方法管理的甘特图或者工作分解结构指明了要进行的活动,我们可以基于这种方法来度量 开发小组的进度。这种基于活动的管理存在着一些问题: 第一个问题是:客户并不能从活动的完成获取价值,功能才是客户价值的计量单位。所以, 规划项目应该是在功能层面上的而不是活动层面上的。 第二个问题是:当我们审阅进度表的时候,我们实际上是在寻找被遗忘的活动而不是缺少的 功能。 第三个问题是:基于活动的计划往往导致项目超期,而一旦面临项目超期,项目组或者通过 不恰当的降低质量来节省时间;或者通过变更控制程序来限制变动,即使是是高价值的功能变更, 也往往被限制。 为什么基于活动的规划会导致项目超期呢?我们可以从三个方面来讨论它的原因: 1))))活动不会提前完成活动不会提前完成活动不会提前完成活动不会提前完成 设想某一个资深程序员正在为一个产品编制一个有趣的新功能,同时他还承担了一个为 CMMI 审核准备文档的任务,他会怎么样分配时间?毫无疑问,他会把大部分时间用于编制这个 有趣的程序,而准备审核只留下了刚刚够用的时间,而且是到最后才不得不草草完成。实际上这 种行为非常普遍,以至于有了一个名称叫帕金森定律(Parkinson’s Law ,1993 ):“工作总是要拖 到最后一刻才完成。” 如果一项工作在甘特图上分配了 5 天,处理这个活动的员工一定会用满 5 天,即使能够提前 完成,他也会通过增加一些花哨的修饰(镀金需求),或者尝试着用一些新的热点技术。在不少 企业文化中,一项活动提前完成,往往被指责为当初他做的估计不准确,或者会另派其它的任务 给他,为什么要冒这个风险来提前完成呢?人的本性就是如此:用多余的时间去做一些对自己有 价值,但对别人不一定有用的事情。 2))))延误沿着进度表向下传递延误沿着进度表向下传递延误沿着进度表向下传递延误沿着进度表向下传递 由于传统的计划是基于活动的,因此它们主要关注活动之间的依赖性。考察下面的甘特图, 它显示了 4 项活动及它们之间的依赖关系。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 7 - 如果需要提前完成测试,就要求一些事件的幸运巧合:  对中间层的编码提前完成,而它受到向数据库添加数据表这一活动完成时间的影响。  对用户界面的编码提前完成。  提前安排好测试人员。 关键之处在于,即使一个如此简单的案例中,在启动测试之前也有 3 件事情必须发生,但是 下面任何一件事情,都可能导致测试推迟:  用户界面编码结束时间延误。  对中间层编码花费时间超出计划。  中间层编码花费时间符合要求,但是向数据库添加数据表结束过晚,导致延误了测试时 间。  未安排好测试人员。 也就是说提前启动测试需要完成很多事情,其中一件事情延误,都可能导致总体上的延误。 事实上我们已经确认了活动很少会提前完成,所以发生的绝大多数事情是延误,而这种延误 会沿着进度表传递下去,最终导致后续项目很少会提前启动。 3))))活动不是独立的活动不是独立的活动不是独立的活动不是独立的 当一项活动的时间范围不影响另一项活动的时间范围的时候,我们称这两项活动是独立的。 在建筑工程上,很多活动是独立的,比如粉刷墙壁和挖掘地下室。但是,在软件开发中,大部分 活动之间是不独立的。比如编写客户端程序,如果第一个界面花费的时间超过计划 50% ,那么 接下来每个界面花费的时间都要比计划的多。换句话说,如果一项活动花费了比计划更多的时间, 那么所有类似的活动都可能花费比计划更多的时间,积累起来数据就会非常庞大。 2,,,,多任务处理导致更多的延迟多任务处理导致更多的延迟多任务处理导致更多的延迟多任务处理导致更多的延迟 传统项目管理方法会失败的第二个原因是多任务处理。多任务在处理两件事情上有时候是有 道理的,当在一项工作中受阻的时候,暂时放下这个工作去做第二件事情,也可能回过头来第一 件工作的障碍就在不知不觉中消除了,这是很多人的经验。但是如果同时处理 3 个甚至以上的任 务,那么任务之间的切换所花费的时间和代价就会大大增多,会对生产率产生可怕的影响。Clark 和 Wheelwright (1993 )曾经研究了多任务的影响,他们得出的增值与任务数之间的关系如下图 所示。 在软件项目中,由于各种任务之间的依赖性很强,多任务带来的问题更加严重,下面我们来 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 8 - 研究一个场景。在项目中有些活动被延误的时候,常常会出现多任务处理的问题。 假定某个任务活动之间的依赖关系如下图所示,每个活动需要的时间使 10 天,整个任务完 成时间是 30 天。 但是,用户界面的开发者希望更早开始工作,他要求 API 的开发者首先开发那些可以让他 开始工作的 API,同样的,测试人员也要求开发那些可以使测试自动化的部分用户界面,于是进 度表变成了下面的样子。 表面上看起来工作速度提高了,似乎大家都在忙,但仔细想想并不是这么回事情。每项任务 的完成时间由 10 天变成了 20 天,而每项工作的延误都会迅速向同类工作传递开,特别是这些工 作的依赖性并不是这么简单,它还会影响到其它的相关任务。再者,多项任务的频繁切换会大大 降低生产率,造成工作的混乱,把工作分配到个人而不是小组,会使这个情况更加恶化。 另一方面,所有人都在忙碌却无法保留一定的余量用于处理任务中固有的可变性,100% 负 荷的车辆挤在一条 100% 负荷的高速路上,结果谁都无法取得进展。 3,,,,不按照优先级开发功能不按照优先级开发功能不按照优先级开发功能不按照优先级开发功能 传统项目管理方法不一定能够带来高价值产品的第三个原因是,制定的计划没有按照对用户 或者客户所具有的价值大小来排列工作的优先级。很多传统的计划实际上假定工作都可以完成, 工作的顺序主要是由依赖性来决定。但是随着项目结束时间的逼近,开发小组会匆忙放弃一些功 能以跟上进度,由于不是按照功能优先级顺序开发的,某些被放弃的功能反而比交付的功能更重 要。 4,,,,忽视了不确定性忽视了不确定性忽视了不确定性忽视了不确定性 传统规划方法的第 4 个缺点,是不承认不确定性的存在。人们假定最初的需求分析就可以产 生对产品来说完整的、完善的定义。我们假定用户在计划覆盖的整个时间内都不会改变想法,他 们的观点也不会更细化,也不会提出新的需求。 与之类似,我们还忽视了如何构建产品这样的不确定性,某种技术在实现中才发现并不一定 合适,但我们已经给它分配了确定的时间(两个星期),事实上我们不可能指望一开始就确定项 目进程中所需要的所有活动,但我们又不愿意承认这一点。 即使考虑了这些不确定性,进度表也被描述成了一个简单的、绝对的日期。“我们会在 6 月 30 日交付。”是不是能够用更确切的“我们会在 6 月到 8 月的某个时间交付。”这样是不是让人 感觉更可靠?事实上,随着项目的进展,项目的不确定性和风险会逐渐减少,这样就可以做出更 精确的估计,但这样一种处理问题的风格,在受过严格经典项目管理训练的管理层面前,会不会 受到认可? 5,,,,把估计当成了承诺把估计当成了承诺把估计当成了承诺把估计当成了承诺 在每个估计中都包含了一个可能性,它表示了某个特定工作在估计的时间内得以完成的可 能。假定开发一个高端字处理软件,它在一周内完成的可能性是 0% ,在 10 年内完成的可能性 是 100% ,但介于 1 周到 10 年之间的任何估计值,都是位于 0~100% 的可能性。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 9 - 如果开发小组或者利益相关人把估计当成承诺,传统项目管理方法就会出现问题。对可能性 做出承诺几乎是不可能的。 在分析了传统项目管理方法所存在的问题以后,我们对那么多项目令人失望就不会感到奇怪 了。基于活动的规划方法分散了我们对功能的注意力,而功能才是衡量客户价值的单元。本来项 目的参与者满怀好意的把多任务处理当成解决延误的办法,结果却造成项目更加的延误。当进度 表剩余时间不够的时候,不可避免地要放弃一些功能,由于开发人员是按照最有效的开发方式来 安排进度的,因此放弃的功能对用户来说不一定是价值最低的。 忽视用户需求的不确定性,会导致虽然按时完成了任务,但很多后来发现的重要功能没有办 法加入,或者即使加入了但要不是项目延误,或者不恰当的降低质量。 这是在这些分析的基础上,我们就必须考虑解决这些问题的办法。 三三三三、、、、软件开发增量模型的提出软件开发增量模型的提出软件开发增量模型的提出软件开发增量模型的提出 对瀑布模型的一个关键性的改进,是所谓增量模型的出现。增量模型是指首先构建部分系统, 再逐渐增加功能或者性能的过程。它降低了取得初始功能之前的成本,强调采用构建方法来帮助 控制更改需求的影响,也提高了创建可操作系统的速度。 增量模型是综合了瀑布模型和原型化的产物,提倡以功能渐增方式开发软件,经验表明,这 种增量模型在特大型项目和小型项目中同样适用。增量模型描述了为系统需求排定优先级然后分 组实现的过程,每个后续版本都为先前版本增加了新功能。 在生命周期的早期阶段(计划、分析、设计),需要建立一个考虑了整个系统的架构,这个 架构应该是具有强的可集成性的,后续的构件方式开发,都是建立在这个架构之上。剩下的生命 周期阶段(编码、测试、交付),来实现每一个增量。 首先创建的应该是一组核心的功能,或者对于项目至关重要的最高优先级的系统,或者是能 够降低风险的系统。随后基于核心功能反复扩展,逐步增加功能以提高性能。 增量模型的优点:  整个项目的资金不会被提前消耗,因为首先开发和交付了主要功能和高风险功能。  每个增量交付一个可操作的产品。  每次增量交付过程中获取的经验,有利于后面的改进,客户也有机会对建立好的模型作 出反应。  采用连续增量的方式,可把用户经验融入到细化的产品,这比完全重新开发要便宜得多。  “分而治之”的策略,使问题分解成可管理的小部分,避免开发团队由于长时间的需求 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 10 - 任务而感到泪丧。  通过同一个团队的工作来交付每个增量,保持所有团队处于工作状态,减少了员工的工 作量,工作分布曲线通过项目中的时间阶段被拉平。  每次增量交付的结为,可以重新修订成本和进度的风险。  便于根据市场作出反应。  降低了失败和更改需求的风险。  更易于控制用户需求,因为每次曾两开发的时间很短。  由于不是一步跳到未来,所以用户能逐步适应新技术。  切实的项目进展,有利于进度控制。  风险分布到几个更小的增量中,而不是集中于一个大型开发中。  由于用户能够从早期的增量中了解系统,所以更加理解后面增量中的需求。 增量开发必须注意的问题:  良好的可扩展性架构设计,是增量开发成功的基础。  由于一些模块必须在另一个模块之前完成,所以必须定义良好的接口。  与完整的系统相比,增量方式正式的回顾和评审更难于实现,所以必须定义可行的过程。  要避免把难题往后推,首先完成的应该是高风险和重要的部分。  客户必须认识到总体成本不会更低。  分析阶段采用总体目标而不是完整的需求定义,可能不适应管理。  需要更加良好的计划和设计,管理必须注意动态分配工作,技术人员必须注意相关因素 的变化。 1.3 大型复杂项目敏捷模型的提出大型复杂项目敏捷模型的提出大型复杂项目敏捷模型的提出大型复杂项目敏捷模型的提出 增量模型的提出,需要有一整套相应的项目管理理念与之匹配,人们通过多年的努力,从理 论上和实践上作了艰苦的探索,提出了比较完整的敏捷项目管理方法。需要说明的是,敏捷开发 并不是回归到当初混乱无计划开发状态,更不是赞成随意性。敏捷方法是在长期的实践中,对瀑 布式模型存在问题的分析结果,也是从发现问题、分析问题到解决方案的过程。对管理水平上的 要求是更高而不是更低。 一一一一、、、、敏捷开发的价值观敏捷开发的价值观敏捷开发的价值观敏捷开发的价值观 实际上敏捷开发运动在数年前就开始了,但它正式开始的标志是 2001 年 2 月的“敏捷宣言” (Agile Manifesto ),这项宣言是由 17 位当时称之为“轻量级方法学家”所编写签署的,他们的 价值观是:  个人与交互重于开发过程与工具;  可用的软件重于复杂的文档;  寻求客户的合作重于对合同的谈判;  对变化的响应重于始终遵循固定的计划。 个人与交互重于开发过程与工具的原因个人与交互重于开发过程与工具的原因个人与交互重于开发过程与工具的原因个人与交互重于开发过程与工具的原因: 一个由优秀的人员组成但使用普通的工具,要比使用优秀的工具但由普通人组成、紊乱的小 组做得更好。多年来人们花了很多时间试图建立一种过程,以便把人当作机器上的一个可以替代 的齿轮,但结果却并不成功。敏捷过程是承认每个人都有特定的能力(以及缺点),并对之加以 利用,而不是把所有的人当成一样来看待。 更重要的是,在这样的理念下,几个项目做下来,每个人的能力都从中得以提高。这种人的 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 11 - 能力的提高,对公司是无价之宝。而不至于把人当成齿轮,随着时间的推移,人的能力慢慢被消 耗掉,最后变成留之无用、弃之可惜的尴尬人物。 可用的软件重于复杂的文档的原因可用的软件重于复杂的文档的原因可用的软件重于复杂的文档的原因可用的软件重于复杂的文档的原因: 可用的软件可以帮助开发人员在每次迭代结束的时候,获得一个稳定的、逐渐增强的版本。 从而允许项目尽早开始,并且更为频繁的收集对产品和开发过程的反馈。随着每次迭代完成软件 的增长,以保证开发小组始终是处理最有价值的功能,而且这些功能可以满足用户的期待。 寻求客户的合作重于对合同的谈判的原因寻求客户的合作重于对合同的谈判的原因寻求客户的合作重于对合同的谈判的原因寻求客户的合作重于对合同的谈判的原因: 敏捷开发小组希望与项目有关的所有团体都在朝共同方向努力,合同谈判有时会在一开始就 使小组和客户出于争执中。敏捷开发追求的是要么大家一起赢,要么大家一起输。换句话说,就 是希望开发小组和客户在面对项目的时候,以一种合作的态度共同向目标前进。当然,合同是必 需的,但是如何起草条款,往往影响到不同的团体是进行合作式的还是对抗式的努力。 对变化的响应重于始终遵循固定的计划的原因对变化的响应重于始终遵循固定的计划的原因对变化的响应重于始终遵循固定的计划的原因对变化的响应重于始终遵循固定的计划的原因: 敏捷开发认为对变化进行响应的价值重于始终遵循固定的计划。他们最终的焦点是向用户交 付尽可能多的价值。除了最简单的项目以外,用户不可能知道他们所需要的所有功能的每个细节。 不可避免地在过程中会产生新的想法,也许今天看起来是必需的功能,明天就会觉得不那么重要 了。随着小组获得更多的知识和经验,他们的进展速度会比开始的时候期望值慢或者快。对敏捷 开发来说,一个计划是从某个角度对未来的看法,而具有多个不同的角度看问题是有可能的。 二二二二、、、、项目的敏捷开发方法项目的敏捷开发方法项目的敏捷开发方法项目的敏捷开发方法 敏捷方法很多,包括 Scrum 、极限编程、功能驱动开发以及统一过程等多种法,这些方法本 质实际上是一样的,敏捷开发小组主要的工作方式可以归纳为:  作为一个整体工作;  按短迭代周期工作;  每次迭代交付一些成果;  关注业务优先级;  检查与调整。 1,,,,敏捷小组作为一个整体工作敏捷小组作为一个整体工作敏捷小组作为一个整体工作敏捷小组作为一个整体工作 项目取得成功的关键在于,所有项目参与者都把自己看成朝向一个共同目标前进的团队的一 员。“扔过去不管”的心理不是属于敏捷开发。设计师和架构师不会把程序设计“扔”给编码人 员;编码人员也不会把只经过部分测试的代码“扔”给测试人员,一个成功的敏捷开发小组应该 具有“我们一起参与其中的思想”, “帮助他人完成目标”这个理念是敏捷开发的根本管理文化。 当然,尽管强调一个整体,小组中应该有一定的角色分配,各种敏捷开发方法角色的起名方案可 能不同,但愿则基本上是一样的。 第一个角色是产品所有者,他的主要职责包括:  确认小组成员都在追求一个共同的目标前景;  确定功能的优先等级,以便总是处理最有价值的功能;  作出可以使项目的投入产生良好回报的决定。 产品所有者通常是公司的市场部门或者产品管理部门的人员,在开发内部使用的软件的时 候,产品所有者可能是用户、用户的上级、分析师,也可能是为项目提供资金的人。 第二个角色是客户: 客户是做出决定为项目提供资金或者购买软件的人,在一个开发内部使用的软件项目中,客 户通常是来自另一个团组或者部门的代表,在这样的项目中,产品所有者和客户的角色往往是重 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 12 - 合的。对一个商业软件来说,客户就是购买这个软件的人。客户可能是也可能不是软件的用户 (user ),用户当然也是一个主要角色。 一个值得注意的角色是开发人员(developer ):所有开发软件的人,包括程序员、测试人员、 数据库工程师、可用性专家、技术文档编写者、架构师、设计师、分析师,等等。 最后一个角色是项目经理: 在所有的敏捷开发项目中,项目经理的角色发生了变化,他更注重的是教导、引导而不是管 理和控制。在 Scrum 中,为了强调责任的变化,改称之为 ScrumMaster 。在某些敏捷开发项目中, 项目经理也承担其它的角色,通常是开发人员,少数的时候也会担任产品所有者。 2,,,,敏捷小组按短迭敏捷小组按短迭敏捷小组按短迭敏捷小组按短迭代周期工作代周期工作代周期工作代周期工作 在敏捷项目中,总体上并没有什么上游阶段、下游阶段,你可以根据需要定义开发过程。在 初始阶段可以有一个简短的分析、建模、设计,但只要项目真正开始,每次迭代都会做同样的工 作(分析、设计、编码、测试。等等)。 迭代是受时间框限制的,也就是说即使放弃一些功能,也必须结束迭代。时间框一般很短, 大部分是 2~4 周,在 Scrum 中采用的是 30 个日历天,也就是 4 周。 迭代的时间长度一般是固定的,但也有报告说,有的小组在迭代开始的时候选择合适的时间 长度。 3,,,,敏捷小组每次迭代交付一些成果敏捷小组每次迭代交付一些成果敏捷小组每次迭代交付一些成果敏捷小组每次迭代交付一些成果 比选择特定迭代长度更重要的,是开发小组在一次迭代中要把一个以上的不太精确的需求声 明,经过分析、设计、编码、测试,变成可交付的软件(称之为功能增量)。当然并不需要把每 次迭代的结果交付给用户,但目标是可以交付,这就意味着每次迭代都会增加一些小功能,但增 加的每个功能都要达到发布质量。每次迭代结束的时候让产品达到可交付状态十分重要,但这并 不意味着要完成发布的全部工作,因为迭代的结果并不是真正发布产品。 假定一个小组需要在发布产品之前对软硬件进行为期两个月的“平均无故障时间”(MTBF) 测试,他们不可能缩短这两个月的时间,但这个小组仍然是按照 4 周迭代,除了 MTBF 测试, 其它都达到了发布状态。 4,,,,敏捷小组关注业务优先级敏捷小组关注业务优先级敏捷小组关注业务优先级敏捷小组关注业务优先级 敏捷开发小组从两个方面显示出他们对业务优先级的关注。 首先,他们按照产品所有者制定的顺序交付功能,而产品所有者一般会按照组织在项目上的 投资回报最大化的方式来确定优先级,并且把它组织到产品发布中去。要达到这个目的,需要综 合考虑开发小组的能力,以及所需功能的优先级来建立发布计划。在编写功能的时候,需要使工 能的依赖性最小化。如果开发一个功能必须依赖其它 3 个功能,那产品所有者这就很难确定功能 优先级。功能完全没有依赖是不太可能的,但把功能依赖性控制在最低程度还是相当可行的。 5,,,,敏捷小组检查与调整敏捷小组检查与调整敏捷小组检查与调整敏捷小组检查与调整 任何项目开始的时候所建立的计划,仅仅是一个当前的猜测。有很多事情可以让这样的计划 失效:项目成员的增减,某种技术比预期的更好或更差,用户改变了想法,竞争者迫使我们做出 不同的反应,等等。对此,敏捷小组不是害怕这种变化,而是把这种变化看成使最终软件更好地 反映实际需要的一个机会。 每次新迭代开始,敏捷小组都会结合上一次迭代中获得新知识做出相应调整。如果认为一些 因素可能会影响计划的准确性,也可能更改计划。比如发现某项工作比预计的更耗费时间,可能 就会调整进展速度。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 13 - 也许,用户看到交付的产品后改变了想法,这就产生反馈,比如他发现他更希望有另一项功 能,或者某个功能并不像先前看得那么重。通过先期发布增加更多的用户希望的功能,或者减少 某些低价值功能,就可以增加产品的价值。 迭代开发是在变与不变中寻求平衡,在迭代开始的时候寻求变,而在迭代开发期间不能改变, 以期集中精力完成已经确定的工作。由于一次迭代的时间并不长,所以就使稳定性和易变性得到 很好的平衡。在两次迭代期间改变优先级甚至功能本身,对于项目投资最大化是有益处的。 从这个观点来看,迭代周期的长度选择就比较重要了,因为两次迭代之间是提供变更的机会, 周期太长,变更机会就可能失去;周期太短,会发生频繁变更,而且分析、设计、编码、测试这 些工作都不容易做到位。综合考虑,对于一个复杂项目,迭代周期选择 4 周还是有道理的。这恰 恰是 Scrum 选择 30 个日历日作为固定迭代周期的原因之一。 在本节结束的时候,让我们看一下 MIT Sloan Management Review (麻省理工学院项目管理 评论)所刊载的一篇为时两年对成功软件项目的研究报告,报告指出了软件项目获得成功的共同 因素,排在首位的是迭代开发,而不是瀑布过程。其它的因素是:  至少每天把新代码合并到整个系统,并且通过测试,对设计变更做出快速反应。  开发团队具备运作多个产品的工作经验。  很早就致力于构建和提供内聚的架构。 从这个报告中所透露出的信息告诉我们,认真研究敏捷过程对软件项目的成功是非常有意义 的,它的意义在于: 1))))给开发小组的自组织提供了机会给开发小组的自组织提供了机会给开发小组的自组织提供了机会给开发小组的自组织提供了机会 经典项目管理就好比一个具备中央调度服务的航空管理系统,这个系统是自治的,而且是封 闭的,但现实中更庞大的系统,似乎并不属于中央调度控制系统,但也同样也是有效的。假如我 们开车到某个地方,我们可以任意选择所需要的路线,我们甚至不需要准确计算停车地点,只要 我们遵守交通法规,驾驶员可以临时根据路况改变某个转弯点,在驾驶游戏规则的框架内,依照 自身最大利益做出决策。 成千上万的驾驶者,并不需要中央控制和调度服务,人们仅仅在简单的交通法规的框架内, 就可以完成综合起来看是更庞大的目标,很多事情从管理的角度只要抓住关键点,并不需要多么 复杂的规则,往往会更有效。 随着系统复杂度的提高,中央控制和调度系统面临崩溃。仔细研究交通系统的特点,我们会 发现这样的系统中独立的个体在一组适当的规则下运行,并不需要设计每个个体临时变更的方 案,而每个个体只需要知道目标和大致的状况,他们完全可以利用自己的聪明才智来决定自己的 行为。 2))))缩短了反馈通道缩短了反馈通道缩短了反馈通道缩短了反馈通道 敏捷过程有效运作的另一个原因是,它极大的缩短了用户与开发者、预测目标与实施状况、 投资与回报之间的反馈回路。在面对不断变化的市场、业务过程以及不断发展的技术状态的时候, 便需要有一种方法在比较短的时间内发展完善。 事实上,所有的过程改进都不同程度的使用着戴明循环,以研究问题、测试解决方案、评估 结果,进而根据已知的事实来进行改进,这就称之为基于事实的决策模式,我们都知道,这比前 端预测的决策方式更加有效。 3))))易于集思广益易于集思广益易于集思广益易于集思广益 敏捷过程能有效应用的另一个原因在于,它可以就一个问题集思广益。我们的经验告诉我们, 当一个问题发生的时候,总有某些人员知道问题所在,但他的观点却遭到忽视。例如航天飞机在 起飞阶段发生爆炸,事后分析出了各种原因,但这种调查也提供给我们一个惊人的事实,就是部 分工程师早就意识到这些潜在问题,却无法说服他人重视这个担忧。对这些事实的深入思考,促 使我们研究我们应该采取何种管理系统,使前线工作人员的经验、观点及担忧得到充分的重视 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 14 - 呢? 1.4 Scrum 敏捷管理原理敏捷管理原理敏捷管理原理敏捷管理原理 软件开发是一项复杂行为,软件开发过程完全是智力活动,它的所有中间产品是全部思维活 动的一部分,它的一个显著的特点是:构造成品的过程中所使用的材料极不稳定,客户的需求是 来自于还没有体验过的程序设想,没有经过验证的其它程序与本程序的互操作性,以及地球上最 复杂的人与人之间的互动。 诸此种种,造成了软件项目管理变得非常复杂和困难,长期以来,人们致力于研究提高软件 成功率的管理方法,Scrum 项目管理是这些探索中的一种,它是专门为解决复杂问题、获取可用 产品而设计的,在过去 10 年中,Scrum 已经成功运用于数千个企业,均取得了良好的成果。软 件开发工作的性质决定了复杂问题的大量涌现,解决这些问题离不开努力工作、智慧与勇气。 Scrum 的特点是并不排除经典软件工程学的一系列规则,但是针对其中的问题加以改进,这 就比较适合大型复杂项目,也比较容易被大型软件企业接受。我们如果吃透一种方法,深入理解 其中的本质和困难,就能灵活应用对自己来说比较合适的过程。 一一一一、、、、Scrum 的骨架与核心的骨架与核心的骨架与核心的骨架与核心 Scrum 是基于 30 天的学习循环,这些循环代表着完整的业务想法,通过挖掘已经存在的事 实,深入研究、快速、全面的学习和掌握情况。Scrum 坚持交付完整的商业价值增量,这个概念 十分重要。我们都知道软件只有在测试、集成乃至发布使用之后,才可能确定是否能产生预期的 商业价值,Scrum 要求早期测试、集成试验结果,然后发布初期产品,每 30 天便成为这样一个 学习循环。 Scrum 并不是只关注商业价值增量,它更注重交付给客户最重要、最优先的商业价值,产品 负责人与团队商讨确定这些价值,然后由团队确定 30 天的工作内容,以交付最优先的商业价值。 这样,短期反馈回路便转化为商业反馈回路,Scrum 具有早期及频繁的检测步骤,有助于确定当 前被开发的系统能否交付商业价值、以及它的确切模样。这样一来,系统可以随着开发过程做出 调整、成型,依照当前的要求来交付价值,在过程中也可以发现更优的价值。 Scrum 把小型团队转化为自身命运的管理者,一般来说,当我们驾驶汽车的时候,总是尽力 寻找最佳路线,避免高峰时交通堵赛,在行程中快速做出决策,但并不脱离城市道路交通这样一 个大的框架。同样,Scrum 团队接受挑战,寻找在整个项目大框架下应对挑战的方法,发挥创意、 避开工作障碍,而这一切都是由中央控制与调度系统无法预先安排的。 如果团队规模合适,能激发各个成员的参与、提高他们的积极性,如果他们意识到他们对自 身命运的掌控,那么各个成员的经验、意见和想法就可以得到充分的应用,而不是遭受压制。如 果团队成员信仰共同的目标,便会设法实现它。 Scrum 被认为是目前全球最流行最有效的敏捷项目管理理念与方法之一,在软件业发达地区 被众多知名企业所采纳,Scrum 的哲学基础是授权于项目的开发团队、以及满足客户需求。其管 理文化植根于“帮助他人完成目标”这个理念。其主要的技术工具是通过学习过程作出基于事实 的决策,一旦上述因素各就各位,Scrum 想不成功都难。 Scrum 的所有实践围绕着一个迭代、增量的过程骨架展开,下图说明了这种骨架。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 15 - 下方循环代表开发活动的迭代,这种循环相继发生。每次迭代的成果变成为产品的增量。上 方循环代表着每日检查,团队成员举行会议相互检查工作,进行适当的调整。需求列表是推动迭 代的主要力量。只要项目有资金,这一循环就会不断重复。下面对这个图作出解释: 该骨架运行方式如下该骨架运行方式如下该骨架运行方式如下该骨架运行方式如下:每一迭代初期,团队评审必办事项,挑选出他们认为在该迭代结束的 时候,能转化成相应完整功能增量的部分。迭代其余时间,团队不受干涉、努力工作。迭代结束 的时候,团队展示完成的功能增量,请利益相关者进行检查,以对项目进行及时调整。 Scrum 的核心在于迭代的核心在于迭代的核心在于迭代的核心在于迭代:团队首先浏览开发需求,考虑可用技术,并且对自身拥有的技术 做出评估。然后共同确定构建功能的方案,并且每日调整方法,以应对新的复杂问题、困难和出 乎意料的情况。团队找出并且选择最佳方案去完成任务。这个创造性过程,就是 Scrum 生产力 的核心。 Scrum 利用三种角色实施迭代和增量骨架利用三种角色实施迭代和增量骨架利用三种角色实施迭代和增量骨架利用三种角色实施迭代和增量骨架:下面我们将概述 Scrum 过程中人员的角色,然 后阐述 Scrum 的流程及人工因素。 二二二二、、、、Scrum 的角色分配的角色分配的角色分配的角色分配 Scrum 方法中只有三种角色:产品负责人(Product Owner )、团队和 ScrumMaster 。 1))))产品负责人产品负责人产品负责人产品负责人((((Product Owner )))):产品负责人代表项目中每位利益相关者的权益,并且为 项目产出的软件系统负责。产品负责人规划项目初始总体要求、投资回报(ROI)目标和发布计 划,从而为项目赢得启动以及后续资金。 软件开发需求清单又称作“Product Backlog ”(产品待办事项表),产品负责人的职责是利用 产品 Backlog ,督促团队优先开发最具价值的功能,并且在这个基础上继续开发。要想达到这个 目标,产品负责人必须频繁检视产品待开发需求的优先秩序,把最具价值的开发需求安排在下一 个迭代中完成。 2))))团队团队团队团队:团队的责任是开发软件功能。他们是自我管理、自我组织和跨职能的。他们负责 找出可以在一个迭代中把产品待开发事项转化为功能增量的方法,并且管理自身工作。团队成员 对每一次迭代和整个项目共同负责。 3))))ScrumMaster :ScrumMaster 需要对 Scrum 过程负责,向所有项目参与者讲授 Scrum 方 法,负责实施 Scrum ,确保它既符合企业文化,又能交付预期利益,还需要督促全体成员遵从 Scrum 规则和实践。 担任上述角色的人对项目承担责任,我们称之为“管理者”角色。其它人或许对项目感兴趣, 但不承担责任,我们称之为“旁观者”角色。 Scrum 严格区分这两类人:对承担责任的人赋予权力,使其完成必要的工作,确保项目成功; 无责任的人则无权对项目施加不必要的干涉。 在 Scrum 方法中,上述区分非常重要,它关系到 Scrum 的全面可见性原则。必须时刻区分 责任人和出主意的人。哪些人对投资回报负责?哪些人在投资回报中分成,但并不承担责任?谁 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 16 - 负责使用高难度技术创造出软件功能?Scrum 规则的这种区分,可以提高生产力,创造势头,防 止混乱局面。 三三三三、、、、Scrum 的过程的过程的过程的过程 下图是基本的 Scrum 过程总图,下面对这个过程作一个简要解释。 1))))最初的愿景最初的愿景最初的愿景最初的愿景: 一个 Scrum 项目的起点是待开发系统的愿景。最初,愿景比较模糊。可能更多使用市场化 的语言(多于系统语言)来描述。但是随着项目的进展,愿景逐渐清晰。产品负责人需要对项目 投资者负责,以投资回报最大化实现愿景。 2))))产品计划产品计划产品计划产品计划: 产品负责人依据上述目标制定出计划,该计划包括产品 Backlog 。这个表是一张关于功能性 需求和非功能性需求的清单,它一旦转换成功能,就可以实现愿景。 产品 Backlog 排列出不同优先等级并把事项分成多个建议发布组,最容易产出价值的事项拥 有最高优先级。 分出优先等级以后的产品 Backlog 是项目的起点,而项目一旦开始,该表的内容、优先等级 和发布组就会不断变化,对这一情况应该有所预料。产品 Backlog 的变化反映出不断变化的商业 需求,以及团队把待开发事项转化为功能的速度。 3))))Sprint 迭代周期迭代周期迭代周期迭代周期: 所有的工作都在 Sprint 迭代周期内完成,每个 Sprint 迭代周期为连续的 30 个日历日。 4))))Sprint 计划会议计划会议计划会议计划会议: 在周期开始的时候,均需要召开 Sprint 计划会议,产品负责人与开发团队共同探讨该 Sprint 的工作内容。产品负责人从最优先的待开发事项中进行筛选,告知团队他的预期目标,团队则提 出来在接下来的 Sprint 内,预期目标可以实现的程度。 Sprint 计划会议的长度不超过 8 小时,它有限定时间是为了避免纠缠不确定的预期。会议的 目的是为了展开实际工作,而不是只想不做! Sprint 计划会议包括两个部分: 第一部分 4 个小时,产品负责人向团队展示最高优先等级的的产品 Backlog 。团队则向他询 问产品 Backlog 的内容、目的、含义及意图。当团队了解了足够的信息以后,前 4 个小时仍然有 剩余,团队可以确定在本 Sprint 内,哪些部分可以转化为完整的产品功能增量。团队向产品负责 人承诺将全力工作。 后半部分 4 个小时,团队计划本 Spring 的安排,团队负责管理自身的工作,因而需要一个 初步的计划,以开展本 Spring 的工作。Sprint Backlog (Sprint 待办事项表)将包含这个计划的任 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 17 - 务。这个表内的任务随着 Sprint 周期的进展而不断涌现,一旦 Sprint 计划会议进入后 4 个小时, 这个 Sprint 周期就正式开始,周期固定的 30 天时间从此刻开始倒计时。 5))))每日每日每日每日 Scrum 简会简会简会简会((((Daily Scrum )))): 每天,团队集合召开 15 分钟的会议,称为“每日 Scrum 简会”,再建徽商每个成员回答三 个问题:自上次 Scrum 简会之后的 1 天里你做了什么?从现在到下次 Scrum 简会的一天里你准 备做什么?在实现 Scrum 及其项目目标的工作中,你遇到了哪些困难?会议的目的是保持团队 全体成员每日工作步调一致,并且安排有必要的其它会议,促进团队工作。 6))))Sprint 评审会议评审会议评审会议评审会议: Sprint 周期结束的时候,需要召开 Sprint 评审会议。该会议限定时间为 4 个小时,由团队向 产品负责人和其他与会利益相关者展示该 Sprint 周期内的产品开发情况。此展示功能的非正式会 议,旨在召集相关人员,共同决定团队接下来的工作。 7))))Scrum 评审会议评审会议评审会议评审会议: 在 Sprint 评审会议和下一次 Sprint 计划会议之间,ScrumMaster 和开发团队需要召开一次限 时 3 个小时的 Scrum 评审会议。ScrumMaster 将鼓励团队在 Scrum 过程框架和实践范围内,对开 发过程作出修改,以使它在下一个 Sprint 周期中更加有效和更加愉快。 Sprint 计划会议、每日 Sprint 简会、Sprint 评审会议以及 Scrum 评审会议共同构成 Scrum 方 法中的经验性检查及适应调整部分。 四四四四、、、、Scrum 的工件的工件的工件的工件 Scrum 引入了一些新的工件运用于整个 Scrum 过程,下面我们分别进行阐述。 1,,,,产品产品产品产品 Backlog 产品 Backlog 列出某一项目开发的系统或者产品需求。由产品负责人负责确定产品 Backlog 的内容、优先级排序以及可用性。产品 Backlog 表应该永远处于不完整状态,在项目计划中所使 用的产品 Backlog 不过是开发需求的初始估计。该表随着产品及其使用环境的变化而变化。它是 动态的,管理层不断对之作出改变,确定产品需求,保证产品的适用性、实用性和竞争性。只要 产品存在,产品 Backlog 也随之存在。 下表是一个假定某“航空机票管理系统”的产品 Backlog ,它属于 Scrum 产品管理工具的一 部分,为电子数据表格的形式(可以用微软 Excel 来完成)。 2,,,,Sprint Backlog Sprint Backlog 用于界定工作或者任务,这些工作会把当前 Sprint 周期选定的产品待办事项 转化为完整的产品功能增量。 团队在 Sprint 计划会议的第二阶段编写任务初步列表,相关任务需要进一步细分以使每项耗 时约 4 ~16 小时。超过这个时限的任务,只能视作尚未恰当界定的任务。仅仅规定团队有权改 变 Sprint Backlog 。这个任务以表格方式描述,这个表以可视性高、实时跟踪的方式描绘了团队 当前 Sprint 所计划要完成的工作情况。 Scrum 要求团队在每个 Sprint 周期内都能完成一部分产品功能增量。这一增量必须是可交付 的,因为产品负责人可能会选择即刻使用这个功能。所以,增量需要由测试充分、构造良好、精 心编写的代码组成,而且需要构建为可执行的程序,同时还应该把用户对该功能的操作以帮助文 件的或者以用户文档的形式记录,这才是“已完成增量”的完整定义。 如果这个 Sprint 周期产品功能增量有比较明确的使用,开发组通常会把这个新增的产品需求 界定为标准或者惯例,在下一个周期中,这能会使用到这个功能。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 18 - 五五五五、、、、使用使用使用使用 Scrum 进行大型项目多维度扩展进行大型项目多维度扩展进行大型项目多维度扩展进行大型项目多维度扩展 许多项目无法由一个 Scrum 团队完成,这种情况需要多团队合作。人们通过一系列的机制 来协调多团队并行开发。两个或者两个以上的 Scrum 团队同时开发的项目称作“扩展项目”,协 调这些项目的机制称作“扩展机制”,每个扩展项目有其自己的复杂性,需要独特的解决方案。 扩展的核心是 Scrum 团队,一个 800 人的项目需要包含 100 个 8 人团队,我们将如何协调全部 团队的工作呢? Scrum 扩展成功的关键:首先,在扩展前构建必需的基础设施和基础架构,一般设计和构建 基础架构需要经过几个 Sprint 周期。第二,构建基础设施的同时,确保交付商业价值,这种商业 价值也包括将来使用这个基础设施的应用案例。第三,其它团队可以在后期建立。优化原始团队 的能力,向其它团队分派至少一名初始团队成员。同时还要注意,项目一开始就取得进展,对取 得利益相关者的支持很重要,但应防止扩展速度过快。上述 Scrum 扩展的方法,可以用下图表 示。 但是不要认为在多维度扩展项目整体控制上还是要采用 Scrum 自组织和自管理的规则。在 总的控制上,采用类似预定义过程的分层管理结构在很多情况下都是合适的,这样可以降低管理 上的复杂性,而且,总体方案上的变更本来就比较小,这样更容易协调和扩展项目。不管怎么说, 一个大型复杂项目的框架被说成是易变的、无法预测的恐怕并不一定符合实际情况,所以书生气 十足的建立全面敏捷管理很可能最终把事情搞坏。 大型项目的高级管理层也不可能达到与团队进行适应的频率和准确度,干预程度也往往不容 易掌握,经过一些对比的尝试,感觉多维度扩展的 Scrum 高层管理还是经典项目管理理念比较 有效,这是稳定性和灵活性的统一。在这样的情况下,一种产品 Backlog 的分层结构如下图所示。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 19 - 1.5 选择合适的软件工程策略选择合适的软件工程策略选择合适的软件工程策略选择合适的软件工程策略 一一一一、、、、选择合适的软件工程策略选择合适的软件工程策略选择合适的软件工程策略选择合适的软件工程策略 在项目很庞大的时候,需要选择合适的软件工程策略,有三种工程项目策略如下表所示: 三种工程项目策略的关键特性三种工程项目策略的关键特性三种工程项目策略的关键特性三种工程项目策略的关键特性 工程策略工程策略工程策略工程策略 首先定义所有需求首先定义所有需求首先定义所有需求首先定义所有需求???? 存在多次开发循环存在多次开发循环存在多次开发循环存在多次开发循环???? 现场安装中间软件现场安装中间软件现场安装中间软件现场安装中间软件???? 一次性完成策略 是 否 否 增量式 是 是 可能 进化式 否 是 是 1,,,,一次完成策略一次完成策略一次完成策略一次完成策略: 这是一种典型的“一次设计、一次通过”的策略,不论是原则上是采取线性过程还是迭代过 程,在初期的需求完成以后,最后要交付完整的产品,如下图所示。 图中也标出了在各个阶段建议的文档,文档的略语表如下。 软件工程策略略语表软件工程策略略语表软件工程策略略语表软件工程策略略语表 CSCI 计算机软件配置项 DBDD 数据库设计说明 SRS 软件需求规格说明 STD 软件测试说明 IRS 接口需求规格说明 STR 软件测试报告 ARS 架构需求规格说明 HWCI 硬件配置项 ADD 架构设计说明 SVD 软件版本说明 ATD 架构测试说明 SPS 软件产品规格说明 SDD 软件设计说明 STrP 软件移交计划 IDD 接口设计说明 在项目的早期,我们的精力集中于系统级的需求收集,在这个层面,我们关注于整个系统的 功能和非功能需求,这时候的功能需求可以用“用户描述”来表达,非功能需求可以生成“补充 规格说明”。对于规模比较庞大的“用户描述”,我们可以对这样的需求进行划分,从而引发一些 派生的需求(例如子系统需求与接口需求等)。这个阶段的需求必须经过评审,以期把整个项目 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 20 - 完整的定义下来,同时可以用这样的需求信息对项目进行早期的估计。 项目的第二个阶段,我们把精力集中于架构级别的需求收集,进一步精化和优化系统划分。 这时候需求收集的重点是各个子系统和模块的需求定义,确定它们如何协调和通讯,以及架构级 别的非功能性需求等。架构设计一般经过一到两次迭代,设计的结果需要经过评审,从而确定整 个产品的体系结构已经完成。 在软件架构完成以后,需要根据已有的需求信息进行整个项目的规划和估计,针对每个子系 统分配资源,组织适当数量的开发团队,使每个开发团队进入各自的迭代周期。 在每个迭代周期的开始,需要根据需求的优先级选择适当数量的的增量功能,在迭代开始以 后,首先是对这些增量功能(以用户描述表达)进行详细的需求收集与分析,这个级别的需求是 由迭代小组中的需求人员带领小组全体成员共同来完成,然后编写详细级别的需求文档并经过确 认。以此为基础顺序的进入设计、编码、测试等各个阶段。每次迭代交付的是可以提交的功能增 量。 每个迭代期间,对需求变更是封闭的,但是在每次迭代开始“选择增量功能”阶段,需要对 需求的变化进行收集和考虑,这个时候对需求变更是开放的。要注意的是,每次变更都必须修改 相应的需求文档,并做下记录。在这样复杂的过程中,良好的软件配置管理是非常必要的。 当所有的子系统都完成以后,还是需要进行最后的系统集成和测试,这可能要经过一到两次 迭代,最后是验收测试和产品发布。 2,,,,增量式策略增量式策略增量式策略增量式策略: 这种策略的特点是确定用户需要和定义系统需求以后,按照构建版顺序完成其余的开发任 务。第一个构建版纳入部分计划和能力,下一个构建版再增加一些能力等,直到系统全部完成。 其中,每一个阶段与一次完成的策略是一样的。 这种策略的本质是一种螺旋方式,开始于一个适度的、良好定义的问题,解决它,再在下一 阶段扩展它。在开发的每一阶段,都有一个良好定义的而不是粗略的需求。每一阶段都为良好定 义的问题产生一个可以证明是正确地解决方案。生成软件即使有漏洞也很少,从每个解决方案我 们都可以知道在下一阶段如何来扩展这个问题,也就是改善需求。 这种策略最成功的应用,当数上个世纪 60 年代美国的阿波罗计划。如此庞大的计划不可能 一开始就尝试所有的细节。于是,十年来 NASA 进行了多次发射,每次发射都力图解决登月计 划问题集的每一个,例如太空生活供给问题,太空外活动问题,太空对接问题等。整个计划都是 由需求来驱动,每次经验都对下次的需求设计有所改进。 向解决方向上增加解决方案的好处,使它可以解决我们从没有想出来的问题,当它成功的时 候,能够发现我们从未像想到的问题,从而引发新的需求,而这些需求又是与用户的需要是吻合 的。拥有良好需求定义的螺旋式方法,支持在问题域的每一个阶段设计软件的期望结果,也就有 更大的机会在在早期发现错误,由于每一阶段都有一些良好定义的东西去测试,甚至实现某种模 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 21 - 式,在后期设计中将会变得更加胸有成竹。 3,,,,进化式策略进化式策略进化式策略进化式策略: 进化式策略的一种是原型方式,它与增量式方法最大的区别是不能有一个良好定义的需求, 即使是一个阶段也不可能。这是一种特殊的“增量式”策略,所不同的是承认用户的需求不完全 清楚,不可能预先定义全部需求。采用此策略的时候,用户需要和需求预先部分的定义,然后在 随后的构建版中逐步得精炼。 进化式策略目的是为需求和最终的软件规格说明书激发灵感。所以,需求本身并不是随意的, 也不能与需求的严格定义相冲突。最终,我们还是需要通过观察每个构建版的行为来改进需求, 也必须让程序严格的映射到需求。 原型方式实际上已经构建了一个模式,而这个模式是站在设计方的角度构建的,我们希望通 过原型达到的,正是突破自己已经构建的模式,使产品真正符合用户的需要。一个优秀的作家往 往把自己的草稿给一部分读者阅读,读者看到了这些草稿,就有了一个基础,激发了想象力,当 这些读者提出各种各样的意见的时候,作家就可以换个思维方式面对自己的草稿,突破自己已经 形成的思维模式,这样他就可以写出真正优秀的作品来。 进化式原型将是最终产品的一部分,所以从架构设计来说必须设计为易于升级和优化的,因 此,我们应该重视软件系统性和完整性的设计原则,要达到进化型原型的质量要求并没有捷径。 我们应该考虑进化型原型的第一次演变,因为它将作为实现需求中易于理解和稳定部分的试验性 版本。从测试和首次使用中获得的信息将引起下一次软件原型的更新,正是这样不断增长并更新, 使软件才能从一系列进化型原型逐渐发展为实现最终完整的产品。 二二二二、、、、利用风险分析选择利用风险分析选择利用风险分析选择利用风险分析选择合适的工程项目策略合适的工程项目策略合适的工程项目策略合适的工程项目策略 工程项目策略由需方选择,但也可以由未来的或者已选定的开发方提议。下表说明了一种合 适的策略所使用的风险分析方法,这种方法就是:列出每种策略的风险项(负面的)和机会项(正 面的),为每个项确定风险的机会和等级(高、中、低):根据风险和机会的权衡做出使用哪种策 略的决定,表中所列的只是一个例子,实际分析可以采用其它的方法。记录在最后一行的“决定” 表明选择了这种策略。 为确定适当的工程项目策略而进行的风险分析样例为确定适当的工程项目策略而进行的风险分析样例为确定适当的工程项目策略而进行的风险分析样例为确定适当的工程项目策略而进行的风险分析样例 一次性完成设计一次性完成设计一次性完成设计一次性完成设计 增量式策略增量式策略增量式策略增量式策略 进化式策略进化式策略进化式策略进化式策略 风险项风险项风险项风险项((((反对这个策略的理反对这个策略的理反对这个策略的理反对这个策略的理 由由由由)))) 风险风险风险风险 级别级别级别级别 风险项风险项风险项风险项((((反对这个策略的理反对这个策略的理反对这个策略的理反对这个策略的理 由由由由)))) 风险风险风险风险 级别级别级别级别 风险项风险项风险项风险项((((反对这个策略反对这个策略反对这个策略反对这个策略 的理由的理由的理由的理由)))) 风险风险风险风险 级别级别级别级别 1,没有很好地理解需求。 2,系统太大以至于不能一次 性的完成。 3,预期任务和技术的快速变 化可能会导致需求的改变。 4,现在可用的人力资源或者 预算有限。 高 中 高 中 1,没有很好地理解需求。 2,在第一次交付的时候用 户就要求所有的能力。 3,预期任务和技术的快速 变化可能会导致需求的改 变。 高 中 高 1,在第一次交付的时 候用户就要求所有的 能力。 中 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 22 - 机会项机会项机会项机会项((((支持这个策略的理支持这个策略的理支持这个策略的理支持这个策略的理 由由由由)))) 机会机会机会机会 级别级别级别级别 机会项机会项机会项机会项((((支持这个策略的理支持这个策略的理支持这个策略的理支持这个策略的理 由由由由)))) 机会机会机会机会 级别级别级别级别 机会项机会项机会项机会项((((支持这个策略支持这个策略支持这个策略支持这个策略 的理由的理由的理由的理由)))) 机会机会机会机会 级别级别级别级别 1,在第一次交付的时候用户 就要求所有的能力。 2,用户要求立即取消旧的系 统。 中 高 1,需要早期能力。 2,系统自然的分解为增量。 3,财力/人力逐步的增加。 高 中 高 1,需要早期能力。 2,系统自然的分解为 增量。 3,财力/人力逐步的增 加。 4,为理解全部需求, 需要对用户的反馈和 对技术变更进行监视。 高 中 高 高 决定决定决定决定::::使用这个策略使用这个策略使用这个策略使用这个策略 工程项目的策略一般适用于整个系统,对系统中的软件可以采用相同的策略去处理,也可以 采用不同的策略来得到。 我们可以看出来,无论是经典模型还是敏捷模型,或者是采用何种软件工程策略,作为设计 的能力基础实际上是一样的,所区别的是在什么时候使用什么样的粒度来进行分析和设计,或者 设计点在什么地方。敏捷模型希望所有团队成员都参与小粒度分析和设计,而不是把分析和设计 仅仅看成几个分析师或者设计师的事情,这种方法和理念的不同,更需要所有团队成员具有分析 和设计的知识基础。 优秀的设计来自于对问题重点的把握,来自于灵活应用基本方法到自己开发团队的过程中 去,来自于对问题的深入理解。所以,在建立了整体框架性思想的指导之下,我们有必要讨论每 个细节,对每个细节的知识、思想和能力基础做比较透彻的讨论。 小结小结小结小结: 有些人认为,中小型项目比较适合敏捷过程,而大型项目还是应该使用瀑布式过程比较正确, 这是一个误解。在项目比较小的时候,或者比较简单的时候,不确定因素的影响其实是比较小的, 这时候使用线性瀑布式过程还是比较合适的。但是当项目很大、很复杂的时候,不确定性因素的 影响往往大到使项目无法进行下去的地步,这种情况才迫使人们研究敏捷过程。 决定软件产品质量最重要的因素是软件架构,在架构设计中对我们最严峻的挑战是,我们如 何合理的组织技术方案,把人和任务作为一个重要的因素进行考虑,使整体上高的投资回报率成 为可能,所以我们的思维集中在两个问题上,第一问题,我们设计的架构如何确保以低的开发成 本达到高的质量要求。第二个问题,如何避免需求变更或者后期升级,造成产品开发成本的大幅 度上升。 多维度敏捷过程的本质是:整体上的宏观规划采用线性过程,部分上的微观规划采用敏捷过 程,但每一次迭代内部还是采用线性过程。这种动态和静态的结合,让所有的方法都用在合适的 地方,加上架构驱动,这就足以使大型复杂项目得以很好地进行,管理上和规划上的难度也得到 了很好的平衡。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 23 - 第二章 从系统工程的角度构建架构策略 需求是设计的源泉,设计方案是对需求需求是设计的源泉,设计方案是对需求需求是设计的源泉,设计方案是对需求需求是设计的源泉,设计方案是对需求的应对。的应对。的应对。的应对。架构策略需要从系统工程的架构策略需要从系统工程的架构策略需要从系统工程的架构策略需要从系统工程的 角度来思考角度来思考角度来思考角度来思考。。。。 在设计产品时,产品的需求决定了合适的架构方法。反过来研究和评审所提出的架构是另一 种解释需求的方法,并且会使需求更加明确。两种方法都围绕着这样一种思维过程:“如果我正 确理解需求,那么这种方法可以满足这种需求。既然我手中有一个最初的架构(或原型),它是 否有助于我更好地理解需求呢?” 经典瀑布式过程要求在需求开发的时候把所有需求都定义清楚,但在即便如此开发过程中仍 然受到需求变更之苦。人们思维方式常常出现的缺失是:当一件事出现困难的时候,总是认为是 自己还做得不够好,而不去考虑这件事做法的本身是不是有问题?人们总认为专家制定的规范是 不会错的,而不去看一看专家在制定这些规范的时候做了什么假定?规范本身是不是随着社会的 进步而变化着?当需求发生变更的时候,总认为自己做的还不够规范,于是进一步强化规范,明 确条块分割,结果造成项目更加困难。正是这样的现实,才迫使人们回过头来想,需求发生变更 的原因到底是什么?这才有敏捷过程的提出,而敏捷过程又对需求分析和架构设计提出了崭新的 要求。这就是软件工程学者这几年如此活跃的原因。 多维度敏捷开发前期的需求开发并不需要太关注某个具体细节,更多的是从整体上考虑和分 析问题。在你可以开始实现各个部分需求前,不必为整个产品进行完整、详细的设计。然而,在 你进行编码前,必须设计好每个部分。设计规划将有益于大难度项目(有许多内部组件接口和交 互作用的系统和开发人员无经验的项目)。然而,下面介绍的步骤将有益于所有的项目:  应该为在维护过程中起支撑作用的子系统和软件组件建立一个坚固的架构。  明确需要创建的对象类或功能模块,定义他们的接口、功能范围以及与其它代码单元的  根据强内聚、松耦合和信息隐藏的良好设计原则定义每个代码单元的预期功能。  确保你的设计满足了所有的功能需求并且不包括任何不必要的功能。 当开发者把需求转化为设计和代码时,他们将会遇到不确定和混淆的地方。理想情况下,开 发者可沿着发生的问题回溯至客户并获得解决方案。 如果不能马上解决问题,那么开发者所做出的任何假设,猜想或解释都要编写成文档记录下 来,并由客户代表评审。如果遇到许多诸如此类的问题,那么就说明开发者在实现需求之前,这 些需求还不十分清晰或具体。在这种情况下,最好安排一两个开发人员对剩余的需求进行评审后 才能使开发工作继续进行。 2.1 从需求分析抽取架构从需求分析抽取架构从需求分析抽取架构从需求分析抽取架构因素因素因素因素 在敏捷模型下需求分析的特点,是在早期集中精力,致力于收集架构层面的需求,把重点放 在整体性的功能以及功能之间关系的需求收集上。然后在每一个迭代周期,由各个开发小组自行 对所选中的用户描述进行详细的需求分析和产品分析,并且在每两次迭代周期之间,由产品负责 人根据内外环境的变化,提出变更想法,这就提供了变更的机会。这种方法,使每一阶段关注的 重点能够集中精力完成,就有可能高效率的构造高质量的产品。 这个观点非常重要,在项目一开始过多关注某些细节,把精力不恰当地投入在一些细节上, 很可能造成对整体关系投入精力不够,最后虽然某一部分实现的很好,但从整体上看可能是一个 失败的产品。组织软件开发与其说好像组织机器制造,更不如果好像组织一场战争。战争与软件 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 24 - 开发有一个地方是相通的,那就是过程中充满了不确定因素。在初期构思战役时候,指挥员如果 不注意各个部队之间的协调以及力量投放的合理性,而只是关注某些细节,那他就不是一个好的 指挥员。如果所有的细节都想清楚了才能开始战争,那就很容易丧失机会。具体的细节的问题, 完全可以交给下级部队的指挥员,在不破坏整体构思的情况下,根据现场情况相机处理。这些成 功的方法,与敏捷模型的核心思维是一脉相承的。 一一一一、、、、使用使用使用使用用户描述反映初始需求用户描述反映初始需求用户描述反映初始需求用户描述反映初始需求 经典软件开发过程要求在任何设计和实现工作之前,尽可能的推敲,把需求完全定义清楚, 并把它稳定下来。这一方面不太可能,另一方面这种重量级的需求分析往往缺乏足够的抽象,庞 大的需求文档使选择搞优先级的需求变的不清晰而且困难。所以在敏捷开发中出师需求推荐使用 一种轻量级的表达方式:用户描述(user story )。应该注意,一旦选中了若干描述进入迭代周期, 就需要把这些用户描述用正规的方式把需求表达出来也就是说每个迭代周期都有需求分析过程, 这个时候是也比较小,也更容易把需求描述清楚。 1,,,,用户描述用户描述用户描述用户描述 由于敏捷开发小组要关注完成和交付具有用户价值的功能,而不是完成孤立的任务(把任务 最终组合成有用户价值的功能)。问题是冗长的用户说明很难快速看清问题,一种比较好的表述 需求的轻量级技术,是把一个用例先用一个简短的说明来表达,我们称之为用户描述,这是从客 户的角度出发对功能的一个简短描述。一般的表达方式是:“作为(用户类型),我们希望可以(能 力)以便(业务价值)”,例如:“作为购书者,我们希望可以根据 ISBN 找到一本书,以便更快 找到正确的书。” 用户描述是轻量级的,并不需要一开始就把它们全部收集和记录下来,或者编写复杂的需求 说明。不管怎么说,收集用户描述应该相对比较容易,每个描述一个卡片,产品所有者和开发人 员都比较容易针对这样的简短描述进行交流。 一旦确定一次迭代需要完成哪些描述,就需要编写正规的需求文档,由于此时的视野比较小, 处理这样的问题一般是没有太大困难的。 2,,,,用户描述和用户描述和用户描述和用户描述和主题主题主题主题 有时,一组相关的用户描述被结合在一起,比如把这些描述卡片用回形针别在一起,当作一 个实体来看,我们常常称之为主题(theme )。还需要注意到的是,用户描述的主题往往构成了架 构的单元。 二二二二、、、、前景文档前景文档前景文档前景文档 前景文档(Vision Docunment )是在较高的抽象层次定义问题和解决方案,它使用一般的术 语描述描述应用,包括对目标市场、系统用户以及应用特性的描述。前景文档是有效需求过程的 关键成分,也可能是最重要的文档。因为一份简短甚至不完全的文档,都有助于项目成员朝向同 一个目标工作。团队由此有了共同的目标和剧本,也可能会有共同的心理。如果团队的目标未知 而且互相冲突,很快就会产生混乱。 前景文档获取用户需要、系统特性以及项目的其它要求。这样,前景文档的范围横跨需求金 字塔的上两层,在较高的抽象级别上定义问题和解决方案。 对于软件产品,前景文档是项目的三个主要内部涉众进行讨论和协商的基础:  营销和项目管理团队,作为客户和用户的代理人,将最终解释项目发行成功的原因。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 25 -  开发应用程序的项目团队。  管理团队,负责所有工作的商业结果。 前景文档是对产品或者应用中所有最重要的内容的简洁描述,他用简单的语言进行恰当的细 节描述,确保项目的主要涉众易于审查和理解,下表概括了前景文档的轮廓。 前景文档模板前景文档模板前景文档模板前景文档模板 1,,,,介绍介绍介绍介绍 提供整个前景文档的概述。 1.1 前景文档的目的前景文档的目的前景文档的目的前景文档的目的 文档的目的是收集、分析、定义高层用户需要和产品特性。 1.2 产品综述产品综述产品综述产品综述 阐述该应用系统的目的、版本以及要交付的新特性。 1.3 参考参考参考参考 这一部分应该列出在前景文档中引用的其它文档的全部清单。 2,,,,用户描述用户描述用户描述用户描述 简要描述系统用户的观点。 2.1 用户用户用户用户/市场统计市场统计市场统计市场统计 总结决定产品动机的主要市场统计。 2.2 用户用户用户用户剖剖剖剖析析析析 简要描述系统预期用户。 2.3 用户环境用户环境用户环境用户环境 描述在使用中包括应用程序和平台等成分的工作环境以及具体的使用模型。 2.4 关键用户需要关键用户需要关键用户需要关键用户需要 列出用户认可的关键问题或者需要。 2.5 替代和竞争对手替代和竞争对手替代和竞争对手替代和竞争对手 确定用户认为可得到的替代品。 3,,,,产品综述产品综述产品综述产品综述 3.1 产品前景产品前景产品前景产品前景 提供系统或产品及其与外部环境接口的框图。 3.2 产品定位陈述产品定位陈述产品定位陈述产品定位陈述 提供一个整体陈述,从最高层面总结产品在市场上的独特定位,推荐以下格式: 为了(目标客户)谁(陈述需要和机遇)“产品名”是一个(产品分类)它(产品名)(对主要优 点的陈述,即激发购买热情的原因)不像(主要竞争替代品)我们的产品(对主要区别的陈述)。 3.3 能力总结能力总结能力总结能力总结 总结产品将提供的主要优点和特性 客户利益客户利益客户利益客户利益 支持特性支持特性支持特性支持特性 利益 1 特性 1 利益 2 特性 2 利益 3 特性 3 3.4 假定和相关条件假定和相关条件假定和相关条件假定和相关条件 3.5 成本和定价成本和定价成本和定价成本和定价 描述持续产品成本和预期产品定价点的任何点。 4,,,,特性属性特性属性特性属性特性属性 描述将用来评估、跟踪、划分优先级以及管理特性的特性属性,以下是一些建议: 状态:建议的、批准的、合并的 优先级:累计投票结果,顺序分级,或者关键的、重要的、有用的。 工作量:低、中、高,团队周数,或人月数 风险:低、中、高 稳定性:低、中、高 目标版本:版本号 分配给:名字 原因:文本字段 5,,,,产品特性产品特性产品特性产品特性 文档这一部分列出产品特性 5.1 特性特性特性特性 5.2 特性特性特性特性 6,,,,典型用例典型用例典型用例典型用例 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 26 - 描述一些典型用例,可以对架构有意义的,或者最方便帮助读者理解系统使用的用例。 7,,,,其它产品需求其它产品需求其它产品需求其它产品需求 7.1 可应用标准可应用标准可应用标准可应用标准 列出产品必须符合的标准。 7.2 系统需求系统需求系统需求系统需求 定义为支持应用所必需的所有系统需求,例如操作系统、网络性能等。 7.3 许可证许可证许可证许可证、、、、安全和安装安全和安装安全和安装安全和安装 描述任何可能影响开发工作量,或可能产生独立安装软件需求的任何许可证、安全或安装的需求。 7.4 性能需求性能需求性能需求性能需求 用这一段细化性能需求 8,,,,建档需求建档需求建档需求建档需求 这一部分描述所有为支持成功部署系统所需要开发的文档。 8.1 用户手册用户手册用户手册用户手册 描述用户手册的目标和内容 8.2 在线帮助在线帮助在线帮助在线帮助 列出在线帮助、工具使用等等的需求。 8.3 安装指南安装指南安装指南安装指南、、、、配置和自述文件配置和自述文件配置和自述文件配置和自述文件 8.4 标记和打包标记和打包标记和打包标记和打包 9,,,,词汇表词汇表词汇表词汇表 在上面的讨论中,我们把系统特性定义在较高的抽象级别上,并且在这个级别上对整个项目 进行规划。这样做得好处是:  可以更多关注系统特性以及它如何体现用户需要,以便更好的理解系统的形状和形式。  可以对系统的完整性、一致性及其对环境的适应性进行评估。  在继续大量投入之前,可以利用这些信息决定可行性并管理系统的范围。  便于在选择迭代内容的时候,能够有一个整体的图像和便捷的用户描述表格。 此外,停留在较高的抽象层次,还有助于我们不至于过早的作出需求决策,我们可以很容易 的加入自己的观点和价值观,也比较容易实现需求变更,在迭代开始的时候发生变更往往是不可 避免的。 2.2 架构架构架构架构层面的需求分析层面的需求分析层面的需求分析层面的需求分析 一一一一、、、、业务用例的分析业务用例的分析业务用例的分析业务用例的分析 不论是预测性过程还是敏捷过程,需求都是从了解业务开始的,并且在建立业务模型的过程 中发现和挖掘需求。建立业务模型首先是建立业务上下文图,也就是业务范围看成一个黑箱,表 示所有相邻系统与业务范围的数据交互关系。例如我们需要了解一个“道路除冰工作系统”,我 们可以把上下文图绘制如下。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 27 - 然后,我们通过研究相邻系统与业务的数据交互,发现业务事件,然后列出一个业务事件清 单,下表列出了这样的事件清单。 道路除冰系统事件清单道路除冰系统事件清单道路除冰系统事件清单道路除冰系统事件清单 编号编号编号编号 事件名称事件名称事件名称事件名称 输入数据流输入数据流输入数据流输入数据流 输出数据流输出数据流输出数据流输出数据流 1 气象站传送数据 气象站读数 2 气象局预报天气 区域气象报告 3 道路工程师通知改变的道路 改变的道路 4 道路工程师安装了新的气象站 新的气象站 5 道路工程师改变了气象站 改变的气象站 6 到了测试气象站的时间 失效的气象站告警 7 卡车车库改变了卡车 卡车改变 修订的除冰调度计划 8 到了检查结冰道路的时间 道路除冰调度计划 9 卡车处理了一条道路 已处理的道路 10 卡车车库报告卡车出问题 卡车故障 修订的除冰调度计划 11 到了监控道路除冰的时间 对没有处理的道路进行提醒 针对每个业务事件,有一个预先计划的对它的响应,被称之为“业务用例”。业务用例总是 包含着一些可识别的过程,一些被存储的数据,产生一些输出,发送一些消息,或者是这些事件 的组合。也就是说业务用例是一个功能单元,这些功能是编写功能需求与非功能需求的基础。 业务用例是方便研究的组合,可以把业务用例的工作相互隔离开来,因为它与其它业务基本 上没有联系,因此不同的分析师可以调研不同的部分,不需要彼此之间一直保持沟通。实际上业 务用例之间唯一的重合是它们之间存储的数据。 每个业务用例的相互隔离意味着可以找到一个或者多个这方面工作的专家,他们在您的帮助 下可以准确而且详尽的描述这部分工作。可以描述正常情况(计划中的状态),也可以描述异常 情况(偏离计划的状态)及其处理办法。它们可以描述组织是如何相应业务事件的。 一个业务用例的处理应该是连续的,也就是说一个业务用例被触发以后,它将处理所有的事 情,直到从逻辑上说无事可做为止(所有的功能都被执行、所有该存储的数据都已经存储、所有 的相邻系统都已经得到通知),下图就是一个处理的例子。 二二二二、、、、创新思考与边界确定创新思考与边界确定创新思考与边界确定创新思考与边界确定 当对工作有了比较深入而且条理化的理解之后,就可以思考“产品应该是怎样的?”这样一 个问题了。遗憾的是许多项目开始的时候都有关于“产品应该是什么”的先入为主的概念,但不 理解产品将成为其工作的一部分。这里我们看看为了发现优化的产品,我们可以做些什么? 产品分析很重要的一项任务,就是确定工作将来应该是怎样的,以及产品是怎样才能对工作 产生最大的帮助。产品是工作的一部分,工作是打算以某种程度改变的东西(通常是把它自动化), 目标是找到优化的业务用例。 思考“产品应该是怎样的?”的直接结果,是最终确定产品的边界。在一开始就定义清楚产 品边界是危险的,因为它会限制住产品的创造性思维,甚至会限制住产品发展。但是,当我们对 业务用例已经进行了清晰的分析和建模,与风险承担者也进行了深入的交流以后,我们需要最终 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 28 - 确定当前产品的边界并把它固定下来,否则产品将无法进入真正的设计,另一方面,最后确定的 边界,也是作为下一步进行产品建模的必要输入。如果范围不够正确,那么结果将使产品不能完 全满足用户的需要。如果不能正确地确定工作范围,总会导致对产品的修改和需求的增强提前到 来,这会带来很大的风险。 重要的是先理解工作,然后把工作的一部分自动化,我们才能把自动化产品无缝的放到工作 中去。让我们来看一个如何确定工作的多少部分将成为产品的例子。考虑下图的例子,一个顾客 (相邻系统)正通过电话订购某种商品。那么,产品边界的最佳位置在哪里?或者换一种思考方 式,怎样才算是最有用的产品? 采用 1 号边界的产品:一个操作员输入已经检查过的订单,通过信用卡公司授权以后,产品 记录订单,这并不是一个好产品,因为大部分工作留给了操作员。 那么 2 号边界又如何?操作员通过电话接下订单,把它录入产品,其余的都可以自动化完成。 这个方法不坏,但还可以做的更好。 3 号边界把订单接收自动化,可以是一个语音识别系统,也可以是通过按键处理的系统,也 可以通过 Web 方式处理,通过网站在线方式订购。如果顾客没有问题或者不需要太复杂的方式 响应,对销售公司来说可能是个好方案,但对于客户呢?他真正想要的是什么?他喜欢与人对话 还是语音发生器对话?主要销售对象群体对 Web 方式接受吗?销售公司可以提供哪些服务来保 持他的忠诚度? 我们在讨论业务事件的时候,应该寻找事件的真正起源,可以肯定地说,起源不在操作者那 里,操作者只是业务响应的一部分,它是在相邻系统作了某些事情之后发生的。在上图的例子中, 起源是客户发现缺少某种商品,他发现需要订购些什么,他曾经寻找了家里有没有这个商品,或 者清点了相关商品的数目,当他决定购买的时候他拿起了电话。所以,这个事件的起源在于他拿 起电话前的大约 30 分钟。 从顾客的角度,能不能使这个事情变得更方便呢?如果销售公司知道这个顾客已有商品的数 目,并且知道他的消耗数目,顾客就根本不需要打电话了,公司会打电话给顾客通知他这种商品 他快要消耗完了,同时安排恰当的时间送货。这个场景是把产品的边界扩展到了相邻系统思维的 深处,从方便和服务的角度来说(站在顾客的角度),这是不是一个更好的想法呢?从提供服务 来保持顾客的忠诚度来说(站在销售公司的角度),这是不是也是更好的想法呢?如果是,那我 们的业务会做些什么样的改变呢?继续深入的思维和研究,可能会创造出更好的产品。 所以,我们应该通过某个工作事件的进一步思考,来设想产品应该是怎样的。检查业务事件, 特别是那些由人发起的事件,当事件发生的时候,相邻系统都在做什么?可以扩展产品的范围以 包括这些活动吗? 我们的结论是,努力找到业务用例的真正起源,考虑自己的业务用例,他真的是在工作的边 界上发生的吗?还是在它们到达组织之前就开始了?这些思考的结果,会对业务以至于产品的设 计产生重大影响。 注意:主要参与者和用户目标和系统边界有关,在“处理销售”用例中,为什么主要参与者 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 29 - 是收银员而不是顾客呢?这和系统边界有关,我们定义的销售系统边界,服务目标是收银员。如 果把系统边界定义为企业交款服务,那顾客就是一个主要参与者了。 一个用例描述了某个参与者如何与系统交互以获得对于该参与者有价值的结果。所有用例的 集合描述了系统整个的行为。但是我们不想先把用例一个个的写出来,再把它们加起来确定系统 的行为。相反,我们首先构建一个系统的情景行为,然后不断改进模型,直到真正理解系统的行 为细节。但是所有这些改进都是在整个系统的情景下进行的。 三三三三、、、、业务用例与产品用例业务用例与产品用例业务用例与产品用例业务用例与产品用例 我们已经强调了理解工作,而不是理解产品的重要性。通过查看更大范围的工作,可以对业 务需求提出更多的问题并且构建出更好的产品。研究业务用例,我们主要考虑的是工作要做哪些 事情,相邻系统的期望和预期的结果。而产品用例,指的是建议的自动化产品所需要作出的响应。 所谓用例是这样的定义的:用例描述了系统完成动作的序列,这一序列动作对特定参与者产 生一个有价值的可见结果。这是一个相当精确的定义,换句话说就是每个用例描述一组事件,其 中某个特定的参与者与系统交互,从而得到了对这个参与者有价值的结果。 产品用例是从业务用例中推导出来的,下图表达了业务事件对产品用例的推动力。 在确定产品用例的时候,也是在选择与产品交互的参与者,精良的产品用例分析可以给后期 工作带来巨大的好处,事实上引发了基于用例的开发这样一种概念。除了上面列出这一些,还可 以在很多地方享受产品用例带来的好处:  它提供了一种手段,用于发现一些相关的需求并进行分组。  可以通过产品用例来计划实现版本。  测试人员可以把产品用例作为编写测试用例的输入信息。  产品用例为构建模拟原型提供了业务上的基础。  能够更早的响应变更,因为产品用例可以追踪到业务用例,然后追踪到业务事件。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 30 - 在前期分析中,主要是在抽象层面构造用例模型,关注的是产品用例之间的关系,建立一个 产品整体的图像,而不是对每个用例进行详细描述。在进入迭代过程之后,才必须对选中的用例 (或者用户描述)进行详细的分析和描述,从而进入完整的开发过程。 四四四四、、、、逐步构建用例模型逐步构建用例模型逐步构建用例模型逐步构建用例模型 在仔细分析了系统事件及其作用以后,可以根据系统事件构建用例了,由于这是针对用户眼 中的产品而言的模型,所以称之为产品用例。如下图所示。 产品用例不仅仅是把业务用例“翻译”成产品用例,更重要的是为设计提供想法,用例不仅 仅是一种需求开发,还是一种用于驱动整个软件开发生命周期的软件工程技术。但是,在前期规 划系统的时候,主要的着眼点还是放在建立体系的层面。尽管并不存在一个完美的过程来开发这 个模型,但还是可以推荐一个简单而有效的步骤。 1))))第一步第一步第一步第一步::::确定和描述参与者确定和描述参与者确定和描述参与者确定和描述参与者 构建用例的第一步是确定所有与系统交互的参与者,从系统的上下文图中,我们可找到大部 分参与者。在这一步中,我们可以考虑以下问题:  谁使用系统?  谁从系统得到消息?  谁向系统提供消息?  公司在什么地方使用系统?  谁支持和维护系统?  其它还有什么系统使用这个系统? 2))))第二步第二步第二步第二步::::确定用例确定用例确定用例确定用例,,,,写一个简短的描述写一个简短的描述写一个简短的描述写一个简短的描述 一旦决定了参与者,下一步就是决定参与者为了完成他们的工作使用的用例。我们可以通过 按顺序界定每个参与者的特定目标来做这件事情:  参与者用系统做什么?  参与者是否在系统中创建、存储、改变、删除或者读取数据?  参与者是否需要通知系统相关的外部事件或改变?  是否需要通知参与者系统内发生的事情? 要仔细思考用例的名字,通常用例是一个短语,以一个动词开始,说明参与者拿用例做了什 么。有了名字,还需要提供一个简要地描述,概要的说明这个用例的工作。这个描述与前面提到 的用户描述(use story )是一样的,可以写成:“作为(参与者),我们希望可以(能力)以便(用 例结果)”。把这些用户描述列成表,就可以在前期进行优先级的排序等其它工作。也可以把若干 用户描述合并成主题,这样就可以进一步首先从广义上组织需求信息了。这种简短的描述也便于 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 31 - 与用户交流。 3,,,,第三步第三步第三步第三步::::确定参与者和用例的关系确定参与者和用例的关系确定参与者和用例的关系确定参与者和用例的关系 尽管我们注意到只有一个参与者能够发起一个用例,但很多用例可能有多个参与者参与。在 过程的这一步,要分析每个用例,看看有哪些参与者与它交互?审查每个参与者预期的行为,以 验证参与者是否参与了所有必要的用例,是否达到了想要的结果。这个过程可能非常复杂,需要 团队成员一起来讨论,很快就会有大量的用例和参与者,通过用例图,可以使每个人都很好的理 解系统。 4,,,,第四步第四步第四步第四步::::简略描述单个用例简略描述单个用例简略描述单个用例简略描述单个用例 下一步是简略描述整个用例,从而在更深层的意义上理解系统的行为。所描述的包括基本流 和替换流。基本流一般只有一个,称为主事件流,替换流(也称备选事件流)主要指的是正常的 分支与异常事件。为了发现这些事件,需要问一下这些问题: 基本流基本流基本流基本流::::  什么事件发起该用例?  用例如何结束?  用例如何重复某些行为? 替换流替换流替换流替换流::::  用例是否有可选项?  可能发生什么偶然事件?  可能发生什么变数?  什么可能出错?  什么不可能发生?  可能把什么资源锁住? 这个阶段的描述不需要很复杂,主要在宏观上表达问题,更细腻的描述可以在细化系统定义 的时候完成。 2.3 从系统工程的角度分析和组织需求信息从系统工程的角度分析和组织需求信息从系统工程的角度分析和组织需求信息从系统工程的角度分析和组织需求信息 一一一一、、、、应用系统工程帮助应用系统工程帮助应用系统工程帮助应用系统工程帮助分析问分析问分析问分析问题题题题 在大多数需求分析的资料中,都提出来在整个需求分析的过程中,只是关注收集用户需要, 而不要考虑解决方案。在某种意义上,这对防止初期的解决方案对后期设计的影响是有一定的意 义的。但是,从另一个方面来说,把这个观念绝对化也是有害的。从架构设计的角度来看,软件 架构的要求也是一种需求,可能也是第一阶段收集需求的时候最值得关注的需求。这就需要在需 求分析的时候考虑到整体架构的因素,反过来这也会使需求过程更加趋于合理。 在多维度敏捷开发的模型下,第一阶段各个小组并没有成立,整体性的需求收集和架构小组 是这一阶段最重要的团队,在这一阶段,团队成员综合各自的知识,从系统的角度分析和解决问 题,往往更容易导致设计出一个高质量的产品,这就需要利用系统工程来帮助分析问题。 根据国际系统工程委员会(International Council on Systems Engineering ,INCOSE)的说法: 系统工程是一种交叉学科的方法,这种方法主要用于开发周期的早期侧重于定义客户的需要及所 需的功能,并且为需求建挡,而后进行设计合成与系统确认,同时考虑所有方面的问题,包括: 操作、性能、测试、制造、成本和进度、培训与支持以及配置。 系统工程把所有分支和专业小组集成一个团队,形成一个结构化开发过程,形成从概念到产 品的到操作的转化,系统工程同时考虑客户的商业和技术需要,目标是生产出满足客户需要的高 质量产品。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 32 - 这段定义尽管比较长,但我们确实可以把系统工程看成一种问题分析技术,我们可以把这种 技术应用在特定的环境中,帮助我们理解在系统中运行的应用软件之上的需求。 1))))系统工程的实用准则系统工程的实用准则系统工程的实用准则系统工程的实用准则 INCOSE 系统工程实践小组定义了 8 条系统工程基本准则:  了解问题,了解客户,了解消费者。  使用基于需要的效用标准来进行系统决策。  建立并管理需求。  为得到好的解决方案,确认并评估替代方案。  验证并且确认需求和解决方案的性能。  维护系统的完整性。  使用一个清晰而且建档的过程。  根据计划管理。 事实上,系统工程准则的一个子集,是基于一个过程不断把复杂系统分解为简单系统的过程。 2))))复杂系统的组合与分解复杂系统的组合与分解复杂系统的组合与分解复杂系统的组合与分解 任何复杂系统,都可以分解成小问题也就是子系统,每个子系统都可以合理的解释和证明、 成功的设计与制造,然后在集成到整个系统中去。子系统还可以分解成子系统,这种分解(或逐 步细化)过程会一直进行下去,如下图所示,例如 F22 战斗机被分解成 152 个子系统。 在下面的情况发生的时候,可以断定子系统的分解已经完成并且是正确的。  对功能进行分布和划分,对于以最小的成本和最大的灵活性来完成系统整体的功能来说 是最优的。  每个子系统都可以被一个小型团队所定义、设计与开发。  每个子系统都可以使用可行的制造技术制造出来。  在成功地模拟了子系统的其它子系统的接口之后,每个子系统都可以单独进行可靠性测 试。  对于子系统的各个物理方面(大小、重量、位置、分布)都要进行考虑,并在整个系统 环境下加以优化。 二二二二、、、、系统工程中的需求分配系统工程中的需求分配系统工程中的需求分配系统工程中的需求分配 从系统工程的角度考虑需求,就需要把子系统作为一个重要的实体加以考虑。首先赋予子系 统功能描述(例如:子系统 B 将运行风速算法,并直接驱动前导显示器),然后再自上而下的分 解需求。 1))))关于派生的需求关于派生的需求关于派生的需求关于派生的需求 在这样的情况下,很可能我们会生成一个派生的需求,它们必须被赋予子系统,典型情况下, 派生需求可以分成两种:  子系统需求子系统需求子系统需求子系统需求:是子系统必须满足,但随最终用户未必有直接利益的那种需求(例如:子 系统 A 必须计算飞行器的空速)。  接口需求接口需求接口需求接口需求:是子系统为了完成整体任务,必须与另一个子系统进行通信产生的需求,子 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 33 - 系统创建的同时也促进了子系统之间接口的创建。 但是这些子系统需求是真的需求吗?对最终用户来说,它可能并不是重要的。但对于需求人 员来说,开发人员也是需求提供者的客户,这些需求对于开发人员是重要的,所以也是一种至关 重要的需求。 还需要注意的是,虽然这些子系统需求对项目的成功至关重要,但他们是由系统的分解得来 的,不同的分解方式会产生不同的派生需求。所以,在前期设计人员参加一些讨论还是非常有意 义的。重要的是要认识到,对派生需求的描述会影响最终系统完成工作的能力,以及系统的可维 护性和稳定性。 2))))子系统分包子系统分包子系统分包子系统分包 还有一种更加复杂的问题经常会发生,那就是子系统经常是由不同的团队来开发的,这也是 我们生成子系统的目的之一。在这样的情况下,子系统需求与接口,就有可能成为团队之间的契 约。更多的情况,子系统是由其它公司作为分包商开发的,这使得需求失去了其系统和技术的环 境,改变需求变得很困难,项目会被它的短处所牵制,很多大型项目就是在这个问题上翻的船。 3))))解决问题解决问题解决问题解决问题 这就是为什么要专门讨论从系统工程的角度研究需求的原因。我们可以做什么呢?在处理极 其复杂的系统的时候,你可能需要考虑以下建议:  开发、理解和维护横跨子系统的高层需求和用例。它们描述了系统的整体功能,这些用 例提供了系统整体的工作背景,要确保你没有“只见树木,不见森林。”他们还有助于 确保系统架构设计支持最可能的使用场景。  把划分工作做得最好,并把功能限制在子系统范围内。在系统功能中使用对象技术原则: 封装和信息隐蔽。根据合同建立接口,使用消息而不是数据共享。  可能的话,把软件作为一个整体来开发,而不是一些分开的部分。避免在接口的两端, 为了双方的决策,软件都必须重构核心元素(对象)的状态。与硬件不同,软件需求在 双方的分配并不是一个清晰的划分。  当对接口进行编码的时候,在接口两端采用共同的代码。否则如果有什么变化(比如优 化)的话,两端的同步将会非常困难。另一方面,如果将来两个子系统之间的界限消失, 比如系统工程师发现两个子系统可以合并,合并将会非常困难。  最后,看看能否找到一个资深工程师来帮助你实施系统工程,如果他以前做过类似的工 作,它的经验将会对您有很大的帮助。此外这样做的副产品是,这样做有助于消除代沟。  在多团队开发的时候,最好是其中的一个团队来开发接口代码,否则,两个团队有很多 工作是重复的。这样你将会确实的生成系统新的需求,包括接口。把接口作为正式的需 求,可以避免很多集成上的问题。 三三三三、、、、组织复杂软硬件系统的需求组织复杂软硬件系统的需求组织复杂软硬件系统的需求组织复杂软硬件系统的需求 无论需求是表示成用户描述、特性列表、用例集、或者是其它的形式,都必须获取需求并形 成文档。一个由多人参与的工作不可避免的存在交流问题,这就需要获取一份能够被复审和批准, 大家都人认可的,用来参考的文档。传统上,利用需求规格说明这种大型文档,来获取和交流这 种信息,但是敏捷开发基本理念似乎并不需要这样的大型文档。不过,对于大型复杂的敏捷项目, 不可能没有需求规格说明就盲目的开始项目开发,关键是这种规格说明的粒度要合理。 关于如何组织需求信息有几个要点:  对于复杂项目,必须获取需求规格说明,并把它记录在文档、数据库或工具中。  不同类型的项目,要求不同的需求组织技术。  复杂系统需要每个子系统都有需求规格说明。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 34 - 在项目的前期,我们只是宏观的讨论组织需求信息的有关问题。我们必须了解到,很少有需 求能够在单个文档中定义,其原因是:  系统可能非常复杂,建档数量较大,需要有组织的和交互访问的技术。  该系统可能是相关产品系列的一个成员,没有什么文档可以包括所有的规格说明。  所构建的系统可能只是一个大型系统的子系统,仅能满足所确定需求的一个子集。  必须把市场和商业目标从产品的详细需求分离出来,这需要多个文档。  也可能在系统中建立制度或法规这样的需求,这些需求可以在其它地方进行建挡。 在以上例子中,都可能需要维护组成多个需求集的需求,每个需求集反映了一个特殊系统加 上若干子系统的集合的需求,下面有一些这样的例子:  一个需求集用一般术语定义系统特性,这称之为前景文档前景文档前景文档前景文档(Vision Docunment )。而另一 个需求集使用更专用的术语定义需求,这由用例模型用例模型用例模型用例模型和相关的补充需求补充需求补充需求补充需求组成。  一个“父”需求集定义整个“系统”的需求,包括硬件、软件、人员和过程,另一个需 求集只定义软件子系统的需求。  一个需求集定义产品系列的全部需求集,另一个需求集只定义特定应用和特定版本的需 求。 下面我们来看一个组织复杂软硬件系统的需求的案例。对于非常复杂的系统,唯一合理的方 法是把它创建成由多个子系统组成的系统,这些子系统有时是其它子系统构成的系统。在特殊情 况下,比如飞机控制系统,将包括上百个子系统,而每个子系统都有自己的硬件组件和软件。 1))))创建系统级的需求规格说明创建系统级的需求规格说明创建系统级的需求规格说明创建系统级的需求规格说明 在这样的情况下,首先创建一个系统级的需求规格说明,在不了解或者不引用任何子系统的 情况下来描述系统的功能行为。例如飞机油料的装载能力、爬升速度、飞行最大高度等等。 2))))进行子系统划分进行子系统划分进行子系统划分进行子系统划分 正如讨论过的,一旦对系统级的需求达成共识,就开始系统工程活动。我们可以把系统分解 成多个子系统,描述子系统之间的接口,把系统级的需求分配到各个子系统,由此产生了系统架 构描述,定义了系统划分以及系统之间的接口。原则上是可以任意确定划分大小的,一个可能的 原则是,每个子系统可以由一个小型团队通过多次迭代来完成。 在系统分解的时候,必须对系统划分和接口设计进行仔细研究,确保接口有效而且最少。一 个有效的方法是使用 n2 图,n2 图可以有效地对划分和接口进行评估,它是捕获子系统单元如何 输出的一个好办法。下面是一个 n2 的例子,它的特点是:  主对角线方向是系统的划分;  各划分的输出显示在行中;  各划分的输入显示在列中; 数据从一个划分到另一个划分是按照右→下→左→上的路径传递的。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 35 - 下图是一个飞行控制系统 n2 图,为简化表示,图中没有像正规图形那样表示出接口的名称 和类型。 在分析阶段仔细考虑并稳定接口是非常必要的。系统分析师和设计师可以一直这样做下去, 直到表示出接口中所有的原始对象为止。初步的考虑可能是混乱的没有条理的,但有了这样的矩 阵就可以使自己的思维逐步的条理化,最后达到非常优秀的设计。 3))))开发子系统需求规格说明开发子系统需求规格说明开发子系统需求规格说明开发子系统需求规格说明 下一步,为每个子系统开发一个需求规格说明,这些规格说明应该在不引用它自己的子系统 的情况下,完整地描述它的外部行为。在这个过程中会产生一类新的需求:派生需求。这类需求 不是描述整个系统的外部行为,而是描述子系统的外部行为,因此,系统设计过程为组成系统的 子系统创建了新的需求。特别是系统之间的接口成为关键需求。本质上这是子系统与另一个子系 统之间的契约,或者是对子系统同意完成的事情的承诺。 4))))对子系统进行划分对子系统进行划分对子系统进行划分对子系统进行划分 一旦需求达成一致,再次进行系统地分析和设计,如果需要,可以进一步把子系统分解成它 自己的子系统,并且分别开发各个子系统的需求规格说明,如下图所示。 在每一层,都把来自上一层的需求规格说明分配给适当的下一级需求规格说明。例如,燃料 容量需求分配给燃料控制系统和燃料储备系统,同时恰当的发现和定义新的需求。 5))))最底层的需求规格说明最底层的需求规格说明最底层的需求规格说明最底层的需求规格说明 最底层的需求规格说明指的是那些不能再分割的需求规格说明,通常对应于仅仅是硬件或者 仅仅是软件的系统,如下图所示。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 36 - 这些需求规格说明在开发进程中还会有一个演化的过程,使细节更容易理解。 2.4 利用结构规模的估计修正子系统划分利用结构规模的估计修正子系统划分利用结构规模的估计修正子系统划分利用结构规模的估计修正子系统划分 一一一一、、、、规模的规模的规模的规模的估计估计估计估计 在上面的阐述中,如果划分大小的原则是,是每个子系统可以由一个小型团队通过多次迭代 来完成,那就需要对分块的规模作出估计。另一方面,要实现好的项目的规划,其必要的输入就 是规模估计以及持续时间的估计。一个基本的过程如下图所示。 曾经人们付出极大的努力构建了一些重量级估计方法,例如用功能点方法估计规模,用 CoCoMo 模型估计工作量和持续时间等。这些方法努力从数学的角度揭示它们之间内在的关系, 对软件工程学的贡献是很大的。功能点分析法(Function Point Analysis ,FPA)是 20 世纪 80 年 代由 IBM 的 Albrecht 提出来的,它的意图在于度量值和软件的代码量无关。目前已经成为研究 很多重要软件课题的标准,它包括:生产力基线和基准、质量基线和基准、过程改进经济学、合 同外包等。 经验证明,应用功能点分析来度量软件的规模是非常可靠的,尤其是在项目估计、变更管理、 生产率度量和功能需求的沟通等方面。但是,这种重量级方法计算比较繁琐,需要的信息比较多, 应该说对于经典瀑布式过程的还是比较合适的。但对于敏捷开发这种轻量级过程,由于原始信息 本身就比较简约,变动比较大,就需要寻找一些轻量级的估计方法。 1,,,,用描述点估计规模用描述点估计规模用描述点估计规模用描述点估计规模 度量理论的本质是构造映射的基准,通过基准使人们可以从事物的比较中获得对事物的理 解。软件规模度量也是利用相同的原理。但是,敏捷方法给规模度量提出了新的难题,首先,用 户描述是一种功能的抽象表达,细节信息不太清楚,很难用功能点这些重量级方法来计算。另一 方面,敏捷方法提倡小组共同度量,这就更需要一种经验型方法,而不必请专业的估计师来度量。 描述点规模度量是解决这个难题的方法之一。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 37 - 2,,,,描述点是相对的描述点是相对的描述点是相对的描述点是相对的 我们先通过一个例子看看描述点度量的原理。假定我们需要比较 5 个用户描述的规模大小, 而我们自己曾经做过类似功能 A 的产品,我们很熟悉它的工作量,我们希望用这个描述做基准, 判断其它描述的相对大小。我们定义一个“描述点”作为度量标准,直观的感觉,它的规模应该 是中间大小,我们把它的点数定为 5。 仔细的思考和比较,我们就可以给其它的描述给出一个合适的描述点数。功能 C 大约是它 的两倍,所以给它 10 ;功能 E 接近功能 3,但比它稍小,所以给它 9;功能 D 是最小的,所以 给它 1;在不知道细节的情况下,有时候需要猜测,比如功能 B 有多种做法,但一般的印象,最 大的规模也比功能 A 小,最小的规模也比功能 D 大,这样比较的结果,分配给它 3 是合理的。 这样得到的结果如下表所示。 序号序号序号序号 功能功能功能功能 描述描述描述描述点点点点 1 功能 A 5 2 功能 B 3 3 功能 C 10 4 功能 D 1 5 功能 E 9 我们可以看出来,通过基准和比较,我们可以把一个模模糊糊的感觉,用量化的方式表达出 来。这里有三个关键:  所有参与比较的最好在同一个数量级,比如航空管制系统是多少描述点?这就很难说 了,一般来说越接近基准,越容易判断;  基准点最好在相对中间的位置,因为如果基准点偏高或者偏低,离开基准点比较远的部 分误差就比较大;  就是有些情况下可以构造新的参照点,比如张三“身高点”是 5,李四是 8,王五比张 三高,但给多少点呢?如果比李四高可以给 10 ,如果比李四底可以给 7,这样就避免了 模糊性。 根据这个原理,我们可以建立“用户描述”的“描述点”来度量每个功能的规模。一方面, 由于软件开发对需求可追踪性的要求,一般用例和功能包是对应的,这就对用例的规模要求相对 不要差别太大,这恰恰满足了一个数量级的条件。另一方面,当我们有一定的开发经验以后,一 个典型的用例工作量到底有多少,还是有一定的数的,这就使“描述点”度量规模成为可能。 描述点是用于表达用户描述、功能或者其他工作的总体规模的度量单位。我们使用描述点进 行估计的时候,对每个对象分配一个点值。我们分配的原始点值本身并不重要,重要的是这个值 的相对大小,一个分配值是 2 的用户描述工作量是分配值是 1 的用户描述的 2 倍。 建议基准选择一个基本上处于中等的用户描述,然后各他分配一个基本上处于中间的描述点 值。这个中间点值可以定为 5,然后可以比较基准描述点,或者与已经分配了点值的用户描述进 行比较,来估计其它的用户描述。 研究显示我们对以基准数为核心相对大小这一类东西,同样数量级内的事情估计的准确度比 较高。例如,假定在同一个街区,以一个停车场为基准,另一个停车场距离是它的几倍这类估计, 一般估计的比较准确,但是要估计纽约的一个停车场距离是它的几倍,那误差就会相当大了。所 以,我们希望大多数估计都是在一个数量级的范围内。 推荐的估计尺度是 1、2、3、4、5、8。这称之为斐波纳契数列,它的特点是两另两数之间 的差距随着数的增大而增大。这个非线性数列反映了数值越大,估计的不确定性越高,因此非常 便于使用。通过主题,开发小组可以减少估计的工作量。但是也要认识到,对主题的估计,不确 定性要比更明确、更小的用户描述进行估计更高。 在最近几次迭代要实现的用户描述需要足够小,以便在一次迭代中完成,这是应该在一个数 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 38 - 量级范围内估计这些对象,我们使用的序列是 1、2、3、5、8。离最近几次迭代更远的用户描述 可以当成主题,可以用 8 以外的数据来估计它,也就是:1、2、3、5、8、13 、20 、40 、100 。 某些情况下,这种描述点定义需要一些猜测,也需要一些工作经验,随着时间的推移,估计 会越来越准确。 二二二二、、、、持续时间的估计持续时间的估计持续时间的估计持续时间的估计 1,,,,速度速度速度速度 一个没有单位的描述点估计为什么可能会有效呢?还需要知道一个概念就是“速度 (velocity )”。速度是对开发小组进度律的度量,可以通过计算小组在一次迭代中完成的描述点 数的总和来得到速度。比如,下组一次迭代完成了 3 个用户描述,每个描述点为 3,那么它的速 度就是 15 。比如小组在上一次迭代中完成了 10 个描述点,那么这次迭代中,有 2 个 5 点的用户 描述,或者有 5 个 2 点的用户描述,都是可以完成的,这个估计应该是正确的。 如果把所有的预期描述点加起来,就会得到项目总的规模估计,用这个数值除以小组一次迭 代能够完成的描述点数,就可以得到迭代次数,把这个时间反映在日历上,就可以得到进度表。 敏捷规划的一个关键原则就是先估计规模,然后推算出持续时间。 2,,,,利用速度修正估计误差利用速度修正估计误差利用速度修正估计误差利用速度修正估计误差 幸运的是,随着开发小组在项目的用户描述开发上取得进展,他们的速度在最初几次迭代就 可以显示出来,使用描述点估计方法的美妙之处,就在于对速度的使用可以自我修正。假定开发 小组要开发一个 200 个描述点的项目,他们最初估计一个迭代可以完成 25 点,这样他们认为 8 次迭代就可以了。但是项目开始以后他们发现一次迭代只能完成 20 点,他们立刻就可以判断出 项目需要的迭代次数是 10 次而不是 8 次。这种修正早期就可以完成,并不需要等到项目接近尾 声的时候仓促的加班,以降低质量换取时间的达到。 由于所有的数据都是无量纲的相对数据,所以要膨胀就整体膨胀,要缩小就整体缩小。用前 期的数据外推出后期的结果,这从数学上来说也是合理的。把工作量与持续时间的估计隔离开, 允许对它们独立的作估计,只需要通过计算和推算,这个区别很微妙但很重要。 2.5 利用模式的观点重构问题利用模式的观点重构问题利用模式的观点重构问题利用模式的观点重构问题 一一一一、、、、对功能分解的再讨论对功能分解的再讨论对功能分解的再讨论对功能分解的再讨论 上述功能分解方法有时候也称之为“自顶向下逐步求精法”,这是一种长期主导软件业的分 析思想,其基本原理在于如果一个功能对人脑来说太大,以至于不能立刻勾画或者实现它,那么 就把它重复分解成小到足以能够处理的子功能。用户所需求的每个主功能都可以精确分配给一个 设计元素,保证每个功能都被实现,每个设计元素都可以回溯到需求的功能,保证设计不包括多 余的元素。 这样做还有个附带的好处:不同的子功能可以分配给不同的工程师或者团队,使他们各自处 理不同的设计单元。对于一个大型系统,就像我们上面说过的飞机控制系统或者操作系统,这样 的分配是必需的,这样就有可能把一个复杂的问题变得比较简单和可行。 但是仅仅靠分解是不够的,我们经常会看到,分析人员所作出来的功能分解,到了有经验的 设计人员手中,他们会从下面几个方面改变当初的分解:  从质量属性对产品的要求的重新考虑产品形态;  从耦合性与内聚性的角度从新考虑划分方法; ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 39 -  从各个功能相互的关系(共享、缠绕、分散)重新考虑分层;  从复用与构件化的角度对某些情况进行重新处理。 结果,分析人员的功能划分可能会完全变了一个样子,很多情况下会把划分变得更加简单和 有效。当然从迭代和优化的角度,这种变化也无可厚非。但是细究起来,还是可以问:为什么即 使有了一段工作经验的分析人员,仍然不能像一个有经验的设计人员一样,在初期就形成一个有 效的功能划分呢? 我们应该看到,功能分解只是诸多问题解决技术的一种,如果一个分析人员不去关注历史上 各个项目类似功能的成功实现方案,只是一味的就功能谈功能,往往不能得到很有效的划分方法。 在上一节讨论功能划分的时候,我们主要注意到了根据子系统(或者功能块)的规模,来控 制划分的粒度,但是具体如何划分还是比较随意的。我们应该看到,把高层问题分成子问题事实 上可以有多种划分方法,而且早期也没有办法判断哪种分解方法更好更有效。只是在开始设计以 后,才能够评价一种特定的分解在实现上是不是合理,但到了那个时候,去纠正原先的高层的错 误已经太迟了。因此,比较常用的办法是:  尽可能收集和应用已经存在的、经过时间考验的划分和设计。  把新的方法融入到老的是设计中去,而不是总是另起炉灶。 一个有心的分析师会不断关注自己的功能分解方案,是如何被设计师改变的,改变的原因是 什么?并把这些心得记录下来,并在后来的项目中尝试把这些思想应用到新的划分中去。慢慢的 收集一些被多次应用的方案,这样就有可能迅速做出判断。 二二二二、、、、利用模式解决划分中的困难利用模式解决划分中的困难利用模式解决划分中的困难利用模式解决划分中的困难 把上面的思想进一步延伸,就可以形成一种“模式”的概念。所谓“模式”强调的是某种功 能单元可能被使用上百次,但使用的方式却不尽相同。模式是一种灵活的思想,运用它却需要智 慧和想象力,因为没有两种完全一样的功能需求。 “模式”这个词最初来自于 Christopher Alexander (克里斯多弗. 亚历山大),在城市规划和 建设的过程中,他发现许多相同的原则,在 Pattern Language (模式语言)这本书中,他把建筑 学中大量简单事务划分为若干模式(咖啡馆、街角杂货铺、天窗、与腰等高的柜台等)当拥有丰 富的模式就相当于拥有的大量的词汇,有助于使设计更完美。这个思想同样可以用在软件分析和 设计中,当拥有大量模式的时候,就可以使自顶向下的分解变得更加主动。 在软件开发的早期,由于强调创新,软件领域内不会产生可靠的结果,每个新设计都会包含 大量的没有经过测试的新思想,而这些未经测试的新思想经常会失败。 当一个工程领域成熟以后,我们就有机会对大量经受了时间考验的设计进行组合和微小的修 改,从思维方式上,就可以专注于解决良好定义的问题集合中的问题,而不是一切都从头开始。 就好比做变压器,现在由于已经有了经过考验的模式,只需要告诉他基本参数,他就可以用相同 的形式做出用户需要的变压器,而不要从磁通量开始计算和设计。 不过,软件的创新仍然在继续,所以我们就可以把软件分成两的部分:  条理性工程:应用经过考验的模式,通过恰当的组合和微小的修改达到目的。  探索性工程:对新的各种各样的设计的非结构化探索。 当我们在分析系统的时候能够很好的分开这两个部分,整个分析和设计过程就可能变得及其 有智慧。模式的意义并不在于死搬硬套,世界上的问题是千变万化的,但如果我们已经有了大量 问题家族和解决方案集合,我们的解决方案就可能受到它的启发,这种启发往往是极其珍贵的。 三三三三、、、、模式的合成与分解模式的合成与分解模式的合成与分解模式的合成与分解 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 40 - 如果已经存在大量的分析和设计模式,就可以使我们在较高层次上的功能分解变得充满信 心。没有经验的分析员在分解的时候总是试探性的尝试使用各种分解方法,希望从中得到某种启 发,有经验的人往往可以根据已知的模式进行恰当的组合,使将来的程序运转起来更加简单。 有经验的设计师在实现功能分解的时候好像与别人没什么不同,但是由于他们掌握了大量的 历史信息,直到某种划分在实现上更加可靠和方便。他们力图把高层次的模式分解成子模式,总 是把这种子模式与已知的设计结合起来。这种抽象问题的分解演绎成一个能够构造的组件,结果 就使复杂问题的解决有了保障。 好的思维方式往往可以引导人们走向成功,这是历史大量的案例告诉我们的。 在莱特兄弟制造飞机之前,人们的种种努力都失败了,因为那些人的设计大多借鉴了鸟的结 构,而鸟是无法用机械实现的。莱特兄弟对飞机的功能作了分解,专注于每个子功能在当前的工 艺条件下都能够很好的实现(或者已经实现)。 子功能子功能子功能子功能 实现实现实现实现 前进 四缸引擎 推进 螺旋桨推进器 升空 机翼 驾驶 方向舵,弯曲的翼尖 然后,每种功能都成为一种模式,具体的实现可以委托完全不同的工程师来完成,一直到今 天,尽管飞机的性能已经有了翻天覆地的变化,但这些基本模式并没有改变,这个案例,足可以 对我们需求分析人员提供很好的思维方式借鉴。 关于模式,还可以有更深入的思考,当人们刚刚介入某个领域,思维上还处在混沌状态的时 候,是没有模式可言的。随着对问题理解越来越深、解决的问题越来越多,慢慢的总结、取舍、 试验、归纳,就会逐步的形成一些属于自己的模式,表现在处理问题就会有自己的一套办法,这 就是工作经验比较多的表现。但是,模式也会限制人们的思考,思维方式更向上一步,最困难的 也就是要突破自己已经形成的固有模式,这种突破固有思维模式的过程也就是创新的过程,也就 是说一个人最困难的是要战胜自己,只有少部分人能做到这一点,而做到了这一点的人才有可能 达到光辉的顶点。当然,在这样一群人的眼里,顶点是不存在的。 事实上,在今天所有科学技术的进步,以及社会形态的变化,都是突破固有思维模式的过程。 所以,对待模式(Pattern )既要认识到它的重要,在主动地建立了大量模式以后,又要防止思想 被模式所束缚,这实际上并不是一件很容易的事情。 四四四四、、、、发现需求的变化规律发现需求的变化规律发现需求的变化规律发现需求的变化规律 今天对软件开发影响最大的,恐怕就数需求变更了。需求的蠕变,可能使开发成本上升,交 付时间推后,产品质量下降,所以关注变更无论对分析和设计都是相当重要的。 从需求分析的角度来看,首先要做到的是尽可能全面地收集需求,分析需求,确保不是由于 前期需求分析的漏洞造成后期无畏的变更,当然如果发生了这种变更,无疑责任是在需求分析师 和他的团队身上。 但是很多情况下需求变更是客观存在的,这是今天竞争激烈,变化越来越快的商业环境所决 定的。如何灵活快速适应变化甚至创新求变,成为企业生存的头等大事。当业务发生变化的时候, 难道需求不会发生变化吗?复杂性和易变性是信息技术(IT)必须面对的现实,无论是构建新的 应用、替换现有的应用,以及及时处理各种维护与改进的要求,都是处理各种复杂情况,这种对 于复杂性和易变性的挖掘和发现,是对产品开发的巨大挑战。考虑业务的易变性问题,需要把变 和不变进行恰当的分离,大致的情况可以分成两种情况:  很多情况下业务流程具有相似性,而业务单元却各自独立的变化,这在业务粒度比较小 的时候尤其普遍。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 41 -  另一种经常遇到的情况,是业务单元具有相似性,而业务流程将会发生改变的情况,这 在业务粒度比较大的时候很常见。 今天有一系列的设计模式可以解决这一类问题,如果需求人员能够很深入地把这些变化规律 找出来,设计人员就可以采取适当的设计措施,使产品有针对性地适时、快速的响应变化。这种 具备恰当、明智的应变措施的产品,将可以在变化的环境中高质量长周期的运行。但如果需求人 员不能挖掘出这种规律,那设计人员的设计将会非常盲目,技术措施的应对点也会不对,虽然费 了很多努力,但是设计无法与客观世界的变化特点融合。一般来说,盲目的应用设计模式只会使 产品的性能变得更糟糕。 所以,从现代的观点来看,需求分析不仅仅是发现功能性、非功能性需求。也需要关注客观 世界的变化规律,这也是一种需求,可能对后期产品架构设计造成重大影响。 五五五五、、、、组织产品线组织产品线组织产品线组织产品线的需求的需求的需求的需求 许多产业构造了一系列在功能上相近的产品集,但每种产品又包含了某些独特的特性,被称 之为产品线。假定,正在构建一套软件系统,每个产品都有共享的功能,在使用过程中需要共享 数据,或者与其它部分进行通讯。在这种情况下,可以用如下方法组织需求:  开发产品系列的前景文档,描述产品共同的工作方式以及共享的特性。  为了更好的理解共享用法的模型,也应该设计一套用例,先是用户如何与共同运行的不 同应用自建交互。  开发定义关于共享功能的特殊需求的公共软件需求,例如,公共 GUI 和通信协议。  为系列中的每个产品开发前景文档、补充规格说明以及定义特殊功能的用例模型。 组织的结果如下图所示。 前景文档(Vision Docunment )是在较高的抽象层次定义问题和解决方案,它使用一般的术 语描述应用,包括对目标市场、系统用户以及应用特性的描述。前景文档是有效需求过程的关键 成分,也可能是最重要的文档。因为一份简短甚至不完全的文档,都有助于项目成员朝向同一个 目标工作。团队由此有了共同的目标和剧本,也可能会有共同的心理。如果团队的目标未知而且 互相冲突,很快就会产生混乱。 前景文档获取用户需要、系统特性以及项目的其它要求。这样,前景文档的范围横跨需求金 字塔的上两层,在较高的抽象级别上定义问题和解决方案。 由于产品可能会以很多方式发生变化,因此在需求分析中需要尽可能获取变化点,这些变化 点包括特性、平台、用户接口、质量属性以及目标市场等。除此以外,我们还要考虑如下问题。 1))))把价值映射为把价值映射为把价值映射为把价值映射为约束约束约束约束 这个问题的本质,是产品线架构的受益人如何把客户价值与架构约束捆绑。这里所谓受益人, ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 42 - 实际上是指使用架构的开发队伍。 2))))一致性和灵活性的考虑一致性和灵活性的考虑一致性和灵活性的考虑一致性和灵活性的考虑 一致性的问题:是指受益人使用架构与期望值的符合程度。 灵活性的问题:是指受益人不破坏架构的情况下,利用共享框架的创建新的没有预见到的情 况下的容易程度。 3))))防止防止防止防止产品线架构被拉向不同的方向产品线架构被拉向不同的方向产品线架构被拉向不同的方向产品线架构被拉向不同的方向 当产品线上有多个产品的时候,为了避免架构的设计被被动的拉向不同的方向,可以使用下 面的三步方法:  清楚、简明的、阐述一条迫切的用户价值。  把用户价值映射为少数的能解决的问题。  把以上问题转译为一组最小的约束条件。 遵循准则:  各方面的一致性。  实施人员信任并使用架构。  架构潜藏的知识对用户是可识别和可获得的。 2.6 基础架构的建立基础架构的建立基础架构的建立基础架构的建立 基础架构的建立,应该纳入发布规划和迭代规划,成为整个设计工作的一个环节。架构实现 可能需要一个单一团队,架构的功能,也可以用用户描述来说明,但是问题更集中在顶层描述, 主要是框架、对外通讯、内部框架之间的通讯,以及大的、集中在主题层面地描述。架构的成果 也应该是一个可发布的、经过测试的版本,一个架构一般也需要经过一到两次迭代完成。 一一一一、、、、建立弹性软件架构建立弹性软件架构建立弹性软件架构建立弹性软件架构 对于基础架构,我还有几个观点要说明。在单一团队工作结束的时候,将建立系统早期的、 关键性的第一个版本,这是一个可执行的版本,我们称之为架构基线架构基线架构基线架构基线版本。在建立好架构基线之 前可能要花费几个 Sprint ,但完成以后,将会证实你的假设、以及系统开发方法,也就可以降 低风险,基于这个架构,其它部分的开发速度将会大大加快。 什么是好的架构?如何构建好的架构呢?毫无疑问,一个好的架构必须满足性能、可靠性等 系统级的关注点,它必须是易于理解的,以便能跟踪出架构中哪一部分实现了哪些需求或者用例。 每个类(或者包)都必须扮演已定义的角色,并且很好的履行这个角色全部的、而且仅仅属于这 个角色的职责。职责应该是少量的,并且在类之间没有重复。 一个好的架构要确保不同类型的关注点互相独立,当其中一个发生改变的时候,不至于影响 系统的其它部分。架构师应该致力于创建一个弹性架构弹性架构弹性架构弹性架构,也就是说各个不同类型的关注点保持独 立,而系统中的一部分发生变化的时候,对其余部分的影响要最小。架构设计也必须满足性能、 可靠性等系统的关注点。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 43 - 好的架构必须使每个关注点互相分离,尽可能使系统一部分的改变不至于影响到其它部分, 即使有一定影响,也要清晰的识别出哪些部分需要改变,如果需要扩展架构,影响应该最小化, 已经可以工作的部分应该可以继续下去。 1,,,,分离功能性需求分离功能性需求分离功能性需求分离功能性需求 一般的希望保持功能性需求之间是分离的,功能表明了不同最终用户的关注点,并且可能互 相独立的发展,所以不希望一个功能的改变会影响到到其它。功能性需求一般是站在问题域的高 度来表达的,因此很自然的希望系统特定功能从领域中分离出来,这样,就便于把系统适配到类 似的领域中。另一方面,一些功能需求会以其它功能需求扩展的形式来定义,这样更需要它们互 相独立。 2,,,,从功能需求中分离出非功能性需求从功能需求中分离出非功能性需求从功能需求中分离出非功能性需求从功能需求中分离出非功能性需求 非功能性需求通常标识所期望的系统质量属性:安全、性能、可靠性等等,这就需要通过一 些基础结构机制来完成,比如,需要一些授权、验证以及加密机制来实现安全性;需要缓存、负 载均衡机制来满足性能要求。通常,这些基础结构机制需要在许多类中添加一小部分行为(方法), 这就意味着与基础结构机制实现的一点变动都会造成巨大的影响,因此,要使功能需求与非功能 需求之间保持分离。 3,,,,分离平台特性分离平台特性分离平台特性分离平台特性 现在的系统运行在多种技术之上,比如身份验证的基础结构机制就可能有许多可选的技术, 这些技术经常是与厂商有关的,当一个厂商把它的技术升级到一个新的、更好的版本的时候,如 果你的系统适紧密依赖于这项技术前一个版本的,那么进行升级就并不那么容易,所以要使平台 特性与系统保持独立。 4,,,,把测试从被侧单元中分离出来把测试从被侧单元中分离出来把测试从被侧单元中分离出来把测试从被侧单元中分离出来 作为完成一项测试的一部分工作,你必须采用一些控制措施和方法(调试、跟踪、日志等), 这些控制措施是保证系统运行流程符合测试要求的规程。这些方法是为了在系统执行的过程中提 取信息,以确认系统确实是按照预期的测试流程执行的。 这些控制措施和方法,经常需要在测试过程中向系统内插入一些与系统一起运行的代码,在 擦拭完成以后这些代码将会被删去,因此,希望把测试的实现与被测的系统分离开来。 二二二二、、、、建立架构基线的步骤建立架构基线的步骤建立架构基线的步骤建立架构基线的步骤 良好的架构必须尽早建立,技术在理论上,都没有办法通过重构等增量技术,把一个糟糕的 架构改造成一个好的架构,在实践中就更难了。事实上如果重构的成本超过了管理者所能接受的 程度,他就宁可简单的重写代码而不是重构。所以,一个良好的架构需要在建立成本很小的时候 建立起来,事实表明,优先架构会获得良好的投资回报,它减少了项目执行过程中的重新设计和 做无用功。如果已经建立了一个良好的架构,就需要持续的进行重新评估,并且做一些必要的完 善和重构。 1,,,,架构基线架构基线架构基线架构基线 架构是最终系统的一个早期版本,也称之为架构基线。架构基线是整个系统的子集,我们称 之为骨架系统骨架系统骨架系统骨架系统(skinny system )。这个骨架系统包含了项目结束时的“完整”系统所具有的模型 的一个版本,它包含了相同的子系统、组件和节点的“骨架(skeleton )”,但并非所有的“肌肉 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 44 - (musculature )”都已齐全。 不管怎么说,骨架系统确实具有行为,并且是可执行的代码,可能会需要对结构和状态作某 些细微的改变,在精化阶段或者架构设计迭代结束的时候,我们就可以得到一个稳定的架构了。 尽管骨架系统(架构基线)通常只包含 5%~15% 的最终代码,但他已经足够验证你所做的关 键设计了,更为重要的,你必须确认骨架系统能够成长为一个完整的系统,应该有一个书面的架 构描述文档,但现在,这个文档必须通过架构基线来验证和确认。 2,,,,用例驱动的架构基线用例驱动的架构基线用例驱动的架构基线用例驱动的架构基线 架构基线是由关键用例子集驱动建立起来的,我们称这个子集为架构重要用例,在你提取出 这些架构重要用例之前,必须尽可能收集存在的信息,以识别出系统所有的用户描述。这种识别 主要是对系统需要做的事情进行界定、探索和发现。当确认了与架构有关的用户描述以后,需要 把若干描述集合成一个个的主题。通过讨论,确定架构的基本方案,由于架构对产品的重要性, 大多数情况下,这个方案还需要经过评审,然后才能进入架构设计迭代。此时,需要把用户描述 转换为描述用例,这是对用例中的流程和步骤进行细化,描述用例会贯穿项目的整个个生命周期, 而识别用例则必须尽可能尽早的完成。 在这些识别出的用例中,确认哪些是最重要的,所谓“重要”,意味着它们组合在一起,可 以覆盖所有需要作出的关键决策:  演练系统的关键功能和特性。  涵盖大部分的功能性、基础结构、平台特性等方面的风险。  突出系统中一些高复杂性和高风险的部分。  是系统剩余部分的基础。 架构重要用例列表应该包含应用用例和基础结构用例,要注意对其中一些具有技术相似性或 者交互相似性的的用例,可以选择有一个用例作为代表,只要解决了其中的一个用例,就可以解 决其它用例了。 当挑出了架构重要用例以后,就可以分析它们当中的关键场景,通过分析用例场景,更好的 理解系统需要做什么以及系统的元素是如何交互的,通过对系统的理解,就可以定义和评估架构, 这个过程将不断迭代的形成一个稳定的架构。 稳定就意味着系统关键风险已经被解决,并且所做的决定可以基本上满足着手开发系统剩余 部分的需要,这个架构不仅受架构重要用例的影响,还受使用的平台、必须集成的遗留系统、标 准和方针、分布性需求、所使用的中间建和框架等等的影响。通过用例,可以评估所做的选择是 否足够,并且发现哪些方面还需要完善。 3,,,,迭代地建立架构基线迭代地建立架构基线迭代地建立架构基线迭代地建立架构基线 前面已经提到过,在架构设计的雏形完成以后,我们可以从以下四个视角优化架构:  从质量属性及其应对策略的视角从质量属性及其应对策略的视角从质量属性及其应对策略的视角从质量属性及其应对策略的视角:根据非功能性需求和质量验收标准,提出整体上的体 系结构策略。  从模块划分的视角从模块划分的视角从模块划分的视角从模块划分的视角:进一步进行模块划分,从开发成本与集成成本两方面综合考虑问题, 合理划分模块。  从解决缠绕和分层的视角从解决缠绕和分层的视角从解决缠绕和分层的视角从解决缠绕和分层的视角:分离出共享和缠绕部分,建立合理的分层结构  从构件化和软件复用的视角从构件化和软件复用的视角从构件化和软件复用的视角从构件化和软件复用的视角:应用已经存在的基于复用的软件构造块,修改体系结构以 适应这种构造块。考虑现有的结构能否有可以被将来复用的部分,按照构件的要求进行 设计 对于一个复杂系统,最终建立一个稳定架构之前需要经过几个迭代,每次架构迭代中必须处 理所有的架构关注点,虽然不可能在每次迭代中处理所有的关注点,但需要全面的考虑它们,每 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 45 - 次迭代过程都产生一个增量,解决一部分架构关注点。 迭代一直需要持续到所有的架构关注点都已经解决,在迭代结束时所得到的早期版本(骨架 系统)需要经过测试和运行来证实结果。伴随着架构基线的产生需要一个架构描述文档,这个文 档将成为后期开发小组工作的指南,也是后续迭代中必须遵循的依据。 架构描述还必须经过评审,已确定架构是否合理,这个描述文档还应该附上一张修定表已说 明历史的演变,它同时也说明了重要的决策。在架构迭代中,由于需要作出决策,所以进展一般 是比较缓慢的,一旦完成了架构迭代,后续的生产率就会明显提高,所以投入到架构迭代中的时 间是非常值得的。 三三三三、、、、从质量属性从质量属性从质量属性从质量属性及其应对策略的视角优化及其应对策略的视角优化及其应对策略的视角优化及其应对策略的视角优化架构架构架构架构 通过从系统工程的角度分析与组织需求信息,我们已经由需求分析得到了一个框架性的架构 雏形,但那只是架构的一个大概形状,在具体进行架构设计之前,架构师必须进行架构的进一步 分析和设计,从不同的角度审视架构设计,多角度、多层次的修改架构设计方案,从而得到一个 合理的架构基线产品。 对初步的架构轮廓作第一个方面的审视,是从需求分析中仔细思考质量需求对架构的要求, 换句话说就是获取架构因素。架构分析的本质,是识别可能影响架构的因素,了解它的易变性和 优先级,并解决这些问题。其难点是,应该了解提出了什么问题,权衡这些问题,并掌握解决影 响架构重要因素的众多方法。 1,,,,架构分析需要解决的问题架构分析需要解决的问题架构分析需要解决的问题架构分析需要解决的问题 架构分析是高优先级和大影响力的活动。架构分析对如下的工作而言是有价值的:  降低遗漏系统设计核心部分的风险;  避免对低优先级的问题花费过多的精力;  为业务目标定位产品。 这些工作,实现了对由系统工程学的角度提出的初步架构轮廓做第一次改进。 架构分析是在功能性需求过程中,有关识别非功能性需求的活动,事实上非功能需求就是质 量需求,这些信息对于架构设计来说,是最值得关注的。 下面说明在架构级别上,需要解决的诸多问题的一些示例:  可靠性和容错性需求是如何影响设计的?  购买的子组件的许可成本将如何影响收益?  分布式服务如何影响有关软件质量需求和功能需求的?  适应性和可配置性是如何影响设计的? 架构分析的一般步骤架构分析的一般步骤架构分析的一般步骤架构分析的一般步骤如下如下如下如下::::  辨识和分析影响架构的非功能性需求。  对于那些具有重要影响的需求而言,分析可选方案,并做出处理这些影响的决定,这就 是架构决策 2,,,,识别和分析架构因素识别和分析架构因素识别和分析架构因素识别和分析架构因素 1))))架构因素架构因素架构因素架构因素 任何需求对一个系统架构都有重要影响。这些影响包括可靠性、时间表、技能和成本的约束。 比如,在时间紧迫、技能有限同时资金充足的情况下,更好的办法是购买和外包,而不是内部开 发所有的组件。然而,对架构最具影响的因素,包含功能、可靠性、性能、支持性、实现和接口。 通常是非功能性属性(如可靠性和性能)决定了某个架构的独到之处,而不是功能性需求。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 46 - 2))))质量场景质量场景质量场景质量场景 在架构因素分析期间定义质量需求的时候,推荐应用质量场景。它定义了可量化(至少是可 观测)的响应,并且因此可以验证。质量场景很少使用模糊的不具度量意义的描述,比如“系统 要易于修改”。质量场景用<激发因素>< 可量化响应>的形式作简短的描述,如:  当销售额发送到远程计税服务器计算税金的时候,“大多数”时候必须 2 秒之内返回。 这一结果是在“平均”负载条件下测量的。  当系统测试志愿者提交一个错误报告的时候,要在一个工作日内通过电话回复。 这里,“大多数”和“平均”需要软件架构师作进一步的调查和定义。质量场景直到做到真 的可测试的时候,才是真正有效的。这就意味着需要有一个详细的说明。 3))))架构因素的描述架构因素的描述架构因素的描述架构因素的描述 架构分析的一个重要目标,是了解架构因素的影响、优先级和可变性(灵活性以及未来演变 的直接需要)。因此,大多数架构方法,都提倡对以下信息建立一个架构因素表。 因素 测量和质量场景 可变性(当前灵活性和未来演化) 因素(和其变化)对 客户的影响,架构和 其它因素 优 先 级 困难 或风 险 可靠性 --- 可恢复性 从 远 程 服 务 失 败 中 恢 复。 当 远 程 服 务 失 败 的时候,侦听到远程 服务重新在线的一 分钟内,重新与之建 立联系,在产品环境 下实现正常的存储 装载。 当前灵活性—我们的 SME 认为直 到重新建立连接前,本地客户简化的 服务是可以接受的(也是可取的)。 演化—在 2 年之内,一些零售商可 能选择支付本地完全复制远程服务 的功能(如税金计算器)。可能性? 高。 对 大 规 模 设 计 影 响大。 零 售 商 确 实 不 愿 意远程服务失败,因 为这将限制或阻止 它们使用 POS 进行 销售。 高 低 …… …… …… …… 注:SME 表示主题专家。 4))))从需求文档中获取架构因素从需求文档中获取架构因素从需求文档中获取架构因素从需求文档中获取架构因素 在架构设计中,中心功能需求库就是用例,它的构想和补充规范,都是创建因素表的重要源 泉。在用例中,特殊需求、技术变化、未决问题应该被反复审核。其隐含或者清晰的架构因素要 被统一整理到补充规范里面去。 3,,,,架构因素的解析架构因素的解析架构因素的解析架构因素的解析 架构设计的技巧就是根据权衡、相互依赖关系和优先级对架构因素的解决做出合适的选择。 但这还不全面,老练的架构师具有多种领域的知识(例如:架构样式和模式、技术、产品、缺陷 和趋势),并且能把这些知识应用在它们的决定中。 1))))记录架构的可选方案记录架构的可选方案记录架构的可选方案记录架构的可选方案、、、、决定和动机决定和动机决定和动机决定和动机 不管目前架构决策的原则有多少,事实上所有的架构方法都推荐记录: 可选的架构方案;决定;影响因素;显著问题;决定动机。 这些记录按不同的的形式或者完善程度,被称之为:技术备忘录;问题卡;架构途径文档。 技术备忘录的一个重要的方面就是动机或者原理,当开发者或者架构师以后需要修改系统的时 候,架构师可能已经忘了他当初的设计依据(一个资深架构师同时带多个项目的情况非常多见), 备忘录对理解当时的设计背后的动机极为有用。解释放弃被选方案的理由十分重要,在将来产品 进化的过程中,架构师也许需要重新考虑这些备选方案,至少知道当初有些什么备选方案,为什 么选中了其中之一。技术备忘录的格式并不重要,关键是简单、清楚、表达信息完整。 技术备忘录 问题:可靠性--- 从远程服务故障中恢复 解决方案概要:通过使用查询服务实现位置透明,实现从远程到本地的故障恢复和本地服务的部分复制 架构因素 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 47 -  从远程服务中可靠恢复  从远程产品数据库的故障中可靠恢复 解决方案 在服务工厂创建一个适配器…… 动机 零售商不想停止零售活动…… 遗留问题 无 考虑过的备选方案 与远程服务厂商签订“黄金级”服务协议…… 2))))优先级优先级优先级优先级 下面是指导做出架构决定目标:  不可改变的约束,包括安全和法律方面的事务  业务目标  其它全部目标 早期要决定是否应该避免保证未来的设计,应该实事求是的考虑,哪些是将要推迟到未来的 场景,有多少代码需要改变?工作量将是多少?仔细考虑潜在的变更将有助于揭示什么是首要考 虑的重要问题。一个低耦合高内聚的产品,往往比较容易适应将来的变化,但也要仔细分析这样 付出的代价,在这个问题上,架构师的掂量往往是决定这个项目的生命线。 四四四四、、、、从模块划分从模块划分从模块划分从模块划分的视角优化架构的视角优化架构的视角优化架构的视角优化架构 对初步的架构轮廓作第二个方面的审视,是考虑模块化的设计问题。也就是从架构的组成单 元来说,定义清楚子系统以后,下一步就是定义模块。 1,,,,模块化设计的概念模块化设计的概念模块化设计的概念模块化设计的概念 如何合理的进行模块设计呢?这里的关键是要保证模块的独立性。 模块模块模块模块::::模块是数据说明、可执行语句等程序对象的集合,是单独命名的并且可以通过名字来 访问,例如过程、函数、子程序、宏等。 模块化模块化模块化模块化::::软件被划分成独立命名和可独立访问的被称作模块的构件,每个模块完成一个子功 能,它们集成到一起可以满足问题需求。 利用模块化解决方案的注意事项利用模块化解决方案的注意事项利用模块化解决方案的注意事项利用模块化解决方案的注意事项::::  一般来说,倾向于每个用户描述定义一个模块。我们应该努力使每个模块的大小差别在 一个数量级之内。如果发现某个模块规模太大,就需要实现模块切割,然后针对这种切 割的结果,反过来修改需求分析的时候用户描述(或者用例)的表达方式。这就是由设 计引发的需求变更。  模块的大小一般以一个开发团队在一次迭代时间内能完成为好。模块切割方法与开发成 本有关。我们可以这样来思考模块化对软件工作量和成本的影响。实际的情况见下图, 随着模块数量的增加,开发成本减低,但是系统集成的成本增加,所以最小成本的区域 在一个合适的区间。也就是说,模块并不是越多越好,只有模块数量适当的时候,总体 成本才可能下降。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 48 - 2,,,,实现模块化的手段实现模块化的手段实现模块化的手段实现模块化的手段  抽象抽象抽象抽象:抽出事物的本质特性而暂时不考虑它们的细节。  信息隐蔽信息隐蔽信息隐蔽信息隐蔽:应该这样设计和确定模块,使得一个模块内包含的信息(过程和数据)对于 不需要这些信息的模块来说,是不可访问的。 模块独立性问题模块独立性问题模块独立性问题模块独立性问题::::  模块独立是指开发具有独立功能而且和其它模块之间没有过多的相互作用的模块。  模块独立的意义: 1)功能分割,简化接口,易于多人合作开发同一软件; 2)独立的模块易于测试和维护。  模块独立程度的衡量标准: 1)耦合性:对一个软件结构内不同模块间互连程度的度量。 2)内聚性:标志一个模块内各个处理元素彼此结合的紧密程度,理想的内聚模块只做一 件事情。 3,,,,模块化模块化模块化模块化设计的一般准则设计的一般准则设计的一般准则设计的一般准则  改进软件结构,提高模块独立性。  模块规模应该适中。大模块分解不充分;小模块使用开销大,接口复杂。  尽量减少高扇出结构的数目,随着深度的增加争取更多的扇入。扇出过大意味着模块过 分复杂,需要控制和协调过多的下级模块。一般来说,顶层扇出高,中间扇出少,低层 高扇入。  模块的作用范围保持在该模块的控制范围内。模块的作用范围是指该模块中一个判断所 影响的所有其它模块;模块的控制范围指该模块本身以及所有直接或间接从属于它的模 块。  力争降低模块接口的复杂程度模块接口的复杂性是引起软件错误的一个主要原因。接口 设计应该使得信息传递简单并且与模块的功能一致。  设计单入口单出口的模块,以避免内容耦合,易于理解和维护。  模块的功能应该可以预测。相同的输入应该有相同的输出,否则难以理解、测试和维护。 五五五五、、、、从从从从共享共享共享共享分层结构的分层结构的分层结构的分层结构的视角优化架构视角优化架构视角优化架构视角优化架构 下面我们再从第三个方面审视架构设计,关注点在模块的缠绕缠绕缠绕缠绕和分散分散分散分散。所谓缠绕,指的是一 个模块是不是包含了多个模块不同的实现。所谓分散,一个模块的实现分散在多个不同的模块中。 解决这些缠绕和分散问题,需要使用分层结构来解决。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 49 - 1,,,,层模式的问题与机会层模式的问题与机会层模式的问题与机会层模式的问题与机会 在模块分割以后,就会发现有些功能块或者某些功能是共享的或者缠绕的,我们需要把这些 模块或者功能提取出来,分成若干层次,这就是层模式。层模式是构造弹性架构的基础,好的架 构几乎都是在层这个模式基础上建立起来的。分层不是目的,分层必须把分离性与易变性、灵活 性结合起来,这也是设计层模式有挑战性的问题,也是最近几年最吸引人的研究领域之一。合理 的分层也是架构师很大的一部分需要仔细设计的工作。 1))))问题问题问题问题::::  如果系统的许多部分高度的耦合,源代码的变化将波及整个系统。  如果应用逻辑与用户接口捆绑在一起,这些应用逻辑在其它不同的接口上无法重用,也 无法分布到另一个处理节点上。  如果潜在的通用技术服务或业务逻辑,与更具体的应用逻辑捆绑在一起,这些通用技术 服务或者业务逻辑无法被重用,或者分布到其它的节点,或者被不同的实现简单的替换。  在系统的不同部分高度的耦合的情况下,难以对不同开发者清晰界定各自的工作界限。  如果高度耦合混合在系统的各个方面,则改进应用程序的功能,扩展系统,以及使用新 技术进行升级往往是艰苦和代价高昂的。 2))))解决方案解决方案解决方案解决方案: 层模式(Layers pattern )的基本思想很简单。  根据分离系统的多个具有清晰、内聚职责的设计原则,把系统大尺度的逻辑结构组织到 不同的层中,每一层都具有独立和相关的职责,使得较低的层为低级和通用的服务,较 高的层更多的为特定应用。  从较高的层到较低的层进行协作和耦合,避免从底层到高层的耦合。 层是一个大尺度的元素,通常由一些包或者子系统组装而成。层模式与逻辑架构相关,也就 是说,它描述了设计元素概念上的组织,但不是它物理上的包或者部署。层为逻辑架构定义了一 个 N 层模型,称之为分层架构(Layers Architecture ),它作为模式得到了极为广泛的应用和引 述。作为分层架构的模式,我们必须提到框架(framework )的概念,一般来说框架具有如下的 特征:  是内聚的类和接口的集合,它们协作提供了一个逻辑子系统的核心和不变部分的服务。  包含了某些特定的抽象类,它们定义了需要遵循的接口。  通常需要用户定义已经存在的框架类的子类,是客户得以扩展框架的服务。  所包含的抽象类可能同时包含抽象方法和实例方法。  遵循好莱坞原则莱坞原则莱坞原则莱坞原则:“别找我们,我们会找你。”就是用户定义得类将从预定义的类中接受 消息,这通常通过实现超类的抽象方法来实现。 3))))示例示例示例示例:::: 信息系统一般分层逻辑架构如下图所示,图中包的宽度表示的是应用范围。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 50 - 在具体架构设计的时候,可以建立比较详细的包图,但是,并不需要面面俱到。 架构视图的核心,是展示少数值得注意的元素,或者说选择一小组有意义的元素来传达主要 的思想。 2,,,,层模式的设计原则层模式的设计原则层模式的设计原则层模式的设计原则 分层并不是具有固定模式的,比如三层架构或者七层架构,而是需要根据情况合理布局。逻 辑架构还可以包含更多的信息,用来描述层与层以及包与包之间值得注意的耦合关系。在这里, 可以用依赖关系表达耦合,但并不是确切的依赖关系,而仅仅是强调一般的依赖关系。我们还需 要注意到,分层主要解决共享和缠绕问题,而在设计时要考虑下层易变更问题,我们需要仔细考 虑变化点和变更方式,合理应用设计模式,实现封装变化的结构形式,以此寻求架构解决方案, 可以考虑如下一些策略。 1))))协作协作协作协作:::: 在架构层面上,有两个设计上的决策:  什么是系统的重要部分什么是系统的重要部分什么是系统的重要部分什么是系统的重要部分????  它们是如何连接的它们是如何连接的它们是如何连接的它们是如何连接的???? 在架构上,层模式对定义系统的重要部分给出了指导。象外观、控制器、观察者这些模式, 通常用于设计层与层、包与包之间的连接。 2))))外观模式外观模式外观模式外观模式 GoF 的外观模式,定义了一个公共的外观对象综合子系统的服务。 外观不应该表示大量子系统的操作,更确切的说,外观更适合表示少量的高层操作、粗粒度 的服务。当外观呈现大量的底层操作的时候,会趋向于变得没有内聚力。外观模式为了一组子系 统提供一个一致的方式对外交互。这样就可以使客户和子系统绝缘,可以大大减少客户处理对象 的数目。本来一个类的修改可能会影响一大片代码,而加了外观类以后只需要修改很少量的代码 就可以了,使系统的高级维护成为可能。 3))))通过观察者模式通过观察者模式通过观察者模式通过观察者模式((((Observer ))))实现向上协作实现向上协作实现向上协作实现向上协作 外观模式通常用于高层到底层的操作(底层提供外观,高层实现调用)。当需要上层对底层 的操作的时候,可以使用观察者模式。也就是上层响应底层的事件,但这个事件的执行代码由上 层提供。 4))))在不同的层上模糊集合成员在不同的层上模糊集合成员在不同的层上模糊集合成员在不同的层上模糊集合成员 一些元素必定只属于一个层,但有些元素却难以区分,特别是处在技术服务层和基础层之间, ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 51 - 或者领域层和业务基础设施层之间的元素。其实这些层之间只存在模糊的差异,所以,“高层”、 “底层”、“特殊”、“一般”这些术语是可以接受的。 开发组并不需要在限定的分类上做出决定,可以粗略的把一个元素归类到技术服务层或者基 础层,也可以称之为“基础设施层”,注意,对于层并没有十分确定的命名习惯和约定,文献上 各种命名上的矛盾是常见的,研究问题主要把握精髓,不要被这些表面的区别搞昏了头。 5))))层模式的优点层模式的优点层模式的优点层模式的优点::::  层模式可以分离系统不同方面的考虑,这样就减少了系统的耦合和依赖,提高了内聚性, 增加了潜在的重用性,并且增加了系统设计上的清晰度。  封装和分解了相关的复杂性。  一些层的实现可以被新的实现替代,一般来说,技术服务层或者基础层这些比较低层的 层不能替换,而表示层、应用层和领域层可能进行替换。  较低的层包含了可重用的功能。  一些层可能是分布式的(主要是领域层和技术服务层)。  由于逻辑上划分比较清楚,有助于多个小组开发。 六六六六、、、、从软件复用与从软件复用与从软件复用与从软件复用与构件构件构件构件化化化化的的的的视角优化架构视角优化架构视角优化架构视角优化架构 事实上,经过从上面三个方面审视架构,我们已经建立了一个完整的而且比较良好的架构。 但我们还需要从第四个方面在更高的层次审视我们的架构,需要考虑的又一个问题就是软件的复 用。复用可以大大降低后期成本,提高整个软件系统的可升级性与可维护性。我们可以考虑哪些 结构可以使用已经存在的可复用结构和产品,某些结构可以利用 GoF 的设计模式设计可复用的 构件已备后期使用。还需要根据需求分析得出的易变点仔细设计产品结构,确保后期的变化不至 于对产品带来太大的影响。而复用的一个重要的手段,就是面向构件的方法。 1,,,,软件复用软件复用软件复用软件复用 软件复用是指重复使用“为了复用目的而设计的软件”的过程。在过去的开发实践中,我们 也可能会重复使用“并非为了复用目的而设计的软件”的过程,或者在一个应用系统的不同版本 间重复使用代码的过程,这两类行为都不属于严格意义上的软件复用。 通过软件复用,在应用系统开发中可以充分地利用已有的开发成果,消除了包括分析、设计、 编码、测试等在内的许多重复劳动,从而提高了软件开发的效率,同时,通过复用高质量的已有 开发成果,避免了重新开发可能引入的错误,从而提高了软件的质量。在基于复用的软件开发中, 为复用而开发的软件架构本身就可以作为一种大粒度的、抽象级别较高的软件构件进行复用,而 且软件架构还为构件的组装提供了基础和上下文,对于成功的复用具有非常重要的意义。 软件架构研究如何快速、可靠地用可复用构件构造系统的方式,着眼于软件系统自身的整体 结构和构件间的互联。其中主要包括:软件架构原理和风格,软件架构的描述和规范,特定领域 软件架构,构件如何向软件构架的集成机制等。 2,,,,面向构件的方法简述面向构件的方法简述面向构件的方法简述面向构件的方法简述 构件也称为组件,面向构件的方法包含了许多关键理论,这些关键理论解决了当今许多备受 挑剔的软件问题,这些理论包括:  构件基础设施  软件模式  软件架构  基于构件的软件开发 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 52 - 构件可以理解为面向对象软件技术的一种变体,它有四点原则区别于其它思想,封装、多态、 后期绑定、安全。从这个角度来说,它和面向对象是类似的。不过它取消了对于继承的强调。 在面向构件的思想里,认为继承是个紧耦合的、白盒的关系,它对大多数打包和复用来说都 是不合适的。构件通过直接调用其它对象或构件来实现功能的复用,而不是使用继承来实现它, 事实上,在我们后面的讨论中,也会提到面向对象的方法中还是要优先使用组合而不是继承,但 在构件方法中则完全摒弃了继承而是调用,在构件术语里,这些调用称作“代理”(delegation )。 实现构件技术关键是需要一个规范,这个规范应该定义封装标准,或者说是构件设计的公共 结构。理想状态这个规范应该是在行业以至全球范围内的标准,这样构建就可以在系统、企业乃 至整个软件行业中被广泛复用。构件利用组装来创建系统,在组装的过程中,可以把多个构件结 合在一起创建一个比较大的实体,如果构件之间能够匹配用户的请求和服务的规范,它们就能进 行交互而不需要额外的代码。 3,,,,面向面向面向面向构件的软件模式构件的软件模式构件的软件模式构件的软件模式 面向构件技术的特色在于:迅速、灵活、简洁,面向构件技术之于软件业的意义正如由生产 流水线之于工业制造,是软件业发展的必然趋势。软件业发展到今天,已经不是那种个人花费一 段时间即可完成的小软件。软件越来越复杂,时间越来越短,软件代码也从几百行到现在的上百 万行。把这些代码分解成一些构件完成,可以减少软件系统中的变化因子。 1))))面向构件方法模式面向构件方法模式面向构件方法模式面向构件方法模式 面向构件技术的思想基础在软件复用,技术基础是根据软件复用思想设计的众多构件。面向 构件将软件系统开发的重心移向如何把应用系统分解成稳定、灵活、可重用的构件和如何利用已 有构件库组装出随需而变的应用软件。 基于面向构件的架构可以描述为:系统=框架+构件+组建。框架是所有构件的支撑框架;每个 构件实现系统的每个具体功能;组建,可以视为构件的插入顺序,不同构件的组成顺序不同,其 实现的整体功能也就不同。 面向构件技术将把软件开发分成几种:框架开发设计、构件开发设计、组装,如果用现代的 工业生产做比喻,框架设计就是基本的生产机器的开发研究,构件开发就是零件的生产,组装就 是把零件组装成汽车、飞机等等各种产品。 2))))面向构件开发的不足之处面向构件开发的不足之处面向构件开发的不足之处面向构件开发的不足之处 (1)系统资源耗费 从软件性能角度看,用面向构件技术开发的软件并不是最佳的。除了有比较大的代码冗余外, 因为它的灵活性在很大程度上是以空间和时间等为代价实现的。 (2)面向构件开发的风险 从细节来看,构件将构件的实现细节完全封装,如果没有好的文档支持,有可能导致构件的 使用结果不是使用者预期的。比如,构件使用者对某构件的出错机制认识不够 3))))开放式系统技术开放式系统技术开放式系统技术开放式系统技术 专用软件是由单个供应商生产的不符合统一标准的产品。这些单个供应商通过版本更换来控 制软件的形式与功能。但是谁这系统越来越复杂,当一个系统建立起来以后,往往更倾向于依赖 于通用的商业软件,这种依赖往往成为内部软件复用的非常有效的形式。正是这种状态,我们需 要讨论一下开放式系统技术这个问题。 商业软件成为复用的有效形式,主要原因是规模经济的作用,通用的商业软件的质量,往往 超过终端用户自主开发能力。 商业软件也可以在某个领域内实现专有,也就是提供应用程序接口(API)为应用软件提供 服务。当软件不断升级的过程中,这些接口的复杂性可能超出用户的需要,这就需要有复杂性的 控制。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 53 - 一个解决方案就是实现开放式系统技术,开放式系统技术与专有技术有根本的不同。在开放 式系统技术中,由供应商组织来开发独立于专有实现的规范,所有的供应商实现的接口都严格的 一致,并且规定了统一的术语,这样就可以是软件的生命周期得以延长,这种开放式系统技术特 别适合于面向对象的方法。 为了使商业技术能适应各种应用需求,对软件开发和安装就有一定的要求,这种要求称之为 配置(profiling ),适当的配置软件的嵌入也称为开放是软件的一个特点。 从架构的角度来看,不少系统结构具有大量的一对一接口和复杂的相互连接关系,这种模型 被称之为“烟囱”模型,当系统规模增大以后,这种关系会以平方律的速度增加,复杂性的增加 会带来相当多的问题,尤其是升级和修改越来越难以进行,而系统的可扩展性恰恰是开发成本的 重要部分。 为此,我们可以建立一个称之为对象管理器的层,用于统一协调各个对象的沟通,从可维护 性角度这是一个比较好的结构,但是在某些特别强调效率的点可以避开它。系统的架构的一个重 要原则就是对软件接口定义一个已经规划的定义,为系统集成方案提供更高的统一性,软件的子 系统部分要由应用程序接口来定义。这样,就削弱了模块之间的依赖性,这样的系统就比较容易 扩展和维护,并且支持大规模的集成。 经过从四个方面审视我们的架构,经过分析、权衡、设计和部分编码,我们就可以得到一个 稳固的架构。这个架构经过经过评审,就可以作为后期开发的基础架构。 2.7 软件架构设计的流程软件架构设计的流程软件架构设计的流程软件架构设计的流程 综上所述,我们就可以比较条理化的建立软件架构设计的流程了。典型软件架构设计的流程 如下图所示。 一一一一、、、、业务架构概念业务架构概念业务架构概念业务架构概念 在构建软件架构之前,架构师需要仔细研究如下几个问题:  系统是为什么目的而构建的?  系统投运后服务于哪些利益相关者的利益?  什么角色在什么时候操作或者维护系统?  业务系统实现方法是怎样的?  整个业务系统是如何依靠系统而运转的? ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 54 - 为了回答这些问题,需要仔细阅读需求分析文档中的业务模型建立、问题域及其解决构思、 产品模型的构思等等前期文档,站在系统架构的角度,全面清晰的建立业务模型,包括组织结构 关系、业务功能、业务流程、业务信息交互方法、业务地理分布、业务规则和约束条件。这个阶 段的主要活动如下:  建立产品范围、目的、最终用户、业务背景等重要的初始信息。  建立完整的业务和系统的术语字典,确保项目相关人员理解上保持一致。  建立宏观层面业务的总体概念,明确总体流程、业务功能的边界、交互与协作方式,建 立系统的概念模型。  汇总业务总的组织结构与协作职能关系。  分析业务的组成节点,以及节点间交互、协同与信息的依赖关系。  分析业务节点的事件、消息,以及由此引发的状态转换关系。  汇总业务运行的基本数据模型,以便于跟踪信息的流动与格式转换。分析业务数据的关 联关系。  理解问题域以及系统需要解决的问题。  分析业务运作层面的基本业务规则与约束条件。 这个阶段的活动非常重要,架构师只有具备了这些相互关联的业务概念以后,才可能从这些 概念中抽取恰当的架构因素。 二二二二、、、、产品架构概念产品架构概念产品架构概念产品架构概念 在理解业务的基础上,我们需要进一步思考产品架构的概念,这个阶段从活动的层面看实际 上与建立业务架构概念是一样的,但是思维的重心转移到如下几个方面:  新系统投入运行以后,最高层面的业务会怎样运作?  新系统是如何解决原来工作系统的问题的?  新系统的投运,会对原来的组织结构划分发生什么样的影响?  由于新系统取代了原来的一些业务职能,业务节点的分布会发生怎样的变化?变化后的 节点间的信息又是怎样交换与依赖的?  变化后的业务事件传递又会发生怎样的变化?  新的系统加入以后,哪些业务流程将会发生重大变化?哪些不会发生变化?  业务的状态转换关系将会如何随着新系统地加入而改变?  业务的数据模型将会如何随着业务流程的变化而变化的?  新系统地加入,将会如何影响新的业务规则和约束? 从这些角度出发,我们会重新构建未来新系统投运以后的业务规则,相应的新规则也需要建 立,这就实现了业务过程的重构。 三三三三、、、、建立稳定的架构基线建立稳定的架构基线建立稳定的架构基线建立稳定的架构基线 在对业务领域与问题域有深刻的理解以后,我们需要继续研究如下一些问题:  这个复杂系统应该分成多少和哪些子系统?  子系统是如何分布在不同的业务节点或者物理节点上的?  这些分散的子系统将提供哪些接口?这些接口如何进行交互?  各个子系统需要交互哪些数据?  每个单独的子系统,所需要实现的功能有哪些?  整个系统对各个子系统有哪些功能、性能和质量上的要求? ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 55 - “基线”这个词有两个意义:  这个阶段将会对整体架构策略做出重大的设计上的决策。一旦作出了这些决策,后续开 发没有重大情况不允许变动。  这个阶段完成的工作,本身就是架构阶段的重要成果,需要广泛认同、集体遵守以及具 备强制的约束力。 尽管在后期的演变中,这样的基线实际上还会不断的精化和优化,但最初下功夫构建稳定的 架构基线是十分必要的。这个阶段的活动如下:  校验与确认前期所有的业务架构与产品架构的信息,必要的时候补充相应的信息。  修订和增补术语字典,确保所有的相关人员对术语有相同的认知。  把整个系统功能进行拆分,并且分解到不同的运行节点上,构建不同的系统集和子系统, 在全局范围内划分接口与交互规则。  汇总系统/子系统接口信息,便于检索与浏览。  规划整个系统的通信链路、通信路径、通信网络等传输媒介。  把产品架构概念中的业务职能与系统功能相对应,从而确保满足业务要求。  分析系统/子系统在运行起动态协作需要交互的信息。  构建和模拟整个系统在业务环境下的动态特性,规划全系统内部状态变化过程、触发的 事件及约束条件。  详细汇总各个子系统间信息传递的过程、内容以及其它辅助信息。  根据初始的数据模型构建数据物理模型。  汇总质量上对系统的要求,并把这些质量要求细化分解,量化到各个子系统中去。  构建整个系统与子系统的构建和演化计划,在迭代过程中构建整体项目规划和初始的迭 代规划。  按固定时段预测技术的演化,汇总整个系统的应用技术及其演化。  分辨与汇总整个系统在不同阶段必须遵循的标准。  把业务约束映射到各个子系统,必要时附加 IT 业务约束。 四四四四、、、、子系统架构的设计与实现子系统架构的设计与实现子系统架构的设计与实现子系统架构的设计与实现 通过上述各主要过程,我们已经实现了一个重要的总体架构基线。所有的子系统设计都是在 这个庞大的架构基线约束下展开,至此,首席架构师逐渐淡出,让位于子系统架构设计师。子系 统架构设计师的任务是继续分解、细化、设计各个子系统。在这个阶段,将会考虑更加细节的问 题,为后来的构件设计与单元设计作准备,我们需要考虑的问题如下:  规划给该子系统的功能是否可行?  在整个子系统的范围内,又能分解成什么子功能集?  在整个子系统的范围内,又能把哪些子功能合并到某些构件中去?  这些构件与子功能集是如何通过接口与子系统衔接的? 事实上子系统架构设计本身就是一个完整的系统设计,所区别的是视野集中到子系统的范围 内,这个阶段的活动如下:  校验与确认前期与该子系统相关的业务架构与产品架构的信息,必要的时候补充相应的 信息。  增补与本子系统相关的术语字典,确保所有的相关人员对术语有相同的认知。  把整个子系统功能进行拆分,并且分解到不同的构件节点上,构建不同的子功能集,在 子系统范围内划分接口与交互规则。  汇总子系统/构件接口信息,便于检索与浏览。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 56 -  规划整个子系统的通信链路、通信路径、通信网络等传输媒介。  把产品架构概念中的子业务职能与构件功能相对应,从而确保满足业务要求。  分析子系统/构件在运行起动态协作需要交互的信息。  构建和模拟整个子系统在业务环境下的动态特性,规划子系统内部状态变化过程、触发 的事件及约束条件。  详细汇总各个构件间信息传递的过程、内容以及其它辅助信息。  根据初始的数据模型构建子系统相关的更详细的数据物理模型。  根据质量上对子系统的要求,并把这些质量要求细化分解,量化到各个构件中去。  构建整子系统的构建和演化计划,在迭代过程中构建子系统项目规划和更详细地的迭代 规划。  按固定时段预测技术的演化,汇总子系统的应用技术及其演化。  分辨与汇总子系统在不同阶段必须遵循的标准。  把业务约束映射到各个构件,必要时附加 IT 业务约束。 五五五五、、、、构件与实现单元的设计构件与实现单元的设计构件与实现单元的设计构件与实现单元的设计 进入构件设计阶段也就是进入了详细设计阶段。这个阶段主要的工作就是接口与功能设计。 在迭代模型下,这个阶段很大程度上是在迭代过程中完成的,由某个设计人员带领全体开发团队 进行分析和设计。 在这个过程中,我们应该考虑在小粒度架构中如何使产品需求变更不至于对产品质量造成影 响,还需要考虑业务概念模型与产品功能块有相应的追溯和回溯关系。这些问题,我们将会在后 面用适当的篇幅进行讨论。 小结小结小结小结:::: 大型复杂项目的成功依赖于合理的项目组织,这种组织概念包括人力资源的组织和产品架构 的组织两个方面。敏捷项目管理为这两种合理的组织提供了思维基础,为项目的成功提供了保证。 一个典型的例子是 20 世纪 90 年代加拿大空中交通系统项目(首席架构师:Philippe Kruchten )。 150 个程序员被组织到 6 个月的长周期迭代中。但是,即使在 6 个月的迭代中,10~20 人的子 系统,仍然把任务分解成一连串 6 个为期一个月的小迭代。正是这个项目的实践成功,Philippe Kruchten 才成为 RUP 的首倡者。 敏捷过程的提出直接影响到架构设计的核心思维,正是因为敏捷过程的提出,才有了架构驱 动、弹性架构和骨架系统这些理念。也直接影响到需求分析方法、项目规划和估计方法等一系列 领域的新思维。甚至推动了业务敏捷以及与之相适应的基于服务的架构的提出。这些观念的提出 又更加推动了软件工程学向更高的层次发展。 下面我们将讨论几个专题,但讨论的时候希望研究一种思考问题的方法,从而为大家解决更 广阔的问题提供一个思维的平台。这些专题并不是独立存在,而是融合在本章所讨论的各个阶段 之中。再一次强调,方法和技术是会变化的,但优秀的思维方式是永恒的! ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 57 - 第三章 应对质量属性的架构策略 架构设计的本质,架构设计的本质,架构设计的本质,架构设计的本质,是应对质量属性的解决方案,从而形成相应的架构策略。是应对质量属性的解决方案,从而形成相应的架构策略。是应对质量属性的解决方案,从而形成相应的架构策略。是应对质量属性的解决方案,从而形成相应的架构策略。 在上一章关于架构分析的讨论中,我们简要地谈到了架构设计思维重心,很大程度应该放在 对于质量需求的应对策略上。例如,随着需求变更越来越频繁越来越不可避免,在质量需求中就 提出可维护性的 要求,对这种要求的强烈程度,往往就决定了某一种架构设计的特征,而这个 特征表现在对所付出代价的权衡。为了对软件质量进行度量,必须对影响软件质量的要素进行度 量,并建立实用的软件质量体系或模型,在需求分析中,必须对软件质量需求提出可度量的模型, 这是一个好的架构设计最重要的基础。 构建一个好的系统架构,终极目标是使它具备形态之美,体系中每个部分相互配合,共同达 到系统功能的完美实现。但是,系统架构师最需要考虑的问题,是这样的架构能不能达到系统的 质量要求,这就是系统的设计品质而且这种品质又应该是可度量的。我们需要在如下两个点上考 虑问题:  如何构建一个过程来考虑和完善系统的质量属性。  对于不同的质量需求,我们应该采取什么策略来满足要求。 下面我们分别加以讨论。 3.1 应应应应对质量属性的架构对质量属性的架构对质量属性的架构对质量属性的架构设计设计设计设计过程过程过程过程 在上一章我们已经构建了软件架构设计总体过程,为了强调针对质量属性的架构设计策略, 我们还应该在每个节点加入如下子过程。 一一一一、、、、以核心功能为主进行架构设计以核心功能为主进行架构设计以核心功能为主进行架构设计以核心功能为主进行架构设计 对于任何一个基础架构,我们都需要回答如下一些问题:  这个系统有哪些核心功能?  这些核心功能分布在哪些系统级的框架之内?  是什么样的质量要求才使我们这样分配功能分布?  这些系统功能互相交互的时候考虑了哪些质量要求? 事实上,我们总是倾向于使用一些我们熟悉的架构,但是通用的架构很难让我们回答上述问 题。为了确保设计的清晰,在我们已经存在一些熟悉的架构的基础上,需要按照下属步骤来考虑 问题:  清晰的界定核心功能,例如工作流、业务处理单元等。  清晰的界定那些为了实现这些核心功能而必须提供的基础框架,例如通信、安全、并发 等框架。  清晰的界定那些功能与功能,以及功能与框架之间的交互关系。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 58 -  仔细考虑系统质量要求,根据质量要求合理分配核心功能的分布、基础框架的分布,是 核心功能的协作与交互能够满足系统质量要求。 利用这些步骤,我们就可能在一定程度上,把一个系统架构构建成核心功能与基础框架规划 清楚,特别是把核心功能与其他功能的交互关系,或者与基础框架的交互关系更加清楚的表达出 来,这样也有利于进一步重构的体系。 二二二二、、、、以质量属性为依据进行重构和优化以质量属性为依据进行重构和优化以质量属性为依据进行重构和优化以质量属性为依据进行重构和优化 通过上面这个步骤,实现了所谓的架构战略性的设计,但这还不够。一个好的架构总是经历 从粗到精、逐步分解、逐步稳定的过程。仅仅从核心功能上划分并不一定能达到所要求的质量标 准,我们还需要根据质量需求对架构重新拆分、合并、功能再分配等,这就重新构造了新的体系 结构。例如,在以核心功能为主进行架构设计的时候,我们得到如下图所示的基本框架。 但是这个框架具有很大的危险性,因为各个功能块之间有很强的缠绕性,系统的可维护性、 可实现性都很差。这样的系统从功能上可能已经达了要求,但是,它既无法维护,也无法扩展, 当客户流量比较大的时候,性能可能也是个问题。为此,我们需要更加细致地从战术上考虑问题, 仔细对整个架构进行重构和优化,整个优化过程可以依据两个方向:  自底向上,逐步合并各个功能,清理一些设计不合理的构造快,保证以一条清晰的核心 功能为主线,这样我们才可以从目前的架构设计中,清晰的表达先前的所谓战略设计是 什么。  自上而下,把系统的质量要求逐步分解,考虑这些分解出来的质量要求,选择最佳的解 决方案,在适当的地方添加这些内容,确保系统既满足核心功能的需求,又满足了分解 的质量要求。 这样两个方向反复思考和重构的结果,保证系统既有一条清晰的功能主线,更能满足质量要 求,而且更着重以质量为主线考虑问题。 三三三三、、、、增量式的完善架构设计增量式的完善架构设计增量式的完善架构设计增量式的完善架构设计 一次性的完成高水平架构几乎没有可能性,因此,增量式迭代的完善架构几乎是一个普遍采 用的方法。通过迭代,我们可以通过多次的提炼、重构和优化来使架构达到要求。我们建议采取 如下步骤来实现:  把功能需求与质量需求按优先级排序。  估算完成每个需求和质量需求在架构阶段需要完成的时间。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 59 -  决定迭代周期。  决定迭代次数。  把排定的功能需求与质量需求分配给每个迭代周期。  实施每次架构增量,以系统核心功能为主要线索完成任务。  重构和优化,以质量属性为核心优化架构。 四四四四、、、、以测试驱动架构设计以测试驱动架构设计以测试驱动架构设计以测试驱动架构设计 测试驱动的设计是保证设计质量的良好方法,我们可以通过下面的步骤来完成它。  首先,在进行每一次架构的增量、提炼与重构之前,需要进行初步的测试准备工作,也 就是明确如何进行质量的测试。在系统重构完成之后,根据已有的系统核心功能,接口 分布、协作以及系统质量要求,由测试人员帮助建立测试规约。  其次,所建立的测试规约,会被后来的设计、编码人员参考和执行。架构和设计人员必 须保证后续人员完全遵循这些测试规约,也就是说架构人员还附带有监督职能。  在编码过程中的测试,将会发现许多初期没有考虑到的设计缺陷,架构人员需要根据这 些测试报告中检测出来的缺陷,重构自己的架构。 所以,测试驱动的开发是迭代过程中优化架构的重要手段。上述三个步骤第一项是最困难也 是最重要的,因为系统的质量完全在于你如何测量它。这就要求需求分析过程中,对于非功能性 需求一定要定义测试条件,而测试条件的定义并不容易。 下面,我们进一步讨论对于不同的质量需求,应该采取什么样的策略来满足这些需求。注意, 这些讨论会有一些实例,它们都来自于国外公开发表的资料,主要是为了表达问题方便,并不牵 涉到任何具体的设计。 3.2 质量度量模型与质量属性场景质量度量模型与质量属性场景质量度量模型与质量属性场景质量度量模型与质量属性场景 一一一一、、、、三层次软件质量度量模型三层次软件质量度量模型三层次软件质量度量模型三层次软件质量度量模型 软件的质量由一系列质量要素组成,每一个质量要素又由一些衡量标准组成,每个衡量标准 又由一些量度标准加以定量刻画。质量度量贯穿于软件工程的全过程以及软件交付之后,在软件 交付之前的度量(内部属性)主要包括程序复杂性、模块的有效性和总的程序规模。在软件交付 之后的度量(外部属性)则主要包括残存的缺陷数和系统的可维护性方面。 ISO 9126 称为“软件产品评价:质量特性及使用指南”。在这个标准中,把软件质量定义为 “与软件产品满足声明的或隐含的需求能力有关的特性和特性的总和”,可以分为 6 个特性,即: 功能性、可靠性、效率、可使用性、可维护性以及可移植性。这 6 大特性,每个特性包括一系列 副特性,其中各质量特性和质量子特性的含义如下: 特性特性特性特性 副特性副特性副特性副特性 1 功能性功能性功能性功能性(functionality)(functionality)(functionality)(functionality):与一组功能 及其指定的性质的存在有关的一组属 性。功能是指满足规定或隐含需求的那 些功能。 适合性适合性适合性适合性(suitability(suitability(suitability(suitability):与对规定任务能否提供一组功能 以及这组功能是否适合有相关的软件属性。 准确性准确性准确性准确性(accurateness)(accurateness)(accurateness)(accurateness):与能够得到正确或相符的结果或 效果有关的软件属性。 互用性互用性互用性互用性(interoperability)(interoperability)(interoperability)(interoperability):与同其他指定系统进行交互 操作的能力相关的软件属性。 依从性依从性依从性依从性(compl(compl(compl(compli ance)i ance)i ance)i ance):使软件服从有关的标准、约定、 法规及类似规定的软件属性。 安全性安全性安全性安全性(security)(security)(security)(security):与避免对程序及数据的非授权故意或 意外访问的能力有关的软件属性。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 60 - 2 可靠性可靠性可靠性可靠性(reliability)(reliability)(reliability)(reliability):与在规定的一 段时间内和和规定的条件下,软件维持 其性能的有关能力。 成熟性成熟性成熟性成熟性(maturity)(maturity)(maturity)(maturity):与由软件故障引起实效的频度有关的 软件属性。 容错性容错性容错性容错性(fauIt tolerance)(fauIt tolerance)(fauIt tolerance)(fauIt tolerance):与在软件错误或违反指定接 口的情况下,维持指定的性能水平的能力有关的软件属 性。 易恢复性易恢复性易恢复性易恢复性(recoverability)(recoverability)(recoverability)(recoverability):与在故障发生后,重新建立 其性能水平并恢复直接受影响数据的能力,以及为达到此 目的所需的时间和努力有关的软件属性。 3 易使用性易使用性易使用性易使用性(usability)(usability)(usability)(usability):与为使用所需 的努力和由一组规定或隐含的用户对 这样所作的个别评价有关的一组属性。 易理解性易理解性易理解性易理解性(understandability)(understandability)(understandability)(understandability):与用户为理解逻辑概念 及其应用所付出的劳动有关的软件属性。 易学性易学性易学性易学性(1earnability)(1earnability)(1earnability)(1earnability):与用户为学习其应用(例如操作 控制、输入、输出)所付出的努力相关的软件属性。 易操作性易操作性易操作性易操作性(Operability)(Operability)(Operability)(Operability):与用户为进行操作和操作控制 所付出的努力有关的软件属性。 4 效率效率效率效率(efficiency)(efficiency)(efficiency)(efficiency):在规定条件下,软 件的性能水平与所用资源量之间的关 系有软件属性 资源特性资源特性资源特性资源特性(resource behavior)(resource behavior)(resource behavior)(resource behavior):与软件执行其功能时, 所使用的资源量以及使用资源的持续时间有关的软件属 性。 5 可维护性可维护性可维护性可维护性 (maintaina bility)(maintaina bility)(maintaina bility)(maintaina bility):与进行规定的修 改所需要的努力有关的一组属性。 易分析性易分析性易分析性易分析性(analyzability)(analyzability)(analyzability)(analyzability):与为诊断缺陷、失效原因或 判定待修改的部分所需努力有关的软件属性。 易改变性易改变性易改变性易改变性(changeability)(changeability)(changeability)(changeability):与进行修改、排错或适应环 境变换所需努力有关的软件属性。 稳定性稳定性稳定性稳定性(sta(sta(sta(sta、、、、bility)bility)bility)bility):与修改造成未预料效果的风险有 关的软件属性。 易测试性易测试性易测试性易测试性(testability)(testability)(testability)(testability):为确认经修改软件所需努力有 关的软件属性。 6 可移植性可移植性可移植性可移植性(portability)(portability)(portability)(portability):与软件可从 某一缓建转移到另一环境的能力有关 的一属性。 适应性适应性适应性适应性(adaptability)(adaptability)(adaptability)(adaptability):与一软件无需采用有别于为关软 件准备的处理或手段就能适应不同的规定环境有关的软 件属性。 易安装性易安装性易安装性易安装性(installability)(installability)(installability)(installability):与在指定环境下安装软件所 需努力有关的软件属性。 一致性一致性一致性一致性(conf(conf(conf(conformance)ormance)ormance)ormance):使软件服从与可移植性有关的标 准或约定的软件属性。 易替换性易替换性易替换性易替换性(replaceability)(replaceability)(replaceability)(replaceability):与一软件在该软件环境中用 来替代指定的其他软件的 其软件质量模型包括 3 层,即高层:软件质量需求评价准则(SQRC);中层:软件质量设计评 价准则(SQDC);低层:软件质量度量评价准则(SQMC),也就是:质量要素 (factor) 、评价准则 (criteria) 、度量(metric) 。 ISO 认为这 6 个特性是全面的,也就是说软件质量的任何一部分都可以用这 6 个特性中的一 个,或者多个特性的某些方面来描述。这 6 个特性中的每一个都定义为“一组与软件相关方面有 关的属性”,并且能细化为多级子特性。例如: ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 61 - 把软件的质量度量引申到架构设计,就衍生出了架构的质量属性和其实现的问题。我们可以 通过下面的检查表来描述和构造自己的质量度量模型。 软件架构的质量属性软件架构的质量属性软件架构的质量属性软件架构的质量属性 编编编编 号号号号 名称名称名称名称 内容内容内容内容 1 性能性能性能性能 每个用例的预期响应时间是多少。 平均/最慢/最快的预期响应时间是多少。 需要使用哪些资源(CPU, 局域网等)。 需要消耗多少资源。 使用什么样的资源分配策略。 预期的并行进程有多少个。 有没有特别耗时的计算过程。 服务器是单线程还是多线程。 有没有多个线程同时访问共享资源的问题,如果有,如何来管理。 不好的性能会在多大程度上影响易用性。 响应时间是同步的还是异步的。 系统在一天、一周或者一个月,系统性能变化是怎样的。 预期系统负载增长是怎样的。 2 可用性可用性可用性可用性 系统故障有多大的影响。 如何识别是硬件故障还是软件故障。 系统发生故障后,能多快恢复。 在故障情况下,有没有备用系统可以接管。 如何才能知道,所有的关键功能已经被复制了呢。 如何进行备份,备份和恢复系统需要多长时间。 预期的正常工作时间是多少小时。 每个月预期的正常工作时间是多少。 3 可靠性可靠性可靠性可靠性 软件或者硬件故障的影响是什么。 软件性能不好会影响可靠性吗。 不可靠的性能对业务有多大影响。 数据完整性会受到影响吗。 4 功能功能功能功能 系统满足用户提出的所有功能需求了吗。 系统如何应付和适应非预期的需求。 5 易用性易用性易用性易用性 用户界面容易理解吗。 界面需要满足残疾人的需求吗。 开发人员觉得用来开发的工具是易用的和易理解的吗。 6 可移植性可移植性可移植性可移植性 如果使用专用开发平台的话,用它的优点真的比缺点多吗。 建立一个独立层次的开销值得吗。 系统的可移植性应该在哪一级别来提供呢(应用程序、应用服务器、操作 系统还是硬件级别)。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 62 - 7 可重用性可重用性可重用性可重用性 该系统是一系列的产品线的开始吗。 其它建造的系统有多少和现有系统有关呢?如果有,其他系统能重用吗。 哪些现有构件是可以重用的。 现有的框架和其它代码能够被重用吗。 其它应用程序可以使用这个系统的基础设施吗。 建立可重用的构件,代价、风险、好处是什么。 8 集成性集成性集成性集成性 于其它系统进行通信的技术是基于现行的标准吗。 构件的接口是一致的和容易理解的吗。 有解释构件接口的过程吗。 9 可测试性可测试性可测试性可测试性 有可以测试语言类、构件和服务的工具、过程和技术吗。 框架中有可以进行单元测试的接口吗。 有自动测试工具可以用吗。 系统可以在测试器中运行吗。 10 可分解性可分解性可分解性可分解性 系统是模块化的吗。 系统之间有序多依赖关系吗。 对一个模块的修改会影响其它模块吗。 11 概念完整性概念完整性概念完整性概念完整性 人们理解这个架构吗。是不是有人问很多很基本的问题呢。 架构中有没有自相矛盾的决策。 新的需求很容易加到架构中来吗。 12 可完成性可完成性可完成性可完成性 有足够的时间、金钱和资源来建立架构基准和整个项目吗。 架构是不是太复杂。 架构足够的模块化来支持并行开发吗。 是不是有太多的技术风险呢。 上述问题,给我们提供了一个线索,在回答这些问题的时候,就可以利用它建立具体软件的 架构质量标准。同时也给我们初始设计的改进方向,提供了一个一目了然的依据。这些内容,可 以成为我们制定评估表格的基础。还要注意,在高层架构设计中,初步的验证性实验或者说原型 项目是完全必要的,这种概念和方法的验证可以极大的规避风险,因为如果初期的架构思想出现 了本质性的缺陷,对整个设计来说可能就是灾难性的。 当我们建立了架构的质量属性以后,就需要对每一个质量属性进行进一步的分解和研究,从 而找到针对具体质量属性的解决方案,以此综合出架构策略,这种对具体质量属性的细化描述, 我们称之为质量属性的场景。 二二二二、、、、软件架构质量属性的场景软件架构质量属性的场景软件架构质量属性的场景软件架构质量属性的场景 软件的质量属性决定了架构风格,因此在上述整体质量属性研究的基础之上,我们必须把问 题集中一下,在架构设计的过程中,对于某一个具体的质量属性,我们应该如何来描述它呢?描 述质量属性的方法是很多的,我们可以根据三层次质量度量模型,构造出更切合实际的架构设计 质量属性场景来。针对具体质量属性的需求,我们定义的质量属性场景由以下六个部分组成。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 63 - 注意:于不同的系统,对于质量的关注点是不一样的,例如某个设计强调了高性能,另一个 设计可能更强调高可靠性,针对这些质量属性就可以形成基本设计重点,我们称之为“解决方案”, 解决方案是一种影响质量属性的设计决策,把各种解决方案打包集合,我们称之为“架构策略”。 下面我们简要介绍一下几个最重要的质量属性的解决方案。 下面我们通过几个设计中最为关注的可靠性、可维护性以及可集成性问题,来深入的讨论这 个问题。 3.3 可靠性质量解决方案可靠性质量解决方案可靠性质量解决方案可靠性质量解决方案 如果一个项目把可靠性放在了十分重要的位置,那么开发这样的系统就必须花力气研究可靠 性解决方案。关于可靠性是这样表述的,当系统不再提供与其规范一致的服务的时候,错误就发 生了。错误或其组合,可能会导致故障的发生。恰当的解决方案可以阻止错误发展为故障,至少 能够把错误的影响限制在一定的范围之内。 在软件开发中实施可靠性是很昂贵的,因此对可靠性问题必须谨慎,我们必须制定可靠性计 划,借此能够解决具体风险,并降低成本,可靠性问题必须兼顾投资回报的影响,并却反馈到软 件项目计划中去,可靠性计划大概需要研究如下内容并作出决策。 可靠性计划可靠性计划可靠性计划可靠性计划 序号序号序号序号 可靠性问题可靠性问题可靠性问题可靠性问题 解决方案解决方案解决方案解决方案 1 产品可靠性需求 2 错误预测途径 定义功能描述 定义错误 错误失效分类计划 客户可靠性需求 研究折衷方案 确定产品可靠性目标 3 错误预防途径 在设计构件中分配可靠性 满足可靠性目标的工程过程 基于功能描述的资源计划 错误引入和传播管理计划 软件可靠性度量计划 4 错误排除途径 定义操作描述 可靠性增长测试计划 测试过程跟踪计划 附加测试计划 可靠性目标证明过程 5 容错途径 监督现场可靠性目标 跟踪客户对可靠性的满意度 通过监督可靠性来安排新特性的引入 通过可靠性措施引导产品和过程改进 一一一一、、、、可靠性质量属性场景可靠性质量属性场景可靠性质量属性场景可靠性质量属性场景 针对可靠性所关注的问题,我们需要仔细研究:如何检测系统发生的故障,系统故障发生的 频度,出现故障的时候会有什么情况,允许系统有多长时间非正常运行,什么时候可以安全的出 现故障,如何防止故障的发生,以及发生故障的时候需要哪种通知等。 在这个问题上,我们需要把故障和错误区分开来,一般来说,系统用户可能会观察到故障, 但不能观察到错误(异常),当用户可以看到错误的时候,实际上错误已经发展到了故障。错误 是可以实现自动修复的,如果在用户没有观察到的情况下实现了错误修复,则就没有发生故障。 系统的可靠性一般定义成系统正常运行的比率: ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 64 - α=平均正常工作时间/(平均正常工作时间+平均修复时间) 注意,在计算可靠性的时候,一般不计算预定的停机时间,即使用户在这个时间可能得不到 服务,但由于停机是预定的,所以可以不予考虑,可靠性的一般场景如下,我们可以根据具体情 况来设计具体的场景。 可靠性的一般场景可靠性的一般场景可靠性的一般场景可靠性的一般场景 序号序号序号序号 场景场景场景场景 可能的值可能的值可能的值可能的值 1 源 系统内部,系统外部 2 刺激 错误: 疏忽:构件未能对某个输入作响应。 崩溃:构件不断遭受疏忽的错误。 时间:构件做出了响应,但响应时间太早或者太迟。 响应:构件用一个不正确的值作响应。 3 制品 指定系统可靠性的资源:处理器,通信通道,进程,永久存储。 4 环境 当出现错误或者故障的时候,系统状态的期望值。比如:如果看到了 一系列的错误,可能希望关掉系统。如果看到了第一个错误,可能希 望只是对功能的降级。 5 响应 系统出现故障的时候,应该具备的反应,比如: 记录故障,通知适当的各方,禁止导致故障的事件源,在一段时间内 不可用,在降级模式下运行等等。 6 响应度量 系统必须可用的时间间隔,可用时间,系统在降级模式下运行的时间 间隔,修复时间等。 为了解决已定义的可靠性问题,需要有可靠性解决方案,例如,当发生错误的时候,可靠性 解决方案的目标就是屏蔽错误或者进行修改。 维持可靠性的方法都包括某种类型的冗余,以及用来检测故障的某种类型的健康监视,以及 当检测到故障的时候某种类型的恢复。有些情况下,监测和恢复是自动进行的,有些情况下是是 手动进行的。这种模块或者设备的冗余加上健康监测,往往成为高可靠性系统的特征。换句话说 高可靠性的产品都是昂贵的。 问题在于是不是使用了这些技术,设备的可靠性就一定提高了呢?事实证明并非如此,我们 必须利用历史测量的数据进行数据处理,要对模块的易损性以及发生的位置等要素进行统计检 验,从而找到最需要进行可靠性架构设计的位置,力争在比较少的成本下获取比较大的可靠性指 标,这往往是架构决策的基础。 二二二二、、、、健康监测健康监测健康监测健康监测 事实上无论代码写的多么优秀,各种问题考虑得多么全面,但系统发生故障的可能性还是存 在的,作为模块或者设备的冗余配置,恰当的健康监测是判断模块是否工作正常的基础架构。 1,,,,命令命令命令命令/响应响应响应响应((((ping/echo )))): 一个构件发出一个命令,并希望预定时间内收到一个审查构件的响应。和心跳方式相比,它 的特点是发出检测命令是由专门的构件完成的。这个解决方案一般用在处理共同完成某项任务的 一组构件内。一般情况下,“探测器”可以放在底层,它向相关软件进程发出命令,而高层命令 发声器向底层探测器发出测量命令。在模块设计的时候,必须包括对于健康探测器探测命令的响 应信息,这种响应应该力求对于各个模块是统一的,同时也要尽可能的简单。命令/响应机制一 般以方法访问的方式工作,这在某些情况下可能是方便的。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 65 - 健康监测可以远程进行,但更多的情况是在本地进行,和远程探测相比这种方式所需要的网 络带宽比较少。 2,,,,心跳心跳心跳心跳((((dead man ))))计时器计时器计时器计时器:::: “心跳”是一种主动检测方案,由被测构件主动发出一个自检结果信息(心跳),由另一个 构件收听这个信息,如果心跳失败,则可以判断这个构件失灵,并通知按照可靠性策略纠正错误 构件。心跳也可以同时传递数据,比如传递上一次交易的日志。心跳一般以事件机制工作,统一 的定时器并不一定是必须的,但如果所有的备测模块按统一时间工作,给可靠性处理策略将会带 来好处。 上述两种情况,被测模块都可以处于不同的进程中。 3,,,,异常异常异常异常:::: 异常处理是一种最常用的程序内处理方案,当出现异常的时候,异常处理程序将会在同一个 进程中处理相关问题。 三三三三、、、、错误恢复错误恢复错误恢复错误恢复 高可靠性的系统往往伴随着系统冗余,必然带来了造价提高和系统复杂性提高,但在航空设 备、航空管制或者战场指挥这样要求高的但数据相对比较简单的系统,使用恰当的系统冗余是合 适的,但对于大量的长时间数据处理这样的问题,就需要作一些限制。 1,,,,表决表决表决表决 对于复杂的数据处理计算,例如快速傅里叶变换或者卷积、相关函数的计算、航路计算、及 威胁计算以及应对策略计算等等,如果处理上出现了问题(错误),往往会带来灾难性的后果, 在这种情况下,可以使用表决方案。它的特点是运行在冗余处理器上的每个过程都具有相同的输 入,表决器采取某种策略取出数据,常用的是多数法,或者首选法。 极端情况是冗余构件是由不同小组开发,并且是在不同的平台上运行,当然这样开发和维护 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 66 - 是很昂贵的,仅用在要求非常高的环境中,比如作战指挥和分析系统、飞行器的控制等。 2,,,,主动冗余主动冗余主动冗余主动冗余((((热重启热重启热重启热重启)))) 所有的构件都以并行方式对事件做出响应,但只有第一个构件的响应被使用,而其他的响应 被丢弃。比如在数据库系统中,这种情况使切换时间只有几毫秒,但资源消耗却成倍增加。 3,,,,被动冗余被动冗余被动冗余被动冗余((((暖重启暖重启暖重启暖重启/双冗余双冗余双冗余双冗余/三冗余三冗余三冗余三冗余)))) 由一个主要构件对事件做出响应,并通知其它备用构件进行状态更新,一旦错误发生,将自 动切换到备用构件上,在此之前,系统必须保证备用状态是新的。切换的时机可以由备用构件决 定,也可以由其它构件决定。该解决方案依赖于可靠的接管,有时候周期性切换可以提高可靠性。 例如,作为一个典型的例子,在准备产品化应用程序的时候,如何知道数据库是不是能够经 受众多用户对应用程序反复的使用呢?如果数据库服务器关机,会出现什么情况呢?如果数据库 服务器需要快速重启,会出现什么情况呢? 首先,在停止和重启服务器的情况下,我们可以清除连接池并重新建立它。但如何才能保证 结果和服务器关闭之前完全相同呢?这就要用到容错恢复技术了。 请看下面的场景: 使用三台数据库服务器,“主服务器”、“镜像服务器”、“观察者服务器”,使用数据库 镜像的时候,客户只和主服务器联系,镜像服务器处于数据恢复状态(不能进行任何访问),当 向主服务器提交一个事务的时候,也会把这个事物发给镜像服务器。 观察者服务器只是观看主服务器和镜像服务器是不是正在正常工作。 在主服务器关机的时候,观察者自动把镜像服务器切换为主服务器,见下面两张图。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 67 - 注意,当发现主服务器有问题的时候,用户方需要自动清除连接池,并转而使用备用服务器。 4,,,,备件备件备件备件 备件是在计算机系统中配置了用于更换不同故障的构件。在出现故障的时候,必须转入适当 的软件配置,然后重启计算机。这个方法必须记录持久设备的状态变化,以使接入的设备能以适 当的状态工作。有一些重新引入模块的修复解决方案,包括 shadow 操作、状态再同步以及回滚。 5,,,,shadow 操作操作操作操作 以前出现故障的构件,可以短时间内以“shadow 模式”运行,以确保在恢复该构件前,模 仿工作构件的行为。 6,,,,状态再同步状态再同步状态再同步状态再同步 不论是主动还是被动的冗余解决方案,都要求所恢复的构件在重新提供服务前更新其状态更 新的方法取决于可承受的停机时间、更新规模以及更新所要求的消息数量。如果可能的话,最好 用一条消息包含它的状态。 7,,,,检查点检查点检查点检查点/回滚回滚回滚回滚 检查点就是记录已创建的状态,一般可以定期进行,也可以对某种消息进行响应。如果故障 是以不同寻常的方式发生的,可以用上一个检查点拍了快照以后所发生的事务日志来恢复系统。 四四四四、、、、错误预防错误预防错误预防错误预防 1,,,,从服务中删除从服务中删除从服务中删除从服务中删除 这个解决方案是从操作中删除系统的一个构件,以支持某些活动来防止预期发生的故障。比 如在周期性切换的被动冗余方案,可以重新启动构件,以防止内存泄漏导致系统故障发生。 如果从服务中删除是自动的,就需要以架构策略来支持它。如果是手动的,就需要对系统进 行设计以支持这个动作。 2,,,,事务事务事务事务 事务可以保证多步数据处理的一致性。 实现完全孤立的事务当然好,但代价太高,完全孤立性要求只有在锁定事务的情况下,才能 读写任何数据,甚至锁定将要读取的数据。根据应用程序的目的,可能并不需要实现完全的孤立 性,通过调整事务的孤立级别,就可以减少使用锁定的次数,并提高可测量性和性能。 3,,,,进程监视器进程监视器进程监视器进程监视器 一旦检测到进程中存在错误,进程监视器就可以删除非执行进程,并为该进程创建一个新的 实例,再把它初始化一个适当的状态。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 68 - 3.4 基于高可靠性的架构设计基于高可靠性的架构设计基于高可靠性的架构设计基于高可靠性的架构设计 假定某一个大型系统的设计提出了极高的可靠性要求,因此在架构设计的时候,就需要针对 可靠性问题讨论具体的解决方案。 一一一一、、、、进程间提升可靠性的方法进程间提升可靠性的方法进程间提升可靠性的方法进程间提升可靠性的方法 大型系统一般是按照多处理器环境设计的,逻辑上组成处理器组,处理器组的目的是运行一 个或者多个应用程序的副本,这一思想对于支持容错性和可靠性是非常重要的。在多个运行副本 中,一个为主,称为主地址空间(PAS),其它的为辅,称为备用地址空间(SAS)。 一个主地址空间,和相应的备用地址空间的集合称为操作单元,某个操作单元完全驻留在同 一处理器组的处理器中,一个处理器组最多可以包含 4 个处理器。 没有用这种容错方式实现的系统称为功能组,功能组可以根据需要出现在各个处理器上。 应用程序根据可靠性的需要,可以是操作单元,也可以是功能组(FG)。 操作单元的接管过程如下: 操作单元的 PAS 代表整个操作单元接收消息并做出响应,然后 PAS 对自己的状态和相应的 SAS 的状态进行更新(可能会向 SAS 发送多条消息)。 如果 PAS 出现了故障将按如下步骤完成接替工作: 1,把一个 SAS 提升为新的 PAS。 2,为 PAS 重新设置该操作单元以及客户机的关系(实际上是操作单中的一个固定的列表), 在这个过程中,PAS 要向各个客户机发送消息,内容为:原来提供服务的操作单元发生故障,正 在等待 PAS 发送消息吗?如果是,新的 PAS 就处理收到的任何服务请求。 3,启动一个新的 SAS,代替原有的 PAS。 4,新启动的 SAS 把自己的存在告知于新的 PAS,新的 PAS 就会向这个 SAS 发送消息,使 它保持最新的状态。 如果在 SAS 内部发生错误,就需要在另外的处理器上启动新的 SAS,新的 SAS 要与 PAS 协调工作,并接受状态信息。 仔细研究这样的需求和动作,就可以设计出合适的架构来。 如果需要添加一个新的操作单元,需要采用如下步骤进行设计: 1,确定必要的输入数据及所在的位置。 2,确定哪些操作单元需要用到这个新的操作单元的输出数据。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 69 - 3,以一种非循环方式把这个操作单元的通信模式加入到整个系统中,以避免死锁。 4,设计消息,实现所期望的数据流。 5,确定内部状态数据(包括从 PAS 到 SAS 的状态数据)。 6,把状态数据划分为能够很好的适应网络要求的消息。 7,定义必须用到的消息类型。 8,规划 PAS 失败的时候的切换,要对更新数据作合理的规划,保证能够完全的反映出各种 状态。 9,保证切换发生的时候数据的一致性。 10 ,保证需要完成的处理步骤,能在一次系统“心跳”的时间内完成。 11 ,规划和其它操作单元的数据共享和数据锁定协议。 有经验的项目组成员可以按照这个步骤开展工作,为了加速开发,可以构造一个“代码模板”, 以加快开发速度和减少错误。 无论是客户端还是服务方,都可以通过这个方法提升模块的可靠性。 二二二二、、、、保证可靠性的分层结构保证可靠性的分层结构保证可靠性的分层结构保证可靠性的分层结构 对于可靠性极高的系统,由于不能系统出现故障的时候冷启动,而是需要尽可能快地切换到 备用构件上,这就出现了新的架构层次结构,称之为容错层次结构,该结构描述了如何检错、如 何隔离、如何恢复等,它主要用于捕获应用程序交互的错误并从中恢复。 容错层次结构包括“本地可靠性管理器”和驻留在系统管理控制台上的“全局可靠性管理器”, 它们之间要实现通信,以及报告当前状态和接受控制命令,它通过内部时间同步器把本处理器的 时钟和其它处理器的时钟同步。 该系统的容错层次结构提供了多种不同层次的错误检测和恢复机制,在每个层次上都能异步 的进行如下工作: 1,检测自身、同级实体和底层实体的错误。 2,处理来自底层的例外情况。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 70 - 3,诊断、恢复、报告或者提交例外情况。 高可靠性系统要比普通系统复杂的多也昂贵的多,但是在例如飞机交通管制系统、指挥控制 系统等这些需要高可靠性的场合,这样的设计是值得的,实际效果也是非常好的。即使普通的系 统,在恰当的节点上恰当的应用高可靠性解决方案,有的时候也是必要的。 3.5 可维护性解决方案可维护性解决方案可维护性解决方案可维护性解决方案 可维护性直接影响到可扩展、可修改性能。由于需求变更在设计中的不可避免性,加之迭代 开发使用需求变更为驱动力,更重要的是系统升级是必然的,所以,可维护性是我们架构布局需 要十分注意的问题。 一一一一、、、、可维护性质量属性场景可维护性质量属性场景可维护性质量属性场景可维护性质量属性场景 可维护性关注的是有关变更的成本问题,它提出两个关注点: 1,,,,可以修改什么可以修改什么可以修改什么可以修改什么((((制品制品制品制品)?)?)?)? 可以修改系统的任何方面,包括系统的功能、平台、环境、质量属性以及其容量。应该注意 到,泛泛的按照可维护性策略设计,往往系统的开发成本可能会超出所能接受的范围,因为一个 可维护性很好的系统,其开发成本就会比较高,系统性能也可能比较低。所以,必须在需求阶段 很好的定义变更预期,没有这个定义,设计就会非常盲目的。 2,,,,何时进行变更和由谁进行变更何时进行变更和由谁进行变更何时进行变更和由谁进行变更何时进行变更和由谁进行变更((((环境环境环境环境)?)?)?)? 过去常见的就是修改源代码,但这样的做法一般是不合理的。现在提出的问题不仅仅是何时 变更的问题,也要提出由谁进行变更的问题,不同的变更方案,可以由开发人员、最终用户或者 系统管理员来进行,这就需要有相应的设计策略。 可维护性的一般场景如下。 可维护性的一般场景可维护性的一般场景可维护性的一般场景可维护性的一般场景 序号序号序号序号 场景场景场景场景 可能的值可能的值可能的值可能的值 1 源 开发人员、最终用户、系统管理员 2 刺激 希望增加/删除/修改/改变功能、质量属性、容量 3 制品 系统用户界面、平台、环境或者与目标系统交互的系统 4 环境 运行时、编译时、构建时、设计时 5 响应 查找架构中需要修改的位置,进行修改且不会影响其它功能,对所做 的修改进行测试,部署所做的修改。 6 响应度量 根据所影响的元素度量:成本、努力、资金。 该修改对于其它功能或者质量所造成影响的程度。 可维护性解决方案,目的是发生变更的时候,直接影响的模块数量最少,因此这个解决方案 也可以称作“局部化修改”解决方案。另一个设计目标,就是在局部化修改的时候,不要引起“连 锁反应”。第三个设计目标,是控制部署时间的成本,我们称之为“延迟绑定时间”。下面我们对 这三个解决方案进行讨论。 二二二二、、、、局部化修改局部化修改局部化修改局部化修改 一般来说,如果把修改限制在一小组模块之内,最后就可能会降低成本特别是维护成本。这 就需要在设计期为模块分配好责任,把预期的变更限制在一定的范围之内,可以采用的方案如下。 1,,,,维持语义的一致性维持语义的一致性维持语义的一致性维持语义的一致性 语义的一致性是指模块中的责任能够协调一致的工作,不需要过多的依赖其它模块。也就是 设计模块的时候,必须确保内聚度指标。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 71 - 虽然耦合性和内聚度指标可以在一定程度上度量语义一致性,但设计的时候还需要仔细考虑 预期的变更,也就是根据一组预期的变更来度量语义一致性。其中的一个解决方案就是“抽象通 用服务”。通过一个通用服务模块可以支持可重用性,但“抽象通用服务”可支持可维护性,而 且这种修改不会影响其它用户。所以,考虑问题的时候不仅仅要研究语义一致性,也要研究防止 连锁反应。 2,,,,预期期望的变更预期期望的变更预期期望的变更预期期望的变更 考虑所预期的变更的集合,可以给我们提供特定的责任分配方法,我们需要回答下面的问题: “对于每次变更,系统的分解结构是不是限定了完成变更所需要修改的模块集合?” 与此相关的问题是: “根本不同的变更,会影响相同的模块吗?” 从某种意义上说,这个方案是维持语义一致性对于变更的进一步深入。尽管变更是很难预期 的,但是如果在系统分析的时候能够深入研究这个问题,则对于设计质量的提高是非常有意义的。 3,,,,泛化该模块泛化该模块泛化该模块泛化该模块 这里的泛化与 UML 中的泛化并不完全是一层意思,这里所说的是,模块设计的越通用,发 生变更对模块影响就越小。 4,,,,限制可能的选择限制可能的选择限制可能的选择限制可能的选择 当修改的范围很大的时候,可能会影响很多模块,限制各个模块可能的选择,会降低这些修 改所造成的影响,这就需要在设计的时候对模块功能和它的可变范围进行限制。 三三三三、、、、防止连锁反应防止连锁反应防止连锁反应防止连锁反应 连锁反应指的是对一个模块的修改,需要更改修改并没有直接影响到的模块。在设计的时候 必须仔细研究这个问题,这是模块分割的一个重要依据,我们来思考下面几个情况。设两个模块 A 和 B,我们从如下几个方面讨论模块间的依赖性: ((((1))))语法或语义语法或语义语法或语义语法或语义 假定要使 B 正确编译(或执行),必须使用 A 产生的数据类型(或语义),而且必须保证两 者数据类型(或语义)一致,这就发生了语法或者语义的依赖性。 ((((2))))顺序顺序顺序顺序 数据顺序:要使 B 正确执行,必须按一个固定顺序接受由 A 产生的数据。 控制顺序:要使 B 正确执行,A 必须在一定的时间限制内执行(比如,在 B 执行前,A 执 行的时间不能超过 5 毫秒)。 这就发生了顺序上的依赖性。 ((((3))))A 的一个接口身份的一个接口身份的一个接口身份的一个接口身份 A 可以有多个接口,要使 B 正确编译和执行,这个接口的身份、名称或者句柄必须与 B 假 定的一致,这里接口的身份、名称或者句柄被称之为方法(或者函数)的签名。这就发生了接口 身份上的依赖性。 ((((4))))A 在运行时的位置在运行时的位置在运行时的位置在运行时的位置 要使 B 正确执行,A 运行时的位置必须与 B 假定的一致。这就发生了运行位置的一致性。 ((((5))))A 提供的服务提供的服务提供的服务提供的服务/数据的质量数据的质量数据的质量数据的质量 要使 B 正确执行,涉及 A 所提供的数据或者服务的质量,必须与 B 的假定一致,比如,某 个传感器提供的数据必须有一定的准确性,以使 B 的算法能够正常运行,这就发生了服务或者 数据质量上的依赖性。 ((((6))))A 必须存在必须存在必须存在必须存在 要使 B 正确执行,A 必须存在,例如,如果 B 请求对象 A 提供的服务,如果 A 不存在,则 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 72 - B 就不能正常执行,这就发生了存在上的一致性。 ((((7))))A 的资源行为的资源行为的资源行为的资源行为 要使 B 正确执行,A 的资源行为必须与 B 假定的一致。比如 A 必须使用与 B 相同的内存, 这就发生了资源行为的依赖性。 借助于对依赖性的理解,在设计得时候我们必须仔细考虑这些风险,把重要的位置中的风险 列出来,逐个考虑解决方案,特别是用于防止某些类型的连锁反应的解决方案。这样一来,设计 的质量就会比较高。 为了解决连锁反应的问题,我们可以采用如下一些策略。 1,,,,信息隐蔽信息隐蔽信息隐蔽信息隐蔽 信息隐藏就是把某个子系统的责任分解成更小的部分,并选择哪些信息是公有的,哪些是私 有的。可以通过指定的接口获得公有责任。信息隐藏的目的时把变更隔离在一个模块内,防止变 更扩散到其它的模块内,这是防止变更扩散最早的技术,由于它使用变更预期作为分解的基础, 到今天仍然是最重要的技术。 2,,,,维持现有接口维持现有接口维持现有接口维持现有接口 如果 B 依赖于 A 的一个接口的名字和签名,则应该维持这个接口不变(条件是 B 对 A 不应 该有语义依赖性)。在高层设计的时候,必须仔细设计接口,考虑全面以及充分利用预期变更的 信息,确保接口的稳定性。 实现这个解决方案的模式包括。  添加接口添加接口添加接口添加接口::::大多数编程语言都允许多个接口,当添加新的服务或者数据的时候,从而使 现有的接口保持不变并提供相同的签名。  添加适配器添加适配器添加适配器添加适配器::::给 A 添加一个适配器,该适配器把 A 包装起来,并提供 A 原来的签名。  提供一个占位程序提供一个占位程序提供一个占位程序提供一个占位程序 A::::如果程序修改要求删除 A,但 B 依靠于 A 的签名,那么,可以 设计一个占位程序,虚拟的提供 A 的接口和行为,当然这也是为了可修改性付出的代 价。 3,,,,限制通信路径限制通信路径限制通信路径限制通信路径 限制与模块共享数据的模块,也就是说,减少使用由给定模块所产生的数据的模块数量,就 会减少连锁反应。在实时性要求很高的场合,可以把重要的数据在本地建立映像,只在发生更改 的时候才改变这个本地数据集的数据,这样就可以限制通信的数量。 四四四四、、、、推迟绑定时间推迟绑定时间推迟绑定时间推迟绑定时间 后期绑定和允许非开发人员进行修改,可以使系统的可维护性大大提升,推迟绑定时间还能 够使最终用户或者系统管理员进行设置,或者提供影响行为的输入。 后期绑定需要系统的设计上做好准备,解决方案包括:  运行时注册运行时注册运行时注册运行时注册::::即支持即插即用操作。  配置文件配置文件配置文件配置文件::::目的是启动时设置参数。  多态多态多态多态::::允许方法调用的后期绑定。  构件更换构件更换构件更换构件更换::::允许载入时绑定。  遵守已定义的协议遵守已定义的协议遵守已定义的协议遵守已定义的协议::::允许独立进程的运行时绑定。 3.6 基于高可维护性的架构设计基于高可维护性的架构设计基于高可维护性的架构设计基于高可维护性的架构设计 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 73 - 如果一个系统强调可维护性,设计中就会有若干特点。首先各个分系统应该具备极高的独立 性,分系统与主干系统之间的数据信息与控制信息应该仔细设计,确保信息比较少而且稳定。主 干系统的任务是调度,并不真正处于每个分系统的控制环路之中,而主要是作为各个分系统的一 个集中处理和应对策略的分析控制中心,这样的布局有利于保证各个分系统的独立性和稳定性, 也可以保证在发生损坏的时候,各个分系统仍然能够独立工作,这也是一种典型的集散系统方案。 如果分系统的升级频度很高,这样的设计也就强调了可维护性。 一一一一、、、、问题的陈述问题的陈述问题的陈述问题的陈述 主干系统的输入主要用来获取和管理各个独立分系统的数据。 而该主干系统的输出包括各种显示设备,数据的集中处理结果,以及向各个分系统发出相应 的指令。系统要求具备计算和处理能力。 二二二二、、、、高层结构高层结构高层结构高层结构 在模块设计的时候,主要考虑的问题是信息隐藏。很多分系统在主干系统的生命周期内可能 会用新的型号或新原理组成的系统更换,这就需要把与这个系统交互的细节封装到某个模块中 去,这个模块的接口只不过是一个抽象分系统。每个模块都提供了一组仅通过某个特定的访问过 程与其它模块交互,如果换成了新式的分系统,这需要改变这个模块中的某些细节,对软件其它 部分没有影响。 在模块划分的时候遵循了如下原则:  每个模块的结构尽可能简单。  使用不不需要知道其它模块的细节。  对设计修改的容易程度与发生修改的频度有合理的对应关系。  把软件系统作的比较大的修改,可分解成各个模块的一组独立的修改,程序员不需要相 互交流,新老版本的组合、测试不应该有困难。 对于可维护性质量指标的实现,采用如下解决方案。 目标目标目标目标 如何实现如何实现如何实现如何实现 易于改变:分系统、平台、符号、输入 信息隐藏。 了解未来可能的改动 正式的评审过程,借助相关领域专家的经验。 为各开发小组分配任务,使各小组之间 交流很少。 按层次结构组织各模块,每个开发小组负责开发一个二级模 块及其所有子模块。 由于易扩展性摆在了重要的位置,所以模块划分以隐藏模块为基础,以此得到对于这个系统 来说很特殊的模块划分。 1,,,,硬件隐藏模块硬件隐藏模块硬件隐藏模块硬件隐藏模块 这个模块的作用,是保证当硬件的任何一部分被替换的时候,需要修改的仅仅是这个过程, 也就是说这个模块实现的虚拟分系统。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 74 - 2,,,,行为隐藏模块行为隐藏模块行为隐藏模块行为隐藏模块 行为隐藏模块包括当影响系统行为的需求发生变化的时候,可以修改的过程,这个模块主要 实现系统功能性需求的隐藏,这个过程也包括了应该发送给硬件隐藏模块提供的虚拟输出设备的 数据。 3,,,,软件决策模块软件决策模块软件决策模块软件决策模块 软件决策模块把基于数学定律、物理事实、编程思想(什么样的算法速度更快?)的软件设 计决策隐藏起来,该模块隐藏的信息,在需求文档中是没有记录的,模块对外的接口是由设计人 员决定的,所以这种模块明显区别于其它模块。  应用数据类型模块应用数据类型模块应用数据类型模块应用数据类型模块:主要是提供适合各个系统的数据类型,比如距离、时间长度、角度 等,这主要是实现数据类型的转换。它隐藏的是数据转换的方法。  数据专家模块数据专家模块数据专家模块数据专家模块:大多数数据都由专门的模块产生,并保留在本地,它从数据生产过程获 得数据值,集中提供相应系统工作状态的所有数据。数据专家是一个“中间人”,它控 制数据更新的频度和来源,并且只在数据发生变化的才更新。  过滤器行为模块过滤器行为模块过滤器行为模块过滤器行为模块:提供过滤器的数字化模型,用以对噪声等进行过滤。  物理模型模块物理模型模块物理模型模块物理模型模块:不直接测量,但对其它的测量数据进行综合计算得到的估计,这是对这 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 75 - 些模型计算的隐藏。  软件实用程序模块软件实用程序模块软件实用程序模块软件实用程序模块:包括多个程序员编写的实用函数、资源监视程序等。  系统生成模块系统生成模块系统生成模块系统生成模块:隐藏的是直到系统生成的时候才做的决策,这些决策包括系统生成参数 的确定,以及对某一模块不同实现方式的选择。 这样的设计,在分系统不断更新的情况下,能够很容易的升级和维护。这种分解对于设计文 档的编写、联机配置文件的编制、测试计划的制定、编程小组的组织、评审过程的安排、项目进 展的确定等都产生了重要的影响。 3.7 基于高可集成性的架构设计基于高可集成性的架构设计基于高可集成性的架构设计基于高可集成性的架构设计 对可维护性的进一步强调,衍生出可集成性的质量需求,很多系统具有很强的分布性,而且 还必须时常升级和更新,这就对开发和维护这样的软件系统,提出了巨大的挑战。例如,我们希 望所设计的系统能够毫无困难的在各种不同的要求、不同的组合甚至不同的背景下都能够很好的 实现集成,而这种集成需要付出的代价有很小,这就把可集成性提高到系统设计的主要的位置上 来。 在软件系统质量属性中,可集成性的描述并不是总是被强调的,不过,在由分散小组或单独 的组织开发的大型系统,可集成性往往成为驱动因素。事实上一些可集成性的目的也是可维护性, 它的解决方案可以有如下一些:  使接口较小、简单、稳定;  遵守已定义的协议;  松散耦合使元素间的依赖较小;  使用构件框架;  使用已有版本的接口等。 下面我们通过一个假想的系统,研究一下在这些原则的指引下,得出的架构模式能否具有较 高的可集成性,而且能满足软件必须具备的其它质量属性。飞行模拟系统是当前最复杂的系统之 一,它具有很强的分布性,严格的时间要求,而且还必须时常升级和更新,这就对开发和维护这 样的软件系统,提出了巨大的挑战。 一一一一、、、、问题问题问题问题的陈述的陈述的陈述的陈述 飞行训练模拟系统有三种作用:第一,训练飞行人员和机组人员。第二,对环境的模拟,包 括空气、各种威胁、武器和其它飞机的影响等,第三,对教练的模拟,根据训练目的,提前下发 文字材料,教练可以实时设置环境,比如设备故障、暴风导致的湍流等。 事实上飞行环境模型可以达到任意的逼真程度,比如对于气压高度表而言的空气压力影响, ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 76 - 可以考虑上升气流和下降气流,当地天气模式,甚至附近飞机的存在也可能产生湍流。这样而言, 飞行模拟器就需要强大的计算能力,但这样逼真的模式是不是一定能提高飞行员技能,一直还是 存在争议的,因此,性能这个质量属性是影响架构特征的主要因素。 飞行模拟器运行涉及多种状态,其中包括:  运行状态运行状态运行状态运行状态:这是系统的正常操作状态。  配置状态配置状态配置状态配置状态:对于当前的训练科目发生变化时的状态。  停止状态停止状态停止状态停止状态:指停止当前的模拟。  重放状态重放状态重放状态重放状态:指没有机组人员干预的情况下,重放某次模拟。这种重放可以用于演示,也 可以用于研究自己的操作是否恰当。 飞行模拟系统具有以下 4 大特征: 1,,,,实时性要求高实时性要求高实时性要求高实时性要求高 为保证逼真,飞行模拟系统的执行必须保证非常高的帧频,比如“转弯”这个动作,所有的 仪表、景色以及座舱控制的动作都应该与实际情况一致,而且保证很好的协调。 2,,,,连连连连续的开发和修改续的开发和修改续的开发和修改续的开发和修改 不论是军用还是民用飞机,都处于不断的修改、更新的过程之中,因此,飞行模拟系统也是 与此同步不断的修改的。更重要的是,这种修改对于软件设计来说,完全是被动的、不可预知的。 3,,,,规模大规模大规模大规模大、、、、复杂程度高复杂程度高复杂程度高复杂程度高 随着飞机的性能越来越好,飞行模拟器的规模成指数增长的趋势。 4,,,,在分散的地理位置上开发在分散的地理位置上开发在分散的地理位置上开发在分散的地理位置上开发 军用飞机模拟系统一般以分布式的方式开发,造成这种情况至少有两个原因。一个是技术方 面的,也就是不同的部件需要相当不同的专业的知识。另一个是政治上的,这种大规模高科技的 产品,本来就是各方面争夺的对象。 由于通信链路的加长,本来就因为规模大而不易实现的可集成性,实现的难度更大了。 二二二二、、、、架构解决方案架构解决方案架构解决方案架构解决方案 飞行模拟系统的上述特征,迫使架构设计必须考虑以下两个问题:  强调架构的可集成和可修改性强调架构的可集成和可修改性强调架构的可集成和可修改性强调架构的可集成和可修改性:事实上系统测试、集成和修改的代价超过了开发成本, 所以架构设计应该非常强调可集成和可修改性。  软件结构不需要和飞机结构严格对应软件结构不需要和飞机结构严格对应软件结构不需要和飞机结构严格对应软件结构不需要和飞机结构严格对应:把运行时效率作为首要目标,因此软件结构不需 要和飞机结构严格对应,而是和动作相对应。比如作“转向”动作,实际的动作是踩方 向舵,摆动副翼,同时控制升降舵防止掉高。而在飞行模拟系统中,为了保证实时性, 可以构造一个“转向模块”,同样,其它的动作也有相应的模块,换句话说,模块的建 立是来自于对机组人员的操作进行考查,把任务的组件模型化。采用这种方案可以极大 的减少执行计算所需要的通信,但物理模块可能交叉出现在不同的模块中,造成集成的 困难。 下图给出了飞行模拟器的参考模型。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 77 - 对应于需求和质量的特征,可以采用以下三个构架策略: 1,,,,性能策略性能策略性能策略性能策略 这是一个关键质量目标,这主要通过时间调度策略来完成。管理程序所调用的每个子系统, 都有运行时间的限制,而且还确定了硬件规模,以保证容许子系统的这种时间要求。 实时性能要求控制循环分配给子系统的时间,要少于模拟系统的一个操作周期,架构设计中, 要保证每一个激励响应的流程合理(时间最短),单元测试中,响应时间要作为技术参数存在。 为保证实时性,代码或者模块的冗余是允许的。 2,,,,可集成性策略可集成性策略可集成性策略可集成性策略 由于强调可集成性要求,这里采用了所谓结构化模型,它有意识的把子系统之间数据连接和 控制连接应该控制在最小。 首先从构件级别来说,子系统同级构件不能直接传递数据和信息,任何数据和控制信息的传 递,都必须通过子控制器来完成。子系统控制器的数据在内部是一致的,要把另外的构件集成到 子系统,比新构件直接与其它构件通信要简单得多。也就是说,集成问题的规模不再与构件数量 成指数关系,而降低为线性关系。 其次从子系统级别来说,在集成两个子系统的时候,各子系统的构件都不能直接交互,所以 问题又简化了,只要保证两个子系统之间的数据传递一致性就可以了。添加新的子系统可能会影 响到其它子系统,但子系统的数量要比构件少,所以问题的复杂性不会太大。 所以,在结构化模型中,通过有意识的限制可能连接的数量,可集成问题得到了简化。这种 限制的代价,是子系统控制器经常成为各个构件公用的纯粹的数据通道,而且也提高了复杂性和 性能的开销。不过在实践中,这种方法的收益远远超过了成本,这些收益包括创建了一个能够进 行增量式开发和更轻松的集成的骨架系统。 3,,,,可维护性策略可维护性策略可维护性策略可维护性策略 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 78 - 对于结构化模型来说,设计人员和维护人员只需要理解少数几个基本构件配置就可以了。并 且由于功能的局部化,使得某次修改只涉及少数几个子系统控制器或者构件,就使可维护性得到 提高。下面针对可集成性,来考虑几个具体问题。 三三三三、、、、结构化模型的架构模式结构化模型的架构模式结构化模型的架构模式结构化模型的架构模式 针对可集成性的具有挑战性的问题,可以引入一种称之为“结构化模型”的架构模式,它突 出强调以下几个方面:  系统子结构的简单性和相似性。  把数据和控制信息的传递策略与运算分离开。  模块类型数量最少。  较少的系统级协调策略。  设计的透明性。 结构化模型的架构模式,包括一组元素和对元素运行时的协作配置。大型系统结构化模型可 能需要多个处理器运行,所以各个部分需要相互协调和通信,在粗粒度上,可以把结构化模型架 构样式分成两大部分:  管理部分管理部分管理部分管理部分:用以处理协调问题,包括子系统的实时调度、处理器之间的同步、从指挥控 制台上对事件进行管理、数据共享、数据完整性等等。  应用部分应用部分应用部分应用部分:处理各个武器系统的运算,对系统建模等。它由各个子系统和构件完成。 四四四四、、、、子系统管理部分的模块子系统管理部分的模块子系统管理部分的模块子系统管理部分的模块 下图给出了子系统管理部分的结构化模型,各构件的功能简述如下。 1,,,,时间同步器时间同步器时间同步器时间同步器 时间同步器是系统调度机制的基础,它负责维持模拟系统的内部时钟。它接受来自其他三个 部分的数据和控制信息,也负责和其它部分保持时间上的同步。 2,,,,周期时序器周期时序器周期时序器周期时序器 用于完成子系统所要做的所有周期性的处理,也包括按照固定的周期调用某些子系统。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 79 - 它向时间同步器提供两种操作: import :请求周期时序器调用子系统的 import 操作。 update :请求周期时序器调用子系统的 update 操作。 它具备组织“调度”信息和通过某个分发机制实现周期对某些子系统调用。 3,,,,事件处事件处事件处事件处理器理器理器理器 事件处理器用以协调子系统所做的所有非周期处理。它涉及 4 种操作: Configure :启动新的任务。 Condtituuent_event :某个实例针对某个模块的特定实例时使用。 Get_outbound_msg :时间同步器要在系统操作状态下执行非周期处理的时候使用。 Send :子系统控制器使用它向其它子系统控制器发送事件。 4,,,,代理代理代理代理 完成各个模型之间的系统级通讯。 五五五五、、、、子系统应用模块子系统应用模块子系统应用模块子系统应用模块 子系统应用部分只有两大部分,即子系统控制器和构件,如下图所示。 特点: 各个子系统控制器之间可以互相传递数据,但只能向其子构件传递数据。 构件只能与它的父构件传递数据(或者控制信息),但不能向其它任何构件传递数据(或者 反馈信息)。这个规则是防止把数据或者控制信息传递给同级的构件。 这些限制的基本思想,就是通过消除构件实例之间除父子关系之外的所有耦合情况,来简化 集成和维护工作,维护或者集成的影响,由上一级子系统来调解,这就是“限制通信”解决方案 的一个例子。 1,,,,子系统控制器子系统控制器子系统控制器子系统控制器 子系统控制器用来把相关子模块的一组功能联系起来,完成以下功能:  实现对子系统整体的模拟。  调解系统和子系统之间的控制和非周期通信。 因为结构化模型限制了控制器构件之间的通信,所以子系统控制器必须在他的构件和其它的 构件之间建立起逻辑上的联系。 为了建立相应的状态,并且在时间上与系统协调,它还具备两个功能:  为了解决数据一致性和时间连贯性的问题,检索入站连接的数据值,并把它保存在本地。  稳定构件的模拟算法,并报告这个子系统当前是不是稳定的。 此外,子系统控制器还需要支持训练任务参数的重新配置,它分别通过周期时序器和事件处 理器周期的和非周期的实现这个功能。 周期操作: ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 80 - update :周期性的接受所需要的数据源,并且向构件播送改变信息。 import :周期性的读入入站数据,并把它保存在本地,已备 update 使用。 非周期操作: process_event :要求子系统控制器对某个具体的事件做出反应。 configure :非周期的处理系统操作状态(如初始化),该操作用以建立一组命名的条件,如 某些设备的配置或任务等。 2,,,,控制器构件控制器构件控制器构件控制器构件 子系统控制器构件对外部系统来说,可能是对自身系统的模拟。构件之间的所有的逻辑交互 都由子系统控制器来完成。 六六六六、、、、系统设计中需要关注的问题系统设计中需要关注的问题系统设计中需要关注的问题系统设计中需要关注的问题 在系统设计进行模块切分的时候,需要关注以下几个问题。 1,,,,系统的骨架化系统的骨架化系统的骨架化系统的骨架化 对于一个庞大的系统,如果设计规格不加以控制,则会给将来的集成和维护带来极大的困难。 但在这个例子中,仅仅使用了 6 个模块类型(构件、子系统控制器、时间同步器、周期时序器、 事件处理器以及代理),就可以对这么大的系统进行完整的描述。这就使得架构很容易创建、理 解、集成、发展和修改。 更重要的是,如果采用一组标准模式,我们就可以创建一个骨架系统,为此创建出规格表、 代码模版和描述这些模式的示例程序。这样一来,就允许一致性分析。 架构师还可以坚持设计和开发人员仅仅使用所提供的构建快,这虽然听起来有些苛刻,但这 样一来,就可以把设计人员从系统总的功能实现的关注中解脱出来,构件的标准化必然带来可集 成性的提高。 2,,,,功能分配给构件的原则功能分配给构件的原则功能分配给构件的原则功能分配给构件的原则 把功能分配给构件的时候,需要考虑如下原则:  实际物理系统的各个部分应该与软件系统很好的对应,这为我们提供了真实世界的概念 模型。通过对各个分系统交互的理解,也可以帮助我们更好的理解软件各部分交互的方 式。这对于用户和评审也很有帮助。  要理解未来分系统更新换代的规律,比如整体换装设备需要做哪些变化?这种理解可以 帮助我们设计模块的范围,以使将来系统升级时的更改局部化。  努力降低系统接口的数量和规模,这来自于各部分更强的功能内聚,把最大的接口放在 各部分之内而不是各部分之间。 这里讨论的假想案例,旨在说明当系统对性能、可靠性与可修改性提出比较苛刻的要求的时 候,我们如何能合理设计架构,使项目能够在节约成本的情况下实现这些质量属性。成本的节约 可能表现在现场安装小组只有以前所要求的一半,因为他们可以更容易的查找和纠正问题。 设计方案通过以下方式实现了这些质量属性: 限制结构化模型架构模式中的模块类型配备的数量,限制模块类型之间的通信,根据飞机预 期变更的信息分解功能。从度量的角度,主要表现在现场测试描述(即测试问题)的大幅度减少, 开发人员还发现,采用这种方法更容易纠正问题。 3.8 软件产品线的应用软件产品线的应用软件产品线的应用软件产品线的应用 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 81 - 产品线系统产品线系统产品线系统产品线系统(Product Line System) 的定义为的定义为的定义为的定义为:一组软件密集型系统,它们共享一个公共的、 可管理的特性集,以规定的方式使用公共核心资产集开发,来满足某个特定市场或者任务的具体 需要。 产品线系统是一种产品开发的组织方式。产品线集中体现了软件复用思想。经验表明,单靠 技术方法并不能保证成功的产品线生产能力,经济、组织、管理和过程在建立和维护产品线中起 到了关键作用。一个产品线是共享一组共同设计及标准的产品族,从市场角度看是在某市场片断 中的一组相似的产品。建立产品线是根据生产的经济学,使产品族可复用构件能达到最大限度的 复用目的。产品线方法可以通过各种可复用软件构件,如需求分析、产品需求规格说明、架构设 计、代码构件、文档、测试策略和计划、测试案例和数据、开发人员的知识和技能、过程、方法 及工具等,支持最大限度的软件复用。产品线也是基于在相同产品价格条件下提高竞争力的商业 考虑。 换句话说,软件产品线的本质,是在生产软件产品的家族的时候,以一种规范的、策略性的 方式重用资产。所以,它的特点就是一组可重用的资产,它包括一个基本架构以及一些的可剪裁、 可填充的元素。此外,它还包括设计文档、用户手册、项目管理制品(包括预算和进度)以及测 试计划和测试用例。当成功建立产品线了以后,可以把可重用的资产保存在“核心资产库”中去, 由于可以使用到多个系统,可以明显的降低成本和缩短开发时间。 基于产品线的开发代表了软件工程的一个创新的、不断发展的概念,对于不同客户的需求, 如何使产品线的部件具有更大的灵活性,以简化这种针对不同的客户的创建,是研究产品线问题 值得注意的问题。 产品线中可重的要素很多,包括: 1,需求需求需求需求:大多数需求与早期开发的产品线系统需求是基本一致的,可以减少需求工程的工 作量。 2,架构设计架构设计架构设计架构设计:如果产品线的架构是集中了最聪明能干的工程师投入的大量时间,所确定的 架构就已经排除了系统的质量缺陷(性能、可靠性、可修改性等),因此新产品开发可以在相同 的架构上完成,而且可以确保质量。 3,元素元素元素元素:软件元素包括接口及其文档,测试计划和规程等,如果这些元素是适用的,新系 统的搭建就只是代码的具体实现和重用已有代码,要注意,最重要的可重用的元素集就是用户接 口,它代表了很多关键的设计决策。 4,建模和分析建模和分析建模和分析建模和分析:性能分析、可集成性分析、分布式系统问题(如证明没有死锁)、容错方 案以及网络负载策略都可以在产品中重用。在构建新系统的时候,可以认为这些问题都已经解决 了。 5,样本原型系统样本原型系统样本原型系统样本原型系统:一个产品线系统应该有一个高质量的演示原型以及关于性能、安全性、 保密性和可靠性的高质量工程设计原型。 软件产品线依赖于重用,但为什么到今天为止软件重用总是得不到承诺的那么多好处呢?关 键是元素集放什么和放多少的问题并没有真正得到解决。软件产品线事实上是确定一个策略,用 以对架构进行定义,确定功能,了解质量属性,仔细考虑只有构建的时候需要重用的元素才放到 重用库中去。过少则没有意义,过多则难以应用,产品线将依赖战略规划来发挥作用。产品线架 构设计的基本步骤如下。 一一一一、、、、确定范围确定范围确定范围确定范围 所谓确定范围,使定义哪些系统属于这个产品线,哪些不属于这个产品线。这个范围也代表 了组织对于可预测的将来,我们将会开发什么样的产品的最佳预测。如果范围过小,则达不到好 的投资回报,如果范围过大,则利用核心资产库开发单个产品的工作量就太大了。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 82 - 确定范围并不在于发现共性,而在于发现可以充分利用的共性,这才可以极大的降低系统构 建的成本。而且,在确定范围的时候,也不是仅仅考虑相似的产品,在不同的产品线上可能存在 共同的功能块,这也是需要关注的问题。 在核心资产库中,软件架构是重中之重,而一个可以在几乎所有产品线中不同产品可以通用 的架构,设计的关键是架构设计中有一组明确允许可以发生变化的,所以,识别允许的变化是架 构设计责任的一部分。架构设计必须研究清楚,什么是必须保持不变的,以及允许发生什么样的 变化和怎样应对这种变化,而这种变化往往是新系统带有重要特点的一部分。当然,架构本身是 属于不变的那一部分。 二二二二、、、、确定变化点确定变化点确定变化点确定变化点 由于产品可能会以很多方式发生变化,在产品线设计之初,我们必须确定在产品线的需求分 析中获取变化点。其中包括特性、平台、用户接口、质量属性以及目标市场等。其次,在产品线 的架构设计中,我们还可以获取其它的变化点,最后,在产品线的实现过程中,对变化点可能会 带来新的灵感。这是必要,因为某些决策只有在获取更多的信息之后才能确定。 三三三三、、、、支持变化点支持变化点支持变化点支持变化点 架构设计中支持变化点的方式很多,举例如下:  在我们面向对象的设计模式中,利用泛化和特化可以实现这种变化。它的特点是系统具 有相同的接口但具有不同的行为。  把扩展点构建到元素的实现中,也就是放在可以安全的添加额外的行为和功能的地方。  可以通过元素、子系统或子系统集合引入构建参数来完成,这里可能需要使用配置文件, 必要的时候可以使用反射。 对于保存在核心资产库中的架构,必须为它编写文档,重点是表达变化点和应用变化点的原 理,也应该描述架构实例化的过程,也就是如何应用变化点。文档中必须指出有效的和无效的应 用实例,以避免应用上的错误。产品线架构必须经过评估,以确保这个架构是有效的和应用安全 的。 3.9 基于产品线的架构设计基于产品线的架构设计基于产品线的架构设计基于产品线的架构设计 不少组织利用产品线架构取得了显著的经济效益,实现产品线的架构设计是一个系统工程, 并不是只要把历史上的元素保存下来就可以的,很重要的是我们的组织形式和开发方式要和产品 线方式向配合。 产品线系统已有成功的应用实例。典型的是美国空军电子系统中心(ESC) 和瑞典 CelsiusTech System 公司的产品线系统。 在 ESC 采用的产品线方法中,主要有五个关键性的组织,即外围的用户、SPO(系统项目 办公室),和产品线内部的系统构架组、产品线工程中心和产品线构件支持组。SPO 是直接和用 户打交道的组织,由它来决定是开发一个新系统还是从已有的系统升级。SPO 和用户都是产品 的客户,它们介入了整个开发阶段,在系统从原型演化到最终部署的过程中进行监控和认证。产 品线内部的三个关键性组织分别是系统构架组(SAG)、产品线工程中心(PLEC)、产品线构件支持 组(PLAS)。 CelsiusTech 系统公司是瑞典主要的指挥与控制系统供应商,下面我们以这个公司使用产品 线的例子,来讨论他们从最初的开发模式引入产品线的过程的有启发性的案例,对这个问题进行 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 83 - 分析。 一一一一、、、、开发产品线的动因开发产品线的动因开发产品线的动因开发产品线的动因 该公司最初的产品,是把具体的武器系统中模拟计算单元,以嵌入式数字单元代替,它采用 的是传统开发方法,但取得了很好的效果。但是后来,该公司同时接到了两份合同,也就是瑞典 海军和丹麦海军的舰船系统,两个系统都需要很强的容错性和分散性,比以往开发的系统要复杂 得多。 该公司早期的经验,是生产单个系统中的计算单元,但是以前开发单个系统的方法需要大量 的投资和人力,在当时的情况下就已经出现了不能保证进度费用超支的问题,面对这样大型的, 特别两个结构类似的大型综合系统的并行开发情况,对管理层和高层技术人员提出了严峻的挑 战。因此公司的管理者和高级技术人员经过认真研究,决定根本上改变开发模式,采用一种新的 产品族系列的开发方法,这就是 SS2000 产品线。 二二二二、、、、组织结构的变更组织结构的变更组织结构的变更组织结构的变更 开发方式和理念的改变,必须以组织结构的改变为基础。该公司原来的项目开发组织系统, 是以系统功能结构为基础的,以功能领域为基础的项目经理,自开始到系统集成都对整个阶段的 人力资源有使用权,基本上如下图: 在系统比较小的时候,这个组织也还是成功的,但还是发现了很多问题,比如为了减少各功 能领域的通信,把所分配的系统需求和接口写入文档,但往往接口的不兼容需要到系统集成的时 候才能发现,使得在职责分配上浪费了很多时间。系统的集成和安装也耗费了过多的时间。 对于一个大型的系统性的项目,这可能会带来了更多的问题。后来该公司调整了组织结构, 他的特点是构架组、产品线开发组和集成组的分离。首先,成立了一个强有力、小规模的、主要 注意技术问题的架构小组,用于设计和管理公司的产品线。在这个阶段,主要注意力放在了这个 架构小组,并直接向总经理汇报。 该架构小组主要对以下方面负有责任,并直接向总经理汇报:  产品线概念和原则的创建。  各层及对外接口的确定。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 84 -  接口的定义、完整性和受控演变。  系统功能在各层上的分配。  通用机制或者服务的确定。  诸如异常处理、进程间通信协议等通讯机制的确定、原型的建立与实施。  向项目开发人员传达产品线概念和原则。 该公司最初的架构是由两个高级工程师经过两个星期的研究以后提出来的,包括 125 个系统 功能和上面所提出的问题。后来随着产品的开发和升级,又扩充到 200 个功能,整个架构小组稳 定为 10 位高级工程师,到现在为止这个构架仍然是现有产品的基本框架。 关于集成和配置管理小组主要负责如下工作:  单元测试策略、测试计划和测试用例的开发。  所有测试的协调。  增量式构建计划的开发。  有效子系统的集成和发布。  开发和版本库的配置管理。  软件交付介质的制作(比如用户手册等)。 而产品线的开发小组,主要是子系统或者模块级的设计和开发。 也就是说,构架组负责产品线系统构架的定义和演化。产品线开发组负责根据产品线系统构 架,生产和管理可复用构件。集成组则根据具体客户的需求,利用产品线系统构架和可复用构件 进行具体的系统集成。 随着系统的成熟,各个组织的关注点也发生的变化,比如集成组的注意力逐步转入对若干同 时开发的系统集成和版本管理,更多地关心各个客户系统上对测试计划和数据集的重用。而产品 线开发小组也不再非常强调技术的成熟,而是把重点放在如何立业客户的业务过程和更改客户需 求之上。 三三三三、、、、架构解决方案架构解决方案架构解决方案架构解决方案 从架构的角度,我们首先考虑产品最重要的一些质量需求:  性能:系统必须能够对不断到达的传感器输入做响应,并且能控制所要求的分系统。  可修改性:对于计算平台、操作系统、添加和更换传感器和分系统、人机接口需求和通 信协议的变化,架构都需要具有健壮性。系统应该能不破坏架构和其它部分的情况下, 把现有模块更换成针对某个特定系统的模块。  安全性、可靠性和可用性。  可测试性:每个系统都必须是可集成和可测试的,以快速发现、隔离和纠正错误。 1,,,,进程视图进程视图进程视图进程视图 事实上,在分布式计算平台上,是把系统构建成一组通信进程,这个问题的研究需要使用进 程视图。进程视图的关注点是性能,我们还需要关注诸如:死锁、通信协议、容错性(防止通信 线路出现问题)、网络管理及防阻塞的考虑等,我们必须制定一些约定,这些约定不但是分布式 系统而且对于构件的开发都是必要的。比如我们提出的约定如下:  构件之间的通信是通过强类型消息传递的。抽象数据类型和实施操纵的程序是由传递消 息的构件提供的。它使得各个构件可以在不考虑其它构件对数据表示细节的情况下独立 编写。其实强类型也包括了具体的数据类型可以在使用方由特定的文件确定。  进程间通信协议支持本地独立应用程序之间的数据传输协议,这样就可以保证不考虑各 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 85 - 个应用程序所在的物理位置,这种处理器分配的“匿名性”,可以保证应用程序可以在 处理器之间迁移。 数据生产者不需要在了解数据使用者的情况下独立编写,数据的维护和更新从概念上是与数 据的使用者相分离的,也就是这是一个黑箱模式,数据的主要使用者是人机界面(HCI)。包括 存储库的构件称作通用对象管理器(Common Object Manager ,COOB),它的作用如下所示,数 据基本上应该通过 COOB 调整,但有时候为了性能,也不排除少数的绕过 COOB 的情况。比如 目标的轨迹信息(该目标的历史数据)更新频率特别快,所以它绕过了 COOB。 关于数据生产的约定包括:  各个数据应用模块都有本地的数据保留库(称之为实时数据库),仅当数据改动的时候 才发送数据去更新这样的“实时数据库”,这样可以保证不必要的消息进入网络。构件 拥有自己已更改的数据,这就避免了数据的争用。  数据以面向对象的抽象形式表现出来,以便把程序与实现的细节隔离开来,数据必须是 强类型的。  由于数据是分布式的,所以对访问请求的时间很短。  从较长的时间来看,数据在整个系统中是一致的,但允许短时间的不一致。 与网络相关的约定如下:  从设计上保证了网络负载比较小,这需要设计的时候对数据流作大量的工作,确保所传 递的数据都是必要的。  数据通道需要具有一定的防错性,尽可能在应用程序内部解决出现的错误。  允许应用程序偶尔错过某次更新,例如舰船位置,某次方位数据可能会错过,可以事后 根据前后数据进行更新,也就是需要有某种数据“记忆”功能。 这些约定,就可以保证模块在不考虑自己所不能控制的可变部分的情况下独立编写,使 模块更具通用型,而且模块的类型限制在比较少的数量,可以为不同系统直接使用。 2,,,,分层视图分层视图分层视图分层视图 该系统实现层的原则如下:  模块的分组大致是按照所封装的信息类型划分的。一般来说,当这种类型的信息形式发 生变化的时候,只改变这一个层的内容。比如,关于网络、通信协议发生变化的时候, 必须改动的模块,放在一个层中。  层排列的顺序,越是依赖于硬件的层放在底端,越是针对具体应用的层越放在上端。  对层的访问限制,模块只能访问同一层和下一层的模块。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 86 - 3,,,,模块分解视图模块分解视图模块分解视图模块分解视图 本系统由大约 30 个系统功能组组成,其中每个功能组又包含大约 20 个左右的系统功能。系 统功能组是围绕几个大的功能领域组织起来的,它们包括:  指挥、控制和通信。  武器控制。  系统内部通信和计算机环境接口的机制。  人机界面。 四四四四、、、、产品线架构的应用产品线架构的应用产品线架构的应用产品线架构的应用 为了实现规定的质量需求,该系统采用了各种相关解决方案。 编号编号编号编号 需求需求需求需求 实现方式实现方式实现方式实现方式 相关解决方案相关解决方案相关解决方案相关解决方案 1 性能 严格的网络通信协议; 把软件编写为一组进程,以使并发最大化; 把软件编写的独立于位置的,通过重新调整位置来调整 性能; 绕过 COOB 以实现高速数据交换; 仅在改变和分配的时候发送数据,以使响应时间最短。 引入并发 减少需求 多个副本 增加资源 2 可靠性、可 用 性 和 安 全性 冗余的 LAN;容错的软件;标准的异常协议; 把软件编写的独立于位置的,是能够在出现故障的时候 移植; 数据具有严格的所有权,以防止多人争抢改写数据库的 情况。 异常处理 主动冗余 状态再同步 事务 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 87 - 3 可 修 改 性 ( 包 括 产 生 新 的 成 员) 严格使用基于消息的通信,提供与实现细节隔离的接 口; 把软件编写的独立于位置的,分层提供了跨平台、网络 拓扑、网络协议的可移植性; 由于 COOB 的存在,数据的生产者和使用者不需要了解 对方; 大量使用元素参数化; 系统功能和系统功能组提供了语义一致性。 语义一致性 预期期望的变更 泛化模块 抽象公共服务 接口稳定性 配置文件 构件可更换 遵守已定的协议 4 可测试性 严格的数据所有权、元素的语义一致性以及强大的接口定 义,简化了测试中错误责任的发现。 把接口与实现分离 通过上述的一系列措施,SS2000 产品线方法取得了很好的效果,使 CelsiusTech 系统公司获 得了巨大的成功,表现在硬件与软件的费用比例从过去的 35:65 变成了 80:20 。由于软件的费用 得到了良好的控制,该公司现在已经把精力投入到了降低硬件费用上。 使用产品线方法的好处是显而易见的,它将以更小的代价和资源,更快地开发出系统。由于 使用的是高度可靠、性能经过验证的可复用构件,产品线系统的质量将大大提高。而且产品线方 法还开拓了一个广阔的构件市场,这将极大地促进软件构件业的发展。 下面我们对所采用的架构策略进行总结: 1,,,,把架构作为基础把架构作为基础把架构作为基础把架构作为基础:::: 事实上不考虑商业、组织结构方面的问题,仅仅靠架构是不足以形成产品线的。在实现产品 线的过程中,抽象和分层十分重要。通过抽象,可以把可能改变的决策封装在接口边界之内,当 未来发生变化的时候,不需要改变资产库中的内容。 为了合理的设计,设计者必须对对象领域非常熟悉而且有透彻的理解,对变化、差异等要理 解的十分清楚,这样才可能是产品线用于多个差异化的具体产品中。 2,,,,在开发新系统的同时维护资产库在开发新系统的同时维护资产库在开发新系统的同时维护资产库在开发新系统的同时维护资产库 由于产品线架构应用于多个客户目标系统,所以随着需求的变更,产品线架构也会不断变化, 所以可充裕资产也会不断更新。但是,不允许可重用模块以独立于产品线的发展沿着独立于产品 线的方向发展。但是,可以形成针对某些具体类型的产品集,不过在配置方式上要保证统一和灵 活。 3,,,,模块的参数化模块的参数化模块的参数化模块的参数化 在某些情况下,模块的处理可以使用符号来定义参数,以使模型具有通用性。但参数过多也 会带来非法操作和冲突的问题,也要注意到测试中测试这些参数带来的影响。不过,确实很少有 报告说错误操作是由于参数设置不正确造成的。 由于该公司的管理层对产品线方法进行了全面的支持,特别是注意到了从组织结构上与构架 方式匹配,从而取得了巨大的经济效益。在这个过程中,关键是管理层赋予了架构小组在整个设 计中的权威中心,这样就实现了对概念完整性的维护。他们认识到,了解多个领域的知识,并且 具有软件工程技巧的架构师对多个产品线的创建至关重要,同时,新产品开发及产品线改进中, 仍然需要领域专家的协助。 五五五五、、、、产品线架构的障碍产品线架构的障碍产品线架构的障碍产品线架构的障碍 应该看到,产品线方法仍然面临着很大的挑战和障碍,这主要体现在:  文化上文化上文化上文化上:产品线策略意味着软件组织和管理者对他们产品开发的直接控制减少了,对其 它组织的依赖增加了,这意味着一种思想观念上的转变。  战略计划上战略计划上战略计划上战略计划上:产品线的规划不仅是对一组相关系统的管理过程,还需要考虑用户的长期 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 88 - 需要和现有产品线的能力,对未来的发展作出长远规划。  需要折衷需要折衷需要折衷需要折衷:产品线方法需要用户作出折衷,是“独自开发一个恰好是我所需要的系统”, 还是“利用产品线开发一个与我的需要非常接近的系统,但可以节省开发的代价和时 间”。  资源的所有权资源的所有权资源的所有权资源的所有权:产品线构件归谁“所有”?在目前的体制下,这的确是一个容易引起纠 纷的问题。这就需要整个软件开发和管理组织的重心从当前的程序获取转移到商业化的 构件开发上来。  提拔和奖励提拔和奖励提拔和奖励提拔和奖励:当前的开发方法主要是提拔和奖励那些提交了最终系统的开发人员,采用 产品线方法后还应该提拔和奖励那些开发构件和促进了产品线开发中构件复用的人员。 六六六六、、、、复用成熟度模型复用成熟度模型复用成熟度模型复用成熟度模型(RMM) 产品线架构依赖于软件复用,对于一个软件企业重复使用“为了复用目的而设计的软件”的 能力,受 SEI 的能力成熟度模型(Capability Maturity Model ,缩写为 CMM) 的启发,已出现了几 个复用成熟度模型(Reuse Maturity Model ,缩写为 RMM),作为对企业内复用水平层次的度量。 例如,在 IBM 的 RMM 中,将企业的软件复用水平分为五级。随着复用成熟度级别的提高, 复用的范围、复用使用的工具和复用的对象都有变化: 1))))初始级初始级初始级初始级(Initial) :不协调的复用努力。复用是个人的行为,没有库的支持,主要的复用对 象是子程序和宏。 2))))监控级监控级监控级监控级(Monitored) :管理上知道复用,但不作为重点。复用是小组的行为,有非正式的、 无监控的数据库,复用的对象包括模块和包。 3))))协调级协调级协调级协调级(Coordinated) :鼓励复用,但没有投资。复用的范围包括整个部门,有配置管理 和构件文档的数据库,复用的对象包括子系统、模式和框架。 4))))计划级计划级计划级计划级(Planned) :存在组织上的复用支持。在项目级别支持复用,有复用库,复用的对 象包括应用生成器。 5))))固有级固有级固有级固有级(Ingrained) :规范化的复用支持。复用成为整个企业范围的行为,有一组领域相 关的复用库,复用的对象包括 DSSA。 HP 的 RMM 将复用成熟度与复用率联系起来,也分为五级: 1))))无复用无复用无复用无复用:-20 %至 20 %的复用率; 2))))挖掘整理挖掘整理挖掘整理挖掘整理:15 %至 50 %的复用率; 3))))计划复用计划复用计划复用计划复用:30 %至 40 %的复用率; 4))))系统化复用系统化复用系统化复用系统化复用:50 %至 70 %的复用率; 5))))面向领域的复用面向领域的复用面向领域的复用面向领域的复用:80 %至 90 %的复用率。 复用问题是今天软件工程界热衷于研究的话题,对未来软件工程的走势有重要的影响。 3.10 架构决策架构决策架构决策架构决策 当架构设计基本完成而且已经编制了文档以后,需要做的一件最重要的工作就是架构决策, 我们必须知道我们构建的架构对系统重要的质量属性产生的影响,以及对架构方案的取舍做出 定。评估大型系统的架构是一项复杂任务,它的困难在于: 1,大型系统由于结构庞大,要在有限的时间里理解这个构架非常困难。 2,计算机系统的目的是支持商业目标,评估的时候需要把这些目标和技术支持联系起来。 3,大型系统通常都有多个涉众,需要在有限的时间里把这些涉众的观点仔细管理并加以评 估,有的时候是非常困难的。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 89 - 在进行架构评估的时候,有一些方法论可以帮助我们解决这些问题,比如我们可以使用 ATAM(构架权衡分析方法)是一种评估构架的综合全面的方法,这种方法不仅可以揭示出架构 满足特定质量目标的情况,而且可以使我们更清楚地认识到质量目标之间的关系。 ATAM 用以获取系统以及构架的业务目标,并且使用这些目标,通过涉众参与使评估人员把 注意力放在为实现这个目标最重要的构架部分上。 一一一一、、、、ATAM 的参与人员的参与人员的参与人员的参与人员 ATAM 要求如下 3 个小组参与合作: 1,,,,评估小组评估小组评估小组评估小组:::: 该小组时所评估架构项目外部的小组,通常由 3~5 人组成,每个人可能会扮演不同的角色。 这个小组可能是常设的,也可能临时组织的。可能从理解架构的人员中挑选出来,也可能是外部 咨询人员。在任何情况下,他们必须有能力、没有偏见而且私下也没有其它工作要做的外部人员。 评估小组具体的角色如下:  评估小组负责人评估小组负责人评估小组负责人评估小组负责人:准备评估;与评估客户协调;签署评估合同;组建评估小组;最终报 告的生成与提交。  评估负责人评估负责人评估负责人评估负责人:负责评估工作;促进场景的得出;管理场景的选择及优先级的过程;促进 对照架构的场景评估,为现场分析提供帮助。  场景记录员场景记录员场景记录员场景记录员:在得出场景的过程中,把场景写到白板上,描述场景必须用已达成一致的 措辞,如果没有想出这样的措辞,先终止讨论,直到想出来为止。  进展书记员进展书记员进展书记员进展书记员:以电子形式记录评估进展情况;捕获原始场景;捕获促使每个场景的问题; 捕获与场景对应的构架解决方案;打印出采用场景的列表并分发。  计时员计时员计时员计时员:帮助评估人员使评估工作按时进行,评估过程中控制用在每个场景上的时间。  过程观察员过程观察员过程观察员过程观察员:记录如何改进评估过程,以及评估如何偏离了原计划。通常不发表意见, 但可以在过程中向评估负责人提出经过谨慎考虑的基于过程的建议。  过程监督者过程监督者过程监督者过程监督者:帮助评估负责人记住并执行评估方法的各个步骤。  提问者提问者提问者提问者:提出涉众可能没想到的关于架构的问题。 2,,,,项目决策者项目决策者项目决策者项目决策者:::: 这些人对开发项目有发言权,并有权要求进行某些改变,他们通常包括项目管理人员、承担 开发费用的客户代表也可以列入其中,架构设计师必须参与评估。 3,,,,构架涉众构架涉众构架涉众构架涉众:::: 涉众包括开发人员、测试人员、维护人员、性能工程师、用户以及与该项目交互的其它项目 人员,他们的任务是,清晰而且明白无误地说明架构必须满足的具体质量指标,例如可修改性、 安全性以及可靠性等。 二二二二、、、、ATAM 的结果的结果的结果的结果 ATAM 评估至少应该包括如下结果:  一个简洁的架构表述一个简洁的架构表述一个简洁的架构表述一个简洁的架构表述:通常的架构文档是对象模型、接口及其签名的列表。但 ATAM 要求在一个小时内表述构架,这就要求有一个简洁、可理解的架构表述。  表述清楚的业务目标表述清楚的业务目标表述清楚的业务目标表述清楚的业务目标:业务目标能否表述清楚,关系到架构能否无歧义的实现,对于开 发小组这尤其重要。  用场景用场景用场景用场景集合集合集合集合捕捕捕捕获的质量需求获的质量需求获的质量需求获的质量需求:业务目标导致质量需求,一些重要的质量需求是用场景的 形式来捕获的。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 90 -  架构决策到质量需求的映射架构决策到质量需求的映射架构决策到质量需求的映射架构决策到质量需求的映射:可以根据构架决策所支持或者所阻碍的质量属性来解释构 架决策,对于 ATAM 期间分析的每个质量场景,确定哪些有助于实现该质量场景的架 构决策。  所确定的敏感点和权衡点集合所确定的敏感点和权衡点集合所确定的敏感点和权衡点集合所确定的敏感点和权衡点集合:这是对一个或者多个质量场景有显著影响的构架决策。 比如,备份数据库是一个架构决策,它正面的影响了可靠性,是一个关于可靠性的敏感 点。但是保持备份消耗了系统资源,又负面的影响了系统性能。因此它是可靠性与性能 的权衡点,这个决策的风险在于,性能成本是不是超过了规定范围。  有风险决策和无风险决策有风险决策和无风险决策有风险决策和无风险决策有风险决策和无风险决策:ATAM 中有风险决策的定义是:根据所陈述质量属性的需求, 可能导致不期望的结果的架构决策。无风险决策与之对应,被认为是安全的架构决策。  风险主题的集合风险主题的集合风险主题的集合风险主题的集合:分析完成以后,评估小组把发现的所有风险的集合列表,进而寻找确 定架构中系统弱点的总的主题,如果不采取措施,这些风险主题将会影响项目的业务目 标。 三三三三、、、、ATAM 的阶段的阶段的阶段的阶段 ATAM 的活动分四个阶段:  第第第第 0 阶段阶段阶段阶段:为合作关系和准备阶段,评估小组负责人和主要项目决策者进行非正式会议, 以确定此次评估的细节,项目代表向评估人简要概述项目,以使评估小组具备适当的专 业技术人员的协助。另外对于会议的地点、时间以及后勤保障需要实现达成一致,对于 需要什么样的架构文档也需要达成一致。  第第第第 1 和第和第和第和第 2 阶段阶段阶段阶段:为评估阶段,第一阶段,评估小组和项目决策者会晤(通常一天时间), 以开始信息收集和分析工作。第二阶段,构架涉众加入到评估中,分析继续进行(一般 用两天时间),后面会详细讨论这两个阶段的步骤。  第第第第 3 阶段阶段阶段阶段:小组需要生成一个最终的书面报告。在总结会议中,需要讨论哪些活动比较 理想,还有什么需要自我检查和改进的问题,以使评估工作一次比一次更好。 下面对第 2 和第 3 阶段进行讨论,这就是评估阶段的步骤。 第第第第 1 步步步步:ATAM 方法的表述方法的表述方法的表述方法的表述 评估负责人需要向参加会议的项目代表介绍 ATAM,说明每个人要参与的过程,回答有关问 题,负责人需要使用一个简单的演示来简要描述 ATAM 步骤和评估的结果。 第第第第 2 步步步步:商业动机的表述商业动机的表述商业动机的表述商业动机的表述 评估小组成员需要了解促成这个项目开发的主要商业动机,介绍的问题包括:  系统最重要的功能。  相关技术、管理、经济和政治的限制。  该项目相关的商业目标。主要的涉众。  架构驱动因素,也就是形成该构架的主要质量属性目标。 第第第第 3 步步步步:构架的表述构架的表述构架的表述构架的表述 设计师表述架构的本质,在表述中,设计师首先必须谈到架构的约束条件,以及满足这些条 件所使用的模式。在表述中应该抓住重点和本质,讲述构架最重要的方面,而不需要面面俱到甚 至关注自己感兴趣但不太重要的方面。在讲述的时候尽可能使用视图而不是文字。 第第第第 4 步步步步:对架构方法进行分类对架构方法进行分类对架构方法进行分类对架构方法进行分类 通过对每个质量属性使用的模式,可以对架构方法进行分类,评估小组应该对这些模式和方 法进行明确的命名。应该注意到每个方法都影响特定的质量属性,但也可能造成其它不良的影响。 比如分层模式提供了可移植性,但很可能是牺牲性能为代价的。 这个分类表应该由记录员记录下来,以供所有人传阅。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 91 - 第第第第 5 步步步步:生成质量属性效用树生成质量属性效用树生成质量属性效用树生成质量属性效用树 由于质量属性极大的影响了架构设计,而且不同的架构设计方法对不同的质量属性的影响可 能是矛盾的,比如对安全性要求极高的系统,性能极高的架构可能完全是错误的。 所以,我们必须对质量属性的优先级进行分配,可以生成质量属性效用树,这种树实际上是 一个鱼骨图,从不同的动机中寻找最主要的原因,具体的方法在系统分析中已经讨论过了,这里 不再重复。 第第第第 6 步步步步:分析架构方法分析架构方法分析架构方法分析架构方法 在这里,评估小组每次分析一个最高优先级的场景。方法是设计师解释架构如何支持这个场 景,小组成员通过提问探查设计师用来实现场景的构架方法。在这个过程中,评估小组把相关架 构决策编成文档,确定它的有风险决策、无风险决策、敏感点、权衡点,并且对它进行分类。必 要时说明设计师是如何避开这种架构的弱点并保证该方法满足要求的。 评估小组的目标是,确信这个方法能够达到质量属性的要求。 下面给出一个场景构架方法分析的表格,需要整体上把有风险决策、无风险决策、敏感点、 权衡点列成单独的表,表中 R8 、T3 、S4 等代表这个表中的条目。 场景号: 场景:检测主交换机的硬件故障并从中恢复过来 属性 可用性 环境 正常操作 刺激 某个 CPU 不能正常工作了 响应 切换可用概率是:0.999999 架构决策 敏感点 权衡点 有风险决策 无风险决策 备用 CPU S2 R8 无备用数据通道 S3 T3 R9 看门狗 S4 N12 心跳 S5 N13 故障切换路由 S6 N14 推理 通过使用不同的硬件和操作系统,保证不出现通用模式故障(参见风险决策 R8 )。 最坏情况下,4 秒钟(运算状态的最长时间)内完成恢复。 根据心跳和看门狗的速度,保证 2 秒之内检测到故障。 看门狗简单可靠(已经过验证)。 由于缺少备用数据通道,因此可用性存在风险(参见有风险决策 R9 ) 架构图 见附件 1 附件 1:架构图 至此第一阶段结束,评估小组需要对所获得的知识进行总结,并在 1 到 2 周的中断时间内与 设计师进行非正式会晤(通常用电话形式),如果必要,在这个阶段可以分析更多的场景,也可 以解决已澄清的问题。 当评估决策者准备就绪,把评估组成员再召集到一起的时候,第二阶段就开始了。 此时应该重复前面的第一步,重申在这个阶段每个人所扮演的角色,而且需要概述一下第 2~6 步的结果,并给每个人一份有风险决策、无风险决策、敏感点、权衡点的当前列表,当大家 熟悉了以评估结果以后,就可以进行下面的 3 步了。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 92 - 第第第第 7 步步步步:集体讨论并确定架构的优先级集体讨论并确定架构的优先级集体讨论并确定架构的优先级集体讨论并确定架构的优先级 上面第 5 步生成的质量属性效用树,主要是为了了解架构设计师是如何看待质量属性构架驱 动因素的,对场景进行集体讨论,则是为了了解多数涉众的看法,在参与评估的人员比较多的时 候,对场景进行集体讨论非常有效,可以创造出一个人的想法激发其他人灵感的氛围。 这种讨论过程,能够促进相互交流、发挥创造性、并起到表达参评人员共同意愿的作用。如 果集体讨论确定了优先级的一组场景与效用树进行比较,如果相同,则说明设计师所想的与大多 数涉众的想法基本是一致的。如果又发现了其它驱动场景,则可能存在一个风险,表明涉众与设 计师的想法不一致。 在集体讨论中,不同的涉众对场景的要求可能是不一样的,比如,维护人员可能会更关注易 修改性,最终用户可能会关注一个功能或者易操作性,这就需要讨论和平衡。我们鼓励涉众考虑 在效用树中尚未分析过的场景,或者是在前面的分析中被认为没有引起足够重视的场景。 确定了场景以后,就必须划分优先级,我们必须注意把有限的评估时间用在最重要的地方。 首先把涉众认为代表相同行为或者质量属性的场景合并起来,然后投票。 投票的方法是,每个涉众拿到总场景数 30% 的选票(此数只入不舍,比如 20 个场景,每个 人 6 张选票),投票时可以任意使用这些选票,可以全投给一个场景,也可以一个场景投一张。 每个涉众都要公开投票,然后评估负责人对场景进行排序,选择“某得票数之上的”场景, 供后续步骤使用。 第第第第 8 步步步步:分析架构方法分析架构方法分析架构方法分析架构方法 对于已经收集的若干场景并且确定了优先级以后,评估小组引导设计师实现第 7 步中得到的 优先级最高的场景。设计师对相关的构架决策如何有助于实现每个场景作出解释。理想情况下, 设计师使用已经讨论过的构架方法对这些场景作出解释。 第第第第 9 步步步步:结果的表述结果的表述结果的表述结果的表述 最后,把 ATAM 分析中得到的各种信息进行归纳总结,并且呈现给涉众。一般来说应该有 一个书面报告,包括商业环境、促成该构架的主要需求、约束条件和架构策略等,然后表述如下 结果:  已经写了文档的构架方法。  经过讨论得到的场景和优先级。  效用树。  所发现的风险决策。  已经编成文档的无风险决策。  所发现的敏感点和权衡点。 我们在评估过程中得到了这些结果,并且对它进行了讨论分类,同时还可以根据一些常见的 基本问题或者系统缺陷把风险分解为风险主题,比如没有足够的重视文档编写、未提供备份能力 或者为提高可用性等。 在这个过程中,很可能把某个经理认为是技术层面的风险,提高为他该关心的某个问题的威 胁,这都可能改变后期很多问题的做法。 3.10 关于架构的重要结论关于架构的重要结论关于架构的重要结论关于架构的重要结论 通过以上讨论,我们得到了一些关于架构设计的有启发性的结论: 1,架构设计是保证软件质量的最重要的要素之一,因此必须由有多年工作经验、对相关领 域知识了解透彻、对质量和项目管理理解深入的人来担任系统构架的工作,架构人员的想象力是 至关重要的。 2,需求分析的系统全面,并且着重于描述高层需求,是架构设计成功的必要条件,因此我 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 93 - 们必须花大力气改善需求分析工作,尤其是需求分析人员必须受过很深的专业训练,对问题点的 把握要准确。 3,软件质量标准决定了架构的风格,所谓高质量软件对于不同的用户要求是完全不一样的, 因此架构设计必须把质量问题放在重要位置认真研究,以寻找恰当的架构策略。 4,研究质量就必须把每个质量属性细化,建立质量属性场景,并对每个场景确定解决方案, 最后综合出架构策略,最后的策略应该经过权衡,把设计的关注点集中在最主要的方向上。 5,优秀的产品线可以带来巨大的经济效益,但产品线并不仅仅是技术,必须以合适的组织 形式与之协调,与此同时,项目管理与过程也必须依据产品线方式进行过程改进,这样才可能使 架构的特点得到充分的发挥。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 94 - 第四章 模型驱动的架构策略 模型驱动的开发可以保证关注点前后一致,模型驱动的开发可以保证关注点前后一致,模型驱动的开发可以保证关注点前后一致,模型驱动的开发可以保证关注点前后一致,用例不仅仅是用例不仅仅是用例不仅仅是用例不仅仅是一种一种一种一种需求方法,而且需求方法,而且需求方法,而且需求方法,而且 是设计、测试的驱动因素。是设计、测试的驱动因素。是设计、测试的驱动因素。是设计、测试的驱动因素。 在一个迭代周期内,我们必须要注意到经典的软件工程学原理不但适用而且是必须坚持的。 所以一个迭代周期将贯穿整个分析、设计、编码、测试和集成过程。软件开发需要太多的问题需 要关注,当系统设计的时候,必须处理和平衡许多困难的关注点,系统如何达到预期的功能?如 何达到性能和可靠性的要求?如何处理平台特性等。为了保证关注点的前后一致,模型驱动的开 发是个很好的理念,这种理念会使整个开发变得非常有序而且有效。 把问题分解到更小的部分,在计算机科学中被称之为关注点分离,理想情况下,希望把不同 的关注点清晰的分离到不同的模块中去,并且能够互相独立,也就说说把模块当成一个个关注点 的集合,一个个的深入研究和开发,然后再把这些软件模块组合到一起 成功的关注点分离必须尽早开始,依照涉众的关注点来收集系统的需求,但当关注点很多的 时候,任何一个组件都没有办法满足要求,这就构成了影响多个构件的横切关注点,由于面向对 象的方法没有办法使关注点分离,这就造成了面向对象方法的局限性。 构件技术虽然解决了易扩展的问题,但对于不同涉众的关注点,仍然没有很好的方法有条理 的解决这个问题,由于这种冗余代码段存在于许多操作和类中,一个功能的改变可能会影响很多 类。这种冗余被称之为“横切关注点”。 软件架构设计并不仅仅发生在项目的早期阶段,在整个项目过程中都可能迭代的考虑架构优 化的问题。尤其是在敏捷过程中,每个迭代周期中都可能发生功能和非功能需求的收集和设计, 我们的目标是使这个过程具有流畅之美,确保整个项目能够按要求完成。 在软件产业化进程中,软件工程学是目前研究最活跃的领域之一,人们极力的从组织学、方 法学、技术学、经济学乃至数学的领域,多角度全方位的研究和探索,新的思想层出不穷。下面 的讨论有些正处于探索的进程之中,介绍这些思想对开阔我们的视野非常帮助。 从方法论的角度,面向过程、面向对象、面向构件以及面向方面的方法并不是相互矛盾甚至 相互对立的,而是以各种思维方式和技术手段,研究处理关注点独立性的方法。研究整个软件开 发过程中的架构设计,对于提升软件整体的质量是非常有意义的。 4.1 产品用例的细化产品用例的细化产品用例的细化产品用例的细化分析分析分析分析 一旦选中了部分用户描述进入迭代过程,为了确保设计和编码产生完全符合需要的系统,就 要对选中的描述进行细化分析,这实际上是一个细化的需求分析过程,会产生更多的需求信息, 必须有效的组织和处理它们。 一一一一、、、、从系统的角度研究事件及行为从系统的角度研究事件及行为从系统的角度研究事件及行为从系统的角度研究事件及行为 产品用例的细化分析,首先应该从系统的角度研究外部参与者与我们将要创建的软件系统之 间如何交互。交互期间,参与者产生一个发送给系统的事件,通常要求系统响应这个操作。为了 更仔细的研究这些外部参与者与系统交互的过程,使用系统顺序图(SSD)是可取的,在这里所 有的系统都被当作黑箱,图的重点是从参与者跨越到边界的事件。 根据分析的需要,在 SSD 中可以考虑每个相邻系统与系统的交互过程,从而发现和定义尽 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 95 - 可能多的事件,发现事件的粒度可以根据具体情况来决定。下面的例子是用 SSD 显示参与者与 系统(作为黑箱)直接的交互,参与者所产生的事件。 产品用例不仅仅是把业务用例“翻译”成产品用例,更重要的是为设计提供想法,用例不仅 仅是一种需求开发,还是一种用于驱动整个软件开发生命周期的软件工程技术。用例驱动是假定 软件开发过程是模型驱动的,最简单的情况是包括概念模型、设计模型和实现模型,一般来说对 于每次迭代,开发小组都需要最如下的事情:  寻找用例并详细说明它们。  设计每个用例。  设计并实现每个类。  测试每个用例。 二二二二、、、、场景与用例事件流场景与用例事件流场景与用例事件流场景与用例事件流 场景场景场景场景((((scenario)))):是参与者和被讨论系统之间一系列特定的活动和交互,通常被称之为“用 例的实例”。通俗地讲,场景实际上是在说故事。一般来说,一个用例就是描述参与者使用系统 达成目标的时候一组相关的成功场景和失败场景的集合。 用例分析的关键是专注于“怎样才能使系统为用户提供可观测的数据,或帮助用户实现它们 的目标”,而不是仅仅把系统需求用特性和功能的细目罗列出来。在需求分析中,我们必须专注 于考虑系统怎么才能增加价值和实现目标。用例的主要思想是:为功能需求写出用例(而不是老 式的为“ n 系统会怎么做”的功能列表),在统一过程中用例是发现和定义需求的主要方法,是 功能性行为。 用例的一个场景(说故事),用来研究一部分工作的分步骤情节,而这个情节或者情景必须 用规定的格式来描述。由于场景是一个中性的媒体,所有人都能够理解,业务分析师用它来对工 作所要做的事情取得一致意见,在取得一致意见以后,风险承担者将决定多少工作由产品来完成, 然后产生一个参与者与产品交互的场景。 1111,,,,用例事件流及应该用例事件流及应该用例事件流及应该用例事件流及应该包含的内容包含的内容包含的内容包含的内容 这种场景的描述,也可以理解为对完成用例行为所需的事件的描述。事件流描述了系统应该 做什么,而不是怎么做。可以通过一个清晰的,易被用户理解的事件流来说明一个用例的行为。 在事件流中包括用例何时开始和结束,用例何时和参与者交互,什么对象被交互以及该行为的基 本流和备选流,这可以用下图表示。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 96 - 由此可以得出基本的场景描述文档的内容:  简要说明简要说明简要说明简要说明:描述该使用案例的作用(可以不写出)。  前置条件前置条件前置条件前置条件:开始使用该用例之前必须满足的系统和环境的状态和条件(必要条件而不是 充分条件)。  主事件流主事件流主事件流主事件流:用例的正常流程(事件流是关注系统干什么,而不是怎么干),也称为用例 的路径。  备选事件流备选事件流备选事件流备选事件流:用例的非正常流程,如错误流程,异常路径、成功路径和失败路径等几个 方面的内容。  后置条件后置条件后置条件后置条件:用例成功结束后系统应该具备的状态和条件(但不是每个用例都有后置条 件)。 关于前置条件和后置条件,可以参考下面一些指导方针。  前置后置条件所描述的状态应该是用户能观察到的。可观察的状态例如“用户已在系统 注册”或“用户已打开文档”等。  前置条件是用例启动时候的约束,但不是用例启动时候的事件。  虽然你可以在某一个用例层次上定义前置和后置条件,但它们不是只限于一个流程。  无论执行哪个备选流程,后置条件都应该为假,它仅对主事件流成为真。比如在后置条 件中这样概括:“动作完成,或者出现错误,动作未能执行”,而不是“动作已经完成”。  后置条件对描述用例来说是重要的工具,你首先可以定义用例要获得什么后置条件,然 后再考虑如何达成这个条件(所需要的事件流程)。 2222,,,,场景场景场景场景描述文档的基本要求描述文档的基本要求描述文档的基本要求描述文档的基本要求 1111))))首先写出基本的路径首先写出基本的路径首先写出基本的路径首先写出基本的路径 这是最主要的事情,因为它是用户最关心或者最想看到的内容。 2222))))用例交互的四步曲用例交互的四步曲用例交互的四步曲用例交互的四步曲  参与者产生某个行为动作;  然后系统对此动作进行响应;  响应成功后再根据动作的要求进行状态的改变;  最后将改变后的结果再回馈给参与者。 3333))))模板格式模板格式模板格式模板格式 用例的模版可以有不同的形式,关键是要表达清楚: 用例名用例名用例名用例名: 用例类型用例类型用例类型用例类型 用例用例用例用例 ID: 主要业务参主要业务参主要业务参主要业务参与者与者与者与者: 其它参与者其它参与者其它参与者其它参与者: 项目相关人员兴趣项目相关人员兴趣项目相关人员兴趣项目相关人员兴趣: ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 97 - 描述描述描述描述:::: 前置条件前置条件前置条件前置条件:::: 后置条件后置条件后置条件后置条件:::: 触发条件触发条件触发条件触发条件: 基本流程基本流程基本流程基本流程: 扩展流程扩展流程扩展流程扩展流程:::: 结束结束结束结束: 业务规则业务规则业务规则业务规则:::: 实现约束和说明实现约束和说明实现约束和说明实现约束和说明:::: 假设假设假设假设:::: 待解决问题待解决问题待解决问题待解决问题: 对于上述一些事件流,以及一些关键和重要的问题,需要对用例进行详细分析,这是需要使 用文档而不是图。场景的步骤有一定的限制,建议为 3~10 步,可以避免迷失在细节中。因为场 景需要与风险承担者共同商量,又要有合理的细节程度,又不会让某些业务风险承担者感觉太复 杂。系统所需的功能与行为,可以通过一个或者多个用例来描述,它是系统所有可能用途的总合。 对于较大的系统,可以把用例模型划分到不同的用例包里面去,每个包包含针对一组参与者的一 组用例,形成一个个的功能区。 三三三三、、、、场景的细化描述场景的细化描述场景的细化描述场景的细化描述 用例的场景是利用事件流,以此可以实现用例的实例,用例事件流的描述文档前面已经讨论 过,当文本不能很好的描述的时候,可以使用活动图或者流程图来表达。 对于某些交互序列重复发生的事件流,可以把这些部分独立描述,定义成子事件流,而子事 件流的引用,可以使用超级链接等实现技术来完成它。 设想一个“酒店管理系统”的简单例子,我们描述一下场景(事件流)的文档,主要表达的 是对子事件流的引用。 用例名用例名用例名用例名: 预定房间 用例类型用例类型用例类型用例类型 用例用例用例用例 ID: 主要业务参与者主要业务参与者主要业务参与者主要业务参与者: 客户 描述描述描述描述:::: 该用例描述客户如何预定一个房间 前置条件前置条件前置条件前置条件:::: 客户已经登录到系统 后置条件后置条件后置条件后置条件:::: 成功预定之后,创建一个新的一顶记录,特定时间的可用房间数将减少,如 果预定失败,数据库不会发生任何改变。 基本基本基本基本事件流事件流事件流事件流: 1, 客户选择预定一个房间。 2, 系统显示酒店拥有的房间类型,以及它们的收费标准。 3, 客户核对房间的收费(S1 )。 4, 客户对选择的房间提出预定。 5, 系统在数据库减少这个类型可预定房间的数目。 6, 系统基于提供的详细资料创建一个新的预定。 7, 系统显示预定确认号以及入住登记说明。 8, 用例结束。 备选事件流备选事件流备选事件流备选事件流:::: A1. 重复预定重复预定重复预定重复预定 如果第 5 步存在相同的预定(同样的名字、e-mail 、入住时间、离开时间) 则系统显示原有的预定,并询问客户是否进行新的预定。 1,如果客户想继续,则系统开始新的预定,用例重新开始。 2,如果用户说明预定是重复的,用例结束。 A2…… 子事件流子事件流子事件流子事件流:::: S1 ,核定房间收费核定房间收费核定房间收费核定房间收费。 1,客户选择需要的房间类型,并说明将停留的时间。 2,系统根据给出的周期,计算出总的费用并告知用户。 特别需求特别需求特别需求特别需求:::: 系统必须能处理 5 个并发预定,每个预定所花时间不超过 20 秒。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 98 - 在用例分析的时候,还需要避免在基础用例上分解出更小的用例,这称为“功能分解”,这 是要避免的,因为一般情况过细的分解并不能向涉众提供真实的价值,而且很可能掉进分解的陷 阱。如果发现有 200 个用例,一般已经掉进了功能分解的陷阱。所以,用例分解的时候一定要参 考用户需要获取什么价值。 四四四四、、、、用例之间的结构化关系用例之间的结构化关系用例之间的结构化关系用例之间的结构化关系 对于大多数用户和系统描述来说,上面的细化步骤再充分不过了,多数情况可以合理的确定、 精化主流程和替换流程,确定前置和后置条件,为系统提供了充分而广泛地描述。但是随着应用 的复杂性不断的增长和演变,就可能会遇到用例之间的结构化关系,下面我们来讨论有关问题。 用例通过扩展、包含、泛化等关系作为对关注点之间进行建模的手段,称之为用例的结构化, 比如如下图所示的情况,下面讨论这种结构化用例场景的描述规则。在用例建模的时候,不同用 例之间的关系如下:  包含包含包含包含((((include )))):一个用例的实现使用另一个用例的实现。其图形表示方法为在用例图 上用一条从基本用例指向包含用例的虚箭线表示,并在线上标注购造型<> :  泛化泛化泛化泛化((((generalization )))):一个用例的实现从另一个抽象的用例继承。  扩展扩展扩展扩展((((extend )))):扩展关联的基本含义与泛化关联类似,但是对于扩展用例有更多的规 则,即基本的用例必须声明若干新的规则--- 扩展点(Extension Points ),扩展用例只能 在这些扩展点上增加新的行为并且基本用例不需要了解扩展用例的如何细节。它们之间 存在着扩展关系。如果特定的条件发生,扩展用例的行为才能执行。其图形表示同样用 虚箭线表示,并在线上标注购造型<> : 1,,,, 包含包含包含包含((((imclude )))) 为了确保产品设计的品质,产品开发的可追踪性与回朔性要好,因此在用例设计的时候,我 们希望关注点相互独立,其重要性也不能相互比较,这就是所谓对等关注点。例如在酒店管理系 统中,预订房间(Reserve Room )、登记入住(Check In Customer )、结账离开(Check Out Customer ) 都是对等关注点。 随着用例的细化和精化,团队会发现某些用户行为会在多个地方重复出现,事实上,很大一 部分系统功能在多个地方重复出现时有可能的,比如输入口令进行用户确认。显然对相同的用户 行为出现冗余的文档是不合适的,这就可以使用包含关系。包含的目的是在其它用例中引入用例。 作为分析师,我们应该注意到问题越是前期发现和解决,总的风险就越小。所以设计师希望 从分析的时候就做到使关注点相互分离。在分析的时候,对于不同用例的相似步骤,可以把这些 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 99 - 公共事件抽取出来,放在被包含的用例中,其它用例可以引用这些被包含用例中的事件流。 例如,“预定房间”和“登记入住”都需要参与者核对房间的可用性、以及查询有没有可用 的房间等,这可以增加一个“核对房间清单(Check Room Details )”的包含用例。 它的执行过程如下。、 注意,包含并不是调用,而是在同一级别插入并展开。看起来这很复杂,但开发团队很容易 掌握,因为这类似于软件中的子程序,如果用的合理,包含关系能简化开发和维护活动,也是很 重要的一种结构。 2,,,,扩展扩展扩展扩展((((extension )))) 随着系统随时间的不断演变,需要附加一些特性和功能来满足新的和当前的用户需要。假定 有这样的情况,一个项目范围是团队一次迭代所能完成规模的两倍,你就需要砍掉一部分功能, 把某些用例推到下一个周期完成。在第一个迭代周期完成一个主要的功能,下一个迭代周期完成 一些扩展的功能。 例如,酒店管理系统中有一个功能“待分配房间的预订人等候名单”,如果没有房间,系统 就会把客户放进这个“等候名单(Waiting list )”,因此,这个“Waiting list ”就是“预订房间(Reserve Room )”的扩展。把扩展分离出来,可以使问题容易理解,这就就不至于被过多的问题所纠缠。 下图表示了这个扩展用例的存在发生了什么。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 100 - 使用扩展用例的理由有三条:  作为一个实体,扩展用例能够简化用例的维护,同时允许团队关注并细化扩展的功能, 而不需要重读基本用例本身。  把扩展看成是用例开发,扩展点可以在基本用例中给出作为“通向未来特性”的途径。  扩展用例可以表示随意性的行为,而不是一个新的基本或备选流程。 最后一条往往是最有用的。还需要注意需求的变更,标识出哪些需求将来可能变更,尽早提 出一些解决办法,比如把可能变更的功能独立出来(至少建议这样做),这样就可以避免将来的 需求变更带来不利的影响,所以,对于产品用例的考虑,我们可以在早期注意以下几个方面:  用例对应着功能包,所以用例的大小要合适。  注意到缠绕状态,标识出并且提出建议方案。  研究并且标识出易变性,把易变的功能独立起来。 从这些角度说,产品用例是在业务用例的基础上更进一步的思考。 五五五五,,,,用例扩展关系用例扩展关系用例扩展关系用例扩展关系的场景描述的场景描述的场景描述的场景描述 扩展用例包括一个或者多个扩展事件流,扩展事件流与备选事件流很相似,只不过它是在另 一个用例中添加行为,例如上图情况: 用例名用例名用例名用例名: 预定房间 用例类型用例类型用例类型用例类型 用例用例用例用例 ID: 主要业务参与者主要业务参与者主要业务参与者主要业务参与者: 客户 描述描述描述描述:::: 该用例描述客户如何预定一个房间 前置条件前置条件前置条件前置条件:::: 客户已经登录到系统 后置条件后置条件后置条件后置条件:::: 成功预定之后,创建一个新的一顶记录,特定时间的可用房间数将减少,如 果预定失败,数据库不会发生任何改变。 基本事件流基本事件流基本事件流基本事件流: 1,客户选择预定一个房间。 2,系统显示酒店拥有的房间类型,以及它们的收费标准。 3,客户核对房间的收费(S1 )。 4,客户对选择的房间提出预定。 5,系统在数据库减少这个类型可预定房间的数目(E1 )。 6,系统基于提供的详细资料创建一个新的预定。 7,系统显示预定确认号以及入住登记说明。 8,用例结束。 备选事件流备选事件流备选事件流备选事件流:::: …… 扩展点扩展点扩展点扩展点 E1 :更新房间可用性 5.1 扩展点发生在基本流的第 5 步 用例名用例名用例名用例名: 处理等候列表 用例类型用例类型用例类型用例类型 用例用例用例用例 ID: ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 101 - 基本事件流基本事件流基本事件流基本事件流: …… 扩展事件流扩展事件流扩展事件流扩展事件流:::: EF1 ::::房间预定队列房间预定队列房间预定队列房间预定队列。。。。 当没有客户所选择的房间的时候,该扩展事件流则发生于用例“预定房间” 的扩展点“更新房间可用性”上。 1,创建一个等候预定,并根据所选择的房间类型生成一个唯一标识符。 2,把等候预定放入一个等候列表中。 3,显示这个等候预定的唯一标识符。 4,基用例结束。 六六六六,,,,用例泛化用例泛化用例泛化用例泛化的场景描述的场景描述的场景描述的场景描述 泛化与类的泛化意义相同,表达用例之间存在“is-a-kind-of ”关系。当一组用例拥有相同的 事件流序列,或者相似的一组约束的时候,可以使用例的泛化。 一般父用例是抽象的,而子用例将继承父用例的特性。比如“预定房间”或者“预定早餐” 还可以有一套相同的其它服务的时候,可以使用泛化描述。 事件流的描述如下: 用例名用例名用例名用例名: 预定服务 用例类型用例类型用例类型用例类型 用用用用例例例例 ID: 基本事件流基本事件流基本事件流基本事件流: 该用例开始于某个客户预定一个服务 1,系统显示可提供的服务。 2,客户选择一项服务。 3,系统显示所选服务的总费用。 4,系统在数据库中减去相应服务的总数量。 5,系统为所选服务创建一个新的预定。 6,系统显示预定确定号。 7,用例结束。 扩展事件流扩展事件流扩展事件流扩展事件流:::: …… 子事件流子事件流子事件流子事件流:::: {abstract} 选择服务 用例名用例名用例名用例名: 预定房间 用例类型用例类型用例类型用例类型 用例用例用例用例 ID: 基本事件流基本事件流基本事件流基本事件流: 子事件流子事件流子事件流子事件流:::: S1 ,选择服务 1,客户选择预定房间。 2,客户选择房间类型并说明停留时间。 3,系统根据给出的周期计算出总的费用。 事件流执行的规则如下:实例化首先出现在子用例中,沿着基本事件流运行,如果子用例没 有定义基本事件流,则沿着父基本事件流执行,上面的例子由于没有定义子基本事件流,所以沿 着父基本事件流运行。在运行到“选择一项服务”的时候,执行子用例定义的“选择服务”子用 例,如下图所示。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 102 - 要注意到的问题是,首先不要混淆泛化和扩展泛化和扩展泛化和扩展泛化和扩展,Java 中的关键字 extend 是泛化而非扩展, 而用例中的扩展与泛化是完全不同的,扩展事件流只不过是挂接到原有用例事件流的某个位置 上,以添加新的行为,而且是在同一层及解决问题。 另一个方面不能混淆泛化和包含泛化和包含泛化和包含泛化和包含,泛化和包含都是用于抽取用例的公共行为,因此容易混淆, 其实它们是实现复用的两种不同手段,泛化关系要求付用例和子用例之间拥有“is-a -kind-of ”关 系,这样继承才有意义,但包含无需拥有这种关系。在使用包含的时候,当执行到使用公共行为 的步骤时,必须明确指出包含的用例,而且指明使用哪个事件流。 4.2 用例驱动的概念用例驱动的概念用例驱动的概念用例驱动的概念模型模型模型模型 在所有上面分析的基础之上,我们就可以满怀信心地开始产品设计了,作为设计的第一步, 我们需要针对每个用例进行分析,就产生了概念模型,概念模型是作为设计软件对象的启发来源, 也是后续设计的必须输入。概念模型是说明问题域里(对建模者来说)有意义的概念概念概念概念类类类类,,,,必须说 明,用例虽然也是一个重要的分析工作,它是强调的概念的过程视图,而概念模型强调的是用例 场景内概念的关系视图,所以,它的输入是用例的场景,也是一个对功能精化分析的过程。 一一一一、、、、概念概念概念概念建模建模建模建模: 我们设计一个系统,总是希望它能解决一些问题,这些问题总是会映射到现实问题和概念。 对这些问题进行归纳、分析的过程就是概念建模。建立概念模型的好处是:  通过建立概念模型能够从现实的问题域中找到最有代表性的概念对象  并发现出其中的类和类之间的关系,因为所捕捉出的类是反馈问题域本质内容的信息。 经典的面向对象分析的步骤,是把一个相关的领域,分解为单个概念类或者对象(是一个我 们能够理解的概念)。概念模型是概念类或者是我们感兴趣的现实对象的可视化表示。它们也被 称之为:分析模型、领域对象模型、分析对象模型等。概念模型是不定义操作(方法)的一组类 图来说明,它主要表达:  概念对象或者概念类;  概念类之间的关联;  概念类的属性。 二二二二、、、、概念建模概念建模概念建模概念建模的基础案例的基础案例的基础案例的基础案例 下面举个简单的例子,说明概念建模的基本概念。 1))))问题的描述问题的描述问题的描述问题的描述 例如:两个概念类 Payment (支付)Sale (售出)在概念模型中以一种有意义的方式关联。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 103 - 2))))关键概念关键概念关键概念关键概念 仔细考察上面的图,可以看出,概念模型实际上是可视化了分析中的单词或概念类,并且为 这些单词建立了概念类。也就是说,概念模型是抽象了一个可视化字典。模型展现了部分视图或 抽象,而忽略了建模者不感兴趣的细节。它充分利用了人类的特点—大脑善于可视化思维。 3))))概念模型概念模型概念模型概念模型不是软件组件的模型不是软件组件的模型不是软件组件的模型不是软件组件的模型 概念模型视相关现实世界分析中事务的可视化表示,不是 Java 或者 C# 类这样的软件组件。 下面这些元素不适合在概念模型中表述: 1,软件工件(窗口或数据库) 2,职责或者方法:方法是个纯粹的软件概念,在设计工作期间考虑对象职责是非常重要的, 但概念模型不考虑这些问题,在这里考虑职责的正确方法是,给对象分配角色(比如收银员)。 4))))概念概念概念概念类类类类 概念模型表示分析中的概念类或词汇,一个不是太准确的描述:一个概念类就是一个观点、 事务或者对象。比较准确的表达为:概念类可以按照它的符号、内涵和外延来考虑。  符号:代表一个概念类的单词或者图片。  内涵:概念类的定义。  外延:概念类定义的一组实例。 三三三三、、、、概念类概念类概念类概念类的识别的识别的识别的识别 1,,,,识别识别识别识别概念类概念类概念类概念类 我们的目标是在相关分析中创建有意义的概念类,比如说创建“处理销售”用例中的相关概 念类。一个方法是通过建立一个候选的概念类的列表,来开始建立模型。但是,更多的是使用名 词短语分析找出概念类的方法,然后把它们作为候选的概念类或者属性。使用这种方法必须十分 小心,从名词机械的映射肯定是不行的,而且自然语言中的单词本来就是模棱两可的。不过,这 仍然是灵感的另一种来源。 一般来说,用大量细粒度的概念类来充分描述概念模型,比粗略描述要好。下面是识别概念 类的一些指导原则:  不要认为概念模型中概念类越少越好,情况往往恰恰相反。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 104 -  在初始识别阶段往往会漏掉一些概念类,在后面考虑属性和关联的时候才会发现它,这 是应该把它加上。  不要仅仅因为需求中没有要求保留一些概念类的信息,或者因为概念类没有属性,就排 除掉这个概念类。 无属性的概念类,或者在问题域里面仅仅担当行为的角色,而非信息的角色的概念类,都可 以是有效的概念类。 2,,,,概念类概念类概念类概念类识别的指导原则识别的指导原则识别的指导原则识别的指导原则 1))))事物的命名和建模事物的命名和建模事物的命名和建模事物的命名和建模 概念模型是问题域中的概念或这是事物的地图,所以地图绘制员的策略,也适用于概念模型 的建模。  使用地域中已有的地名(和城市名相同)  排除不相关的特性(比如居民人数)  不添加不属于某个地方的事物(比如虚构的山川) 以此,我们建议使用如下的原则:  给概念模型建模,要使用问题域中的词汇。  把和当前不相关的概念类排除在问题域之外。  概念模型应该排除不在当前考虑下的问题域中的事物。 2))))在识别在识别在识别在识别概念类概念类概念类概念类的时候一个常犯的错误的时候一个常犯的错误的时候一个常犯的错误的时候一个常犯的错误 在建立概念模型的时候,最常犯的一个错误就是把原本是类的事物当作属性来处理。Store (商店)是 Sale (出售)的一个属性呢?还是单独的概念类 Store ?大部分的属性有一个特征, 就是它的性质是数字或者文本。而商店不是数字和文本,所以 Store 应该是个类。 另一个例子:考虑一下飞机订票的问题,Destination (目的地)应该是 Flight (航班)的属 性呢还是一个单独的类 Airport (包括属性 name )。在现实世界中,目的地机场并不是数字和文 本,它是一个占地面积很大的事物,所以应该是个概念类。 建议:如果我们举棋不定,最好把这样的事物当做一个单独的概念类,因为概念模型中,属 性非常少见。 3,,,,分析相似的分析相似的分析相似的分析相似的概念类概念类概念类概念类 有一些情况是比较不太容易处理的。举个例子,我们来分析一下“Register(记录)”和“POST (终端)”这两个概念。POST 作为一个销售终端,可以是客户端任何终点的设备(用户 PC,无 线 PDA),但早期商店是需要一个设备来记录(Register )销售。而 POST 实际上也需要这个能力。 可见,Register 是一个更具抽象性的概念,在概念模型中,是不是应该用 Register 而不是 POST 吗? 我们应该知道,概念模型其实没有绝对正确和错误之分,只有可用性大小的区分。根据绘图 员原则,POST 是一个分析中常见的术语,从熟悉和传递信息的角度,POST 是一个有用的符号。 但是,从模型的抽象和软件实现相互独立的目标来看,Register 是一个更具吸引力和可用性的表 达,它可以方便的表达记录销售位置的概念,也可以表达不同的终端设备(如 POST)。两种方 式各具优点,关键是看你的概念类重点是表达什么信息。这也是一个架构师必备的能力—抓住重 点。 4,,,,为非现实世界建模为非现实世界建模为非现实世界建模为非现实世界建模 一些业务分析有自己独特的概念,只要这些概念是在业内被认可的,同样可以创建概念类, 比如在电信业可以建立这样的概念类:消息(Message )、连接(Connection )、端口(Port )、对 话(Dialog )、路由(Route )、协议(Protocol )等。 5,,,,规格说明或者描述规格说明或者描述规格说明或者描述规格说明或者描述概念类概念类概念类概念类 在概念模型中,对概念类作规格说明的需求是相当普遍的,因此它值得我们来强调。假定有 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 105 - 下面的情形:  一个 Item 实例代表商店中一个实际存在的商品  一个 Item 表达一个实际存在的商品,它有价格,ID 两个描述信息  每次卖掉一个商品,就从软件中删掉一个实例。 如果我们是这样来表达: 那很可能会认为随着商品的卖出,它的价格也删掉了,显然这是不对的。比较好的表达方式 是这样的: 1))))何时需要规格说明类何时需要规格说明类何时需要规格说明类何时需要规格说明类 在下面的情况下,需要添加概念类的说明类:  商品或服务的信息描述,独立于商品或者服务当前已经存在的任何实例。  删除所描述的事物,会导致维护信息的丢失。  希望减少冗余或者重复的信息。 2))))服务的描述服务的描述服务的描述服务的描述 作为概念类的实例可以是一次服务而不是一件商品,比如航空公司的航班服务。假定航空公 司由于事故取消了 6 个月的航班,这时它对应的 Flight (航班)软件对象也在计算机中删除了, 那么航空公司就不再有航班记录了。所以比较好的办法是添加一个 FlightDestination (航班目的) 的规格描述类,请看下面的例子。 四四四四、、、、概念模型概念模型概念模型概念模型的属性的属性的属性的属性 发现和识别概念类的属性,是很有意义的。属性是个逻辑对象的值。属性主要用于保留对象 的状态。 1,,,,有效的属性类型有效的属性类型有效的属性类型有效的属性类型 大部分属性应该是简单数据类型。当然也可以使其它的一些必要的类型,比如:Color (颜 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 106 - 色)、Address (地址)、PhoneNumber (电话号码)等。 2,,,,非原始的数据类型类非原始的数据类型类非原始的数据类型类非原始的数据类型类 在概念类中,可以把原始数据类型改成非原始数据类型,请应用下面的指导原则:  由分开的段组成数据(电话号码,人名)  有些操作和它的数据有关,如分析和验证等(社会安全号码)  包含其它属性的数据(促销价格的开始和结束时间)  带有单位的数据值(支付金额有一个货币单位)  对带有上述性质的一个或多个抽象(商品条目标识符) 如果属性是一个数据类型,应该显示在属性框里面。 五五五五、、、、概念模型概念模型概念模型概念模型的关联的关联的关联的关联 1,,,,找出关联找出关联找出关联找出关联 关联,是类(事实上是实例)指示有意义或相关连接的一种关系。关联事实上表示是一种“知 道”。如果不写箭头,关联的方向一般是“从上到下,从左到右”。 2,,,,关联的指导原则关联的指导原则关联的指导原则关联的指导原则  把注意力集中在那些需要把概念之间的关系信息保持一段时间的关联(“需要知道”型 关联)。  太多的关联不但不能有效的表示概念模型,分而会使概念模型变的混乱,有的时候发现 某些关联很费时间,但带来的好处并不大。  避免显示冗余或者导出的关联。 3,,,,角色和多重性角色和多重性角色和多重性角色和多重性 关联的每一端称之为“角色”。角色可选的具有:名称、多重性表达式、导航性。 多重性多重性多重性多重性::::多重性表示一个实例,在一个特定的时刻,而不是一段时间内,可以和多个实例发 生关联,“*”表示多个,“1”表示一个。 再一次提醒,发现概念类比发现关联更重要,花费在概念模型创建的大部分时间,应该被用 于发现概念类,而不是关联。 4,,,,两种类型之间的多重关联两种类型之间的多重关联两种类型之间的多重关联两种类型之间的多重关联 两种类型之间的多重关联是可能存在的。比如航空公司的例子,Flight-to 和 Flight-from 可能 会同时存在,应该把它们都标出来。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 107 - 六六六六、、、、概念模型概念模型概念模型概念模型的泛化建模的泛化建模的泛化建模的泛化建模 泛化和特化是概念建模的基本概念,另外,概念类的层次,往往是软件类层次的基本源泉, 软件类可以利用继承来减少代码的重复。 1,,,,泛化及其应用泛化及其应用泛化及其应用泛化及其应用 CashPayment (现金支付)、CreditPayment (信用卡支付)和 Check Payment (支票支付)这 几个概念非常接近,可以组织成一个泛化的类层次,其中超类 Payment (支付)具有更普遍的概 念,而子类是一个更具体的概念。注意这里讨论的是概念类,而不是软件类。 泛化(generalization )是在多个概念之间识别共性,定义超类和子类关系的活动,它是构件 概念分类的一种方式,并且在类的层次中得到说明。在概念模型中,识别超类和子类及其有价值, 因为通过它们,我们就可以用更普遍、更细化和更抽象的方式来理解概念。从而使概念的表达简 约,减少概念信息的重复。 2,,,,定义概念性超类和子类定义概念性超类和子类定义概念性超类和子类定义概念性超类和子类 由于识别概念性的超类和子类具有价值,因此根据类定义和类集,准确的理解泛化、超类、 子类是很有意义的,下面我们将讨论这些概念。 1))))泛化和概念性类的定义泛化和概念性类的定义泛化和概念性类的定义泛化和概念性类的定义 定义:一个概念性超类的定义,比一个概念性子类的定义更为普遍或者范围更广。 在前面的例子中,Payment (支付),是一个比具体的支付方法更为普遍的定义。 2))))泛化与类集泛化与类集泛化与类集泛化与类集 概念性子类与概念性超类,在集的关系上是相关的。所有概念性子类集的成员,都是它们超 类集的成员。 3))))概念性子类定义的一致性概念性子类定义的一致性概念性子类定义的一致性概念性子类定义的一致性 一旦创建了类的层次,有关超类的声明也将适用于子类。一般的说,子类和超类一致是一个 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 108 - “100% 规则”,这种一致包括“属性”和“关联”。 4))))概念性子类集的一致性概念性子类集的一致性概念性子类集的一致性概念性子类集的一致性 一个概念性子类应该是超类集中的一个成员。通俗的讲,概念性子类是超类的一种类型(is a kind of ),这种表达也可以简称为 is-a。这种一致性称之为 is-a 规则。 所以,这样的陈述是可以的:“信用卡支付是一个支付”(CreditPayment is a Payment )。 5))))什么是正确的概念性子类呢什么是正确的概念性子类呢什么是正确的概念性子类呢什么是正确的概念性子类呢 从上面的讨论,我们可以使用下面的测试,来定义一个正确的子类:  100% 规则(定义的一致性)  is-a 规则(集合成员关系的一致性) 6))))何时定义一个概念性子类何时定义一个概念性子类何时定义一个概念性子类何时定义一个概念性子类 举个例子,把顾客(Customer )划分为男顾客(MaleCustomer )和女顾客(FemaleCustomer ), 从正确性来说是可以的,但这样划分有意义吗? 这样划分是没有意义的,因此我们必须讨论动机问题。把一个概念类划分为不同子类的强烈 动机为:当满足如下条件之一的时候,为超类创建一个概念性的子类:  子类具有额外的相关属性。  子类具有额外的相关关联。  子类在运行、处理、反应或者操作等相关方式上,与超类或者其它子类不同。  子类代表一个活动的事务(例如:动物、机器人),它们与超类的其它子类在相关的行 为方式上也不同。 由此看来,把顾客(Customer )划分为男顾客(MaleCustomer )和女顾客(FemaleCustomer ) 是不恰当的,因为它们没有额外的属性和关联,在运行(服务)方式上也没什么不同。尽管男人 和女人的购物习惯不同,但对当前的用例来说不相关。这就是说,规则必须和我们研究的问题相 结合。 7))))何时定义一个概念性超类何时定义一个概念性超类何时定义一个概念性超类何时定义一个概念性超类 在多个潜在的子类之间,一旦发现共同特征,就可以暗示可以泛化得到一个超类。下面是泛 化和定义超类的动机:  潜在的概念子类代表一个相似概念的变体。  子类遵守 100% 的 is-a 规则。  所有的子类具有共同的属性,可以提取出来并在超类中表示。  所有子类具有相同关联,可以提取并与超类相关。 8))))发现发现发现发现概念类概念类概念类概念类的实例的实例的实例的实例 Payment 类: ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 109 - 注意,在构造超类的时候,层次不宜太多,关键是表达清晰。事实上,额外的泛化不会增加 明显的价值,相反带来很大的负面影响,没有带来好处的复杂性是不可取的。 4.3 行为模型与行为模型与行为模型与行为模型与 GRASP 设计模式设计模式设计模式设计模式 在概念模型中是不考虑行为或者消息的,而主要考虑对象之间的静态关联,以及对象之间的 泛化关系。当概念模型建立起来以后,就需要仔细研究对象之间的交互,这需要一个比较清晰的 思路。利用著名的 GRASP 模式(General Responsibility Assignment Software Patterns 通用职责分 配软件模式)可以作为思考的基础。尽管我们平时设计中已经在使用职责的概念,但是应用模式 将会使我们的设计思路更加清晰,考虑问题也更加完整。 一一一一、、、、根据职责设计对象根据职责设计对象根据职责设计对象根据职责设计对象 什么是职责呢?从对象的角度来看,职责与一个对象的义务相关联,职责主要分为两种类型: 1))))了解型了解型了解型了解型((((knowing )))) 职责包括:  了解私有的封装数据;  了解相关联的相关对象;  了解能够派生或者计算的事物。 2))))行为型行为型行为型行为型((((doing )))) 职责包括:  自身执行一些行为,如建造一个对象或者进行一个计算;  在其它对象中进行初始化操作;  在其它对象中控制或者协调各项活动。 职责是对象设计过程中,被分配给对象的类的。 例:一个 Sale 类负责产生一个 SalesLineItems 对象(行为型职责); 一个 Sale 类负责了解 Sale 类的 total 的值(了解型职责)。 我们常常能从概念模型推理出了解型相关的职责,这是因为概念模型实际上展示了对象的属 性和相互关联。职责和方法不是相同的事物,但可以用执行方法来履行职责。职责是通过使用方 法来实现的。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 110 - 二二二二、、、、职责和交互图职责和交互图职责和交互图职责和交互图 虽然我们讨论的目的是为了给对象分配职责的时候提供一个基本的原则,但职责分配只有在 编程的时候才能最终完成。在 UML 中,职责分配到何处(通过方法来实现)这样的问题,贯穿 了交互图生成的整个过程。请看下面的图。 所以,当交互图创建的时候,实际上已经为对象分配了职责,这体现到交互图就是发送消息 到不同的对象。 三三三三、、、、信息专家模式信息专家模式信息专家模式信息专家模式 解决方案解决方案解决方案解决方案:::: 将职责分配给拥有履行一个职责所必需信息的类,也就是信息专家。 问题问题问题问题:::: 在开始分配职责的时候,首先要清晰的陈述职责。假定某个类需要知道一次销售的总额。根 据信息专家模式,我们应该寻找一个对象类,它具有计算总额所需要的信息。 关键关键关键关键:::: 使用概念模型(现实世界领域的概念类)还是设计模型(软件类)来分析所具有所需信息的 类呢?答: 如果设计模型中存在相关的类,先在设计模型中查看。 如果设计模相中不存在相关的类,则查看概念模型,试着应用或者扩展概念模型,得出相应 的概念类。 我们下面来讨论一个例子。假定有如下概念模型。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 111 - 到底谁是信息专家呢? 如果我们需要确定销售总额。 可以看出来,一个 Sale 类的实例,将包括“销售线项目”和“产品规格说明”的全部信息。 也就是说,Sale 类是一个关于销售总额的合适的信息专家。 而 SalesLineItem 可以确定子销售额,这就是确定子销售额的信息专家。 进一步,ProductSpecification 能确定价格等,它就是“产品规格说明”的信息专家。 上面已经提到,在创建交互图语境的时候,常常出现职责分配的问题。 设想我们正在绘设计模型图,并且在为对象分配职责,从软件的角度,我们关注一下: 为了得到总额信息,需要向 Sale 发出请求总额请求,于是 Sale 得到了 getTotal 方法。 而销售需要取得数量信息,就要向“销售线项目”发出请求,这就在 SalesLineItem 得到了 getSubtotal 方法。 而销售线项目需要向“产品规格说明”取得价格信息,这就在 ProductSpecification 类得到了 getPrice 方法。 这样的思考,我们就在概念模型的基础上,得到了设计模型。 注意: 职责的实现需要信息,而信息往往分布在不同的对象中,这就意味着需要许多“部分”的信 息专家来协作完成一个任务。 信息专家模式于现实世界具有相似性,它往往导致这样的设计:软件对象完成它所代表的现 实世界对象的机械操作。 但是,某些情况下专家模式所描述的解决方案并不合适,这主要会造成耦合性和内聚性的一 些问题。后面我们会加以讨论。 四四四四、、、、创建者模式创建者模式创建者模式创建者模式 解决方案解决方案解决方案解决方案:::: 如果符合下面一个或者多个条件,则可以把创建类 A 的职责分配给类 B。  类 B 聚和类 A 的对象。  类 B 包含类 A 的对象。  类 B 记录类 A 的对象的实例。  类 B 密切使用类 A 的对象。  类 B 初始化数据并在创建类 A 的实例的时候传递给类 A(因此,类 B 是创建类 A 实例 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 112 - 的一个专家)。 如果符合多个条件,类 B 聚合或者包含类 A 的条件优先。 问题问题问题问题:::: 谁应该负责产生类的实例? 创建对象是面向对象系统最普遍的活动之一,因此,拥有一个分配创建对象职责的通用原则 是非常有用的。如果职责分配合理,设计就能降低耦合度,提高设计的清晰度、封装性和重用性。 讨论讨论讨论讨论:::: 创建者模式指导怎样分配和创建对象(一个非常重要的任务)相关的职责。 通过下面的交互图,我们立刻就能发现 Sale 具备 Payment 创建者的职责。 创建者模式的一个基本目的,就是找到一个在任何情况下都与被创建对象相关联的创建者, 选择这样的类作为创建者能支持低耦合。 限制限制限制限制:::: 创建过程经常非常复杂,在这种情况下,最好的办法是把创建委托给一个工厂,而不是使用 创建者模式所建议的类。 五五五五、、、、低耦合模式低耦合模式低耦合模式低耦合模式 解决方案解决方案解决方案解决方案:::: 分配一个职责,是的保持低耦合度。 问题问题问题问题:::: 怎样支持低的依赖性,减少变更带来的影响,提高重用性? 耦合(coupling )是测量一个元素连接、了解或者依赖其它元素强弱的尺度。具有低耦合的 的元素不过多的依赖其它的元素,“过多”这个词和元素所处的语境有关,需要进行考查。 元素包括类、子系统、系统等。 具有高耦合性地类过多的依赖其它的类,设计这种高耦合的类是不受欢迎的。因为它可能出 现以下问题:  相关类的变化强制局部变化。  当元素分离出来的时候很难理解  因为使用高耦合类的时候需要它所依赖的类,所以很难重用。 示例示例示例示例:::: 我们来看一下 POS 机的例子,有如下三个类。 Payment (付款)、Register (登记)、Sale (销售)。 要求:创建一个 Payment 类的实例,并且与 Sale 相关联。哪个类更适合完成这项工作呢? 创建者模式认为,Register 记录了现实世界中的一次 Payment ,因此建议用 Register 作为创 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 113 - 建者。 第一方案: 由 Register 构造一个 Payment 对象。 再由 Register 把构造的 Payment 实例通过 addPayment 消息发送给 Sale 对象。 第二方案: 由 Register 向 Sale 提供付款信息(通过 makePayment 消息),再由 Sale 创建 Payment 对象。 两种方案到底那种支持低的耦合度呢? 第一方案,Register 构造一个 Payment 对象,增加了 Register 与 Payment 对象的耦合度。 第二方案,Payment 对象是由 Sale 创建的,因此并没有增加 Register 与 Payment 对象的耦合 度。 单纯从耦合度来考虑,第二种方案更优。 在实际工作中,耦合度往往和其它模式是矛盾的。但耦合性是提高设计质量必须考虑的一个 因素。 耦合分类耦合分类耦合分类耦合分类:::: 1,无任何连接:两个模块中的每一个都能独立地工作而不需要另一个的存在(最低耦合)。 2,数据耦合:两个模块彼此通过参数交换信息,且交换的仅仅是数据(低耦合)。 3,控制耦合:两个模块之间传递的信息有控制成分(中耦合)。 4,公共环境耦合:两个或多个模块通过一个公共环境相互作用: 1)一个存数据,一个取数据(低耦合); 2)都存取数据(低-- 中之间)。 5,内容耦合(一般比较高): 1)一个模块访问另一个模块的内部数据; 2)两个模块有一部分程序代码重叠; 3)一个模块不通过正常入口而转移到另一个的内部; 4)一个模块有多个入口(意味着该模块有多个功能)。 讨论讨论讨论讨论:::: ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 114 - 在确定设计方案的过程中,低耦合是一个应该时刻铭记于心的原则。它是一个应该时常考虑 的设计目标,在设计者评估设计方案的时候,低耦合也是一个评估原则。 低耦合使类的设计更独立,减少类的变更带来的不良影响,但是,我们会时时发现低耦合的 要求,是和其它面向对象的设计要求是矛盾的,这就不能把它看成唯一的原则,而是众多原则中 的一个重要的原则。 比如继承性必然导致高的耦合性,但不用继承性,这就失去了面向对象设计最重要的特点。 没有绝对的尺度来衡量耦合度,关键是开发者能够估计出来,当前的耦合度会不会导致问题。 事实上越是表面上简单而且一般化的类,往往具有强的可重用性和低的耦合度。 低耦合度的需要,导致了一个著名的设计原则,那就是优先使用组合而不是继承。但这样又 会导致许多臃肿、复杂而且设计低劣的类的产生。所以,一个优秀的设计师,关键是用一种深入 理解和权衡利弊的态度来面对设计。设计师的灵魂不是记住了多少原则,而是能灵活合理的使用 这些原则,这就需要在大量的设计实践中总结经验,特别是在失败中总结教训,来形成自己的设 计理念。 六六六六、、、、高内聚模式高内聚模式高内聚模式高内聚模式 解决方案解决方案解决方案解决方案:::: 分配一个职责,使得保持高的内聚。 问题问题问题问题:::: 怎么样才能使得复杂性可以管理? 从对象设计的角度,内聚是一个元素的职责被关联和关注的强弱尺度。如果一个元素具有很 多紧密相关的职责,而且只完成有限的功能,那这个元素就是高度内聚的。这些元素包括类、子 系统等。一个具有低内聚的类会执行许多互不相关的事物,或者完成太多的功能,这样的类是不 可取的,因为它们会导致以下问题:  难于理解。  难于重用。  难于维护。  系统脆弱,常常受到变化带来的困扰。 低内聚类常常代表抽象化的“大粒度对象”,或者承担着本来可以委托给其它对象的职责。 示例示例示例示例:::: 我们还是来看一下刚刚讨论过的 POS 机的例子,有如下三个类。 Payment (付款) Register (登记) Sale (销售) 要求:创建一个 Payment 类的实例,并且与 Sale 相关联。 哪个类更适合完成这项工作呢? 创建者模式认为,Register 记录了现实世界中的一次 Payment ,因此建议用 Register 作为创 建者。 第一方案: 由 Register 构造一个 Payment 对象。 再由 Register 把构造的 Payment 实例通过 addPayment 消息发送给 Sale 对象。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 115 - 第二方案: 由 Register 向 Sale 提供付款信息(通过 makePayment 消息),再由 Sale 创建 Payment 对象。 在第一个方案中,由于 Register 要执行多个任务,在任务很多的时候,就会显得十分臃肿, 这种要执行多个任务的类,内聚是比较低的。 在第二种方案里面,由于创建 Payment 对象的任务,委托给了 Sale ,每个类的任务都比较简 单而且单一,这就实现了高的内聚性。 从开发技巧的角度,至少有一个开发者要去考虑内聚所产生的影响。一般来说,高的内聚往 往导致低的耦合度。 讨论讨论讨论讨论:::: 和低耦合性模式一样,高内聚模式在制定设计方案的过程中,一个应该时刻铭记于心的原则。 同样,它往往会和其它的设计原则相抵触,因此必须综合考虑。 Grady Booch 是建模的大师级人物,它在描述高内聚的定义的时候是这样说的:“一个组件 (比如类)的所有元素,共同协作提供一些良好受限的行为。” 根据经验,一个具有高内聚的类,具有数目相对较少的方法,和紧密相关的功能。它并不完 成太多的工作,当需要实现的任务过大的时候,可以和其它的对象协作来分担过大的工作量。 一个类具有高内聚是非常有利的,因为它对于理解、维护和重用都相对比较容易。 内聚分类内聚分类内聚分类内聚分类:::: 1,功能内聚:一个模块完成一个且仅完成一个功能(高)。 2,顺序内聚:模块中的每个元素都是与同一功能紧密相关,一个元素的输出是下一个元素 的输入(高)。 3,信息内聚:模块内所有元素都引用相同的输入或输出数据集合(中)。 4,时间内聚:一组任务必须在同一段时间内执行(低)。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 116 - 5,逻辑内聚:一组任务在逻辑上同属一类,例如均为输出(低)。 6,偶然内聚:一组任务关系松散(低)。 限制限制限制限制:::: 少数情况下,接受低内聚是合理的。比如,把 SQL 专家编写的语句综合在一个类里面,这 就可以使程序设计专家不必要特别关注 SQL 语句该怎么写。又比如,远程对象处理,利用很多 细粒度的接口与客户联系,造成网络流量大幅度增加而降低性能,就不如把能力封装起来,做一 个粗粒度的接口给客户,大部分工作在远程对象内部完成,减少远程调用的次数。 关于耦合性和内聚性的设计原则关于耦合性和内聚性的设计原则关于耦合性和内聚性的设计原则关于耦合性和内聚性的设计原则::::  力争尽可能弱的耦合性:尽量使用数据耦合,少用控制耦合,限制公共环境耦合的范围, 完全不用内容耦合。  力争尽可能高的内聚性:力争尽可能高的内聚性,并能识别出低内聚性。 七七七七、、、、控制需求与状态转换关系控制需求与状态转换关系控制需求与状态转换关系控制需求与状态转换关系 在上面的讨论中,我们仅仅考虑了每个事件发生后的业务序列,以及计算机能够告诉系统发 生了什么。在大多数情况下这已经足够定义动态信息问题了。但是对于复杂的控制问题,仅仅使 用用例来描述就显得不足,本节会提出若干方法来描述一些附加规则。 1,状态转换的状态转换的状态转换的状态转换的描述描述描述描述 软件问题绝大多数都是讨论对象在不同的时间具有不同的状态,为了控制它们的状态,就需 要知道什么会引起它们改变状态。对于某些类型的对象,使它们改变状态的规则可以很好地用数 学方程式表达,例如飞机控制系统可以通过一组差分方程,根据空气密度、风的速度以及升降舵、 副翼和方向舵的位置,依据一组复杂的数学方程式,计算出飞机如何运动或者是它当前的状态。 得出控制这类对象的规格说明是控制理论的一部分,也是高度专业化的。 另一方面,更多的对象是执行一些离散的事件,当动作结束的时候,可能从一个状态转换为 另一个状态,这里的状态非常少,少到可以用文本书写它,例如前面用例文档的后置条件,就包 含了这层意思。 但是很多情况下状态转换处于这两者之间,它们具备相当的复杂性和相关性,很多设计者做 出的状态转换图并没有很好的定义开发,引起这种图的价值也受到质疑,从高层设计的角度就需 要把这些状态转换进行简化和条理化,最终可以定义软件产品开发。 下面通过一个电话线路的行为作为例子,讨论如何把一个复杂的状态转换图转变成一个条理 化的设计文档。所讨论的复杂控制的状态转换图如下。这里描述的事件包括拨号、挂断、举起听 筒、长时间不做任何事情(超时)、另一方挂断电话。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 117 - 为了控制复杂性,需要对状态进行分类、合并与条理化,图中已经显示了几种方法: 1))))超状态超状态超状态超状态:::: 如果许多状态在相同的事件触发现具有相同的结果,可以组合这些状态成为一个超状态,并 把它们封装在一个方框中。比如,挂机挂机挂机挂机事件可以在 11 个不同状态下发生,但每一种情况下的结 果都是相同的,所以可以把它们合并成一个“非待机”超状态。超时超时超时超时事件可应用在拨号超状态下 的所有状态上。用灰色的线框表示超状态,不但可以减少由紧密排列的平行线所造成的视觉混淆, 更可以达到归纳整理的目的。 2))))对对对对状态状态状态状态分类分类分类分类:::: 尽管区号中每个数字与本地号码的组合会有不同的状态,还是可以把它们(区号、本地号) 各分解成两个状态,尽管还有更多的状态没有表达出来,但作为表达整体状态关系的图,这就足 够了。 3))))利用文本描述利用文本描述利用文本描述利用文本描述:::: 在图中可以省略任何您想省略的东西,因为还有利用文本解释它们的机会,图形越复杂越棘 手,就需要要更多地考虑用文本来描述状态转换,详细的图形描述往往使图更混乱。记住,我们 的任务是用最简单的方式描述对象和它的状态,让开发者容易去理解,而不是强迫每个描述都成 为标准的图形符号,文本是永远值得信赖的东西。为了用文本描述对象和它的状态,就需要包含 一下信息:  所有状态的列表。  对于每个状态,对象在这个状态中作了什么?或者有关这个状态外部能检测到的差异。 例如:对于“车库开启者”这个对象,一个状态是“打开”,对象做的事情是“用马达 把门拉开”。又比如灯泡的状态是“开”,外部能检测到的差异是“灯泡闪亮”。  对于每个状态,哪个事件是可能的,以及对于一个可能的事件,在那个状态下对象如何 响应,对象执行的行为是什么?在那个事件后对象的行为是什么?  在状态转换图中没有表现出来的附加状态信息,如缩位拨号。  哪个状态是开始状态?  哪个状态(或多个)是结束状态? 下面就是电话拨号状态图的文本描述。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 118 - 除了除了除了除了图中所显示的状态图中所显示的状态图中所显示的状态图中所显示的状态,,,,还有五个状态变量应用到电话线路中还有五个状态变量应用到电话线路中还有五个状态变量应用到电话线路中还有五个状态变量应用到电话线路中 区号区号区号区号 最多 3 个数字的字符串,如果呼叫的是长途,区号要被拨打。 本地号本地号本地号本地号 最多 7 位数字的字符串,在区号内部的电话号码。 定时器定时器定时器定时器 一个 60 秒的定时器,其状态有 2 个,运行(倒计时);关闭。 传送传送传送传送 传送电话音频信号,状态有两个:传送;关闭。 接收接收接收接收 接收传送过来的音频信号,状态有两个:接收;关闭。 事件列表事件列表事件列表事件列表 摘机摘机摘机摘机 回路关闭,只能发生在待机状态。 挂机挂机挂机挂机 回路打开,只能发生在待机状态以外的其它状态。 超时超时超时超时 定时器倒计数为 0。 0,,,,1,,,,2,,,,3,,,,4,,,,5,,,,6,,,,7,,,,8,,,,9 按键拨打脉冲数字。 对方挂机对方挂机对方挂机对方挂机 只能发生在通话中,对方挂机。 状态和响应状态和响应状态和响应状态和响应 状态状态状态状态 事件事件事件事件 行为行为行为行为 下一状态下一状态下一状态下一状态 待机待机待机待机 (传送、接收、定时器 处于关闭状态) 0..9 — 待机 摘机 — 拨号者 超时 (假定不发生)关闭定时器 待机 拨号音拨号音拨号音拨号音 (传送处于关闭状态, 接收拨号音,状态进入 时,设置定时器为 60 秒) 0 — 转接线员 1 — 区内号,等待第 1 个号码 2..9 区内号位数字 区内号,等待第 2 到第 7 个号码 挂机 — 待机 超时 — 超时语音提示 区号等待第区号等待第区号等待第区号等待第 1 个号码个号码个号码个号码 (传送、接收处于关闭 状态,定时器运行) 0,1 — 空号语音提示 2..9 区号为数字 区号等待第 2 第 3 个号码 挂机 — 待机 超时 — 超时语音提示 区号等待第区号等待第区号等待第区号等待第 2、、、、第第第第 3 个个个个 号码号码号码号码 (传送、接收处于关闭 状态,定时器运行) 0..9 添加数字到区号 如果区号已经有 3 位号码,区内号 等待第 1 个号码,否则区号等待第 2 第 3 个号码 挂机 — 待机 超时 — 超时语音提示 (其它状态为了简洁而省略了) 接通到另一方接通到另一方接通到另一方接通到另一方 (传送到本地号码所 指定的线路,如果未拨 区号就是本地号码,否 则就是区号,从相应线 路接收,定时器关闭) 0..9 — 接通到另一方 挂机 — 待机 超时 (假定没有发生)定时器关 闭 接通到另一方 请注意,这个表说明了每一状态超时将会发生什么情况,如果仅仅看状态图,就很容易忽略 这个情况。有些状态表现不明显,但通过一张表就可以系统的关注每种可能的情况。 状态图可以让人更好的理解总体情况,但文本不容易让人理解总体情况,但可以很好的理解 每个状态的细节,每次一个事件的仔细阅读,线性的引导阅读者从一个事情到另一个事情,特别 是图中难以表达的细节,在文本中可以很详细的表达。 对于某些相同的响应(比如超时),也可以用一个专门的表来描述。 虽然这张表已经表达了状态图希望表达的所有东西,而且更详细而确切,但是图的作用还是 很大的,因为在文本中,状态之间的关系难以表达,典型的情况是: 读者首先从文本读取一个状态的信息,然后参照图去研究什么状态可以转换到这个状态,然 后在文本中再读取一点,再回到图中查找。如果没有图,要么读者需要在自己的大脑中形成自己 的视觉化场景,要么纯粹抽象的理解状态转换,这两种情况都加重了读者的负担,尤其是很少有 人具备纯粹抽象的处理问题的本领。最终是开发出的产品结果与当初的要求有很大的出入。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 119 - 所有这些还有一个重要的暗示,很多人喜欢使用各种各样的 CASE 工具来辅助分析与设计, 这当然无可非议,但是工具可以减轻重复劳动,并不能帮助我们思考,尤其不能帮助我们站在读 者的角度处理问题。你必须站在读者的角度考虑元素安排,图的重点要突出,表达的思想要清楚, 图的布局应该和谐,读者的眼睛应该很容易跟踪图的流向,并且和文本的顺序有一定的内在联系。 发起事件的关键状态(比如拨号)应该放在左上角,而不要被其它各种状态包围。对于一些特殊 的异常情况(比如超时提示),也不要和其它状态排列在一起。 在画状态转换图的时候,一个需要时刻铭记于心的原则是,仅仅忠实地事无巨细的画出转换 关系是不够的,如果你画了一个混乱的图,相当于你什么都没画,因为文本已经提供了完整的描 述。如果有了一个混乱的图而没有文本,那也就更不需要操心书写文本了,因为这相当于你什么 都没做。从这个角度来说,设计做到一定的层次更像一种艺术。 2,,,,命名状态和事件命名状态和事件命名状态和事件命名状态和事件 状态与事件在命名上要有明显的区分,基本的命名原则如下: 事件的名称应该是动词或者名词(或者作为动词或名词使用的短语),需要能够清晰地体现 事件在特定时间发生和结果,比如挂机(hang up )。 状态的名称应该是形容词或名词(或者作为形容词或名词使用的短语),能够清晰地体现能 持续一段时间的状态。比如灯泡的状态:开(on )或者关(off )。 用形容词命名状态的一个重要的类型是分词:使用分词可以使一个动词转换成一个形容词, 现在分词一般是动词加 ing ,过去分词与过去式是一样的,比如:connected 。当然也有一些不规 则动词的过去式是不同的,比如:broken 。下面的表示一些命名状态和事件的很有用的词汇。 状态状态状态状态 事件事件事件事件 开始(start ) 开始(start ) 在标题段落中(in header segment ) 创建(create ) 目标获取(target acquired ) 获取目标(acquire target ) 已获得密码(got password ) 得到密码或密码(get password or just password ) 已察觉入侵(detected intrusion ) 察 觉 入 侵 或 入 侵 ( detect intrusion or just intrusion ) 已接收到确认或已确认(received confirmation or confirmed ) 接受确认或确认(receive confirmation or just confirmation ) 等待确认(awaiting confirmation ) 情况变更(status changes ) 已做(done ) 中止(abort ) 其中,开始(start )同时出现在两栏中,因为它经常对事件和状态都有用,但不能描述同一 个对象,“开始(start )”是一个好名称。作为状态:它描述没有经历任何事件的状态。作为事件: 作为状态图描述一个发起一个过程的事件。 3,,,,状态转换的状态转换的状态转换的状态转换的四种解释四种解释四种解释四种解释 状态转换有一些基本的不确定性,可以有意用下面的四种方式中的一种来解释。 从任何状态下出现的事件从任何状态下出现的事件从任何状态下出现的事件从任何状态下出现的事件,,,,是当对象处于那个状态时的是当对象处于那个状态时的是当对象处于那个状态时的是当对象处于那个状态时的:  唯一可能事件唯一可能事件唯一可能事件唯一可能事件:没有显示的事件不可能发生。  对象如何响应的事件对象如何响应的事件对象如何响应的事件对象如何响应的事件:没有显示的事件没有响应,或者不可能在此发生。  唯一允许的事件唯一允许的事件唯一允许的事件唯一允许的事件:没有显示的事件,系统必须阻止它发生。  对象对事件的期望响应对象对事件的期望响应对象对事件的期望响应对象对事件的期望响应:没有显示的事件,要么不可能在此发生,要么期望的相应忽略 了它。 前两种解释是对于问题域的描述:第一种是对于所有可能事件集合的描述;第二种是对因果 关系的描述。后两种解释作为描述性陈述:针对的是设计决策,或者需求规格说明。 如果你正在写自动电话拨号机的设计文档,那么前面的自动电话拨号机状态图纯粹就是描述 性的,开发者会根据这个状态转换关系设计出满足需要的设计来。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 120 - 如果你正在写控制电话公司连接呼叫的设备的设计文档,那自动电话拨号机状态图就是说明 性的,文档需要据此加上其它的描述性陈述,说明对于不同的电话线、连接传送、接收通道都是 些什么事件,创建关于信道状态变更的需求说明。 为了避免模糊性,在描述的时候可以加上某些情态动词,比如:“必须”或者“应当”。必要 的时候,也可以使用命令方式描述事件和响应,比如: “按下数字 2..9 :添加数字到区内号码。” 要注意描述的模糊性,往往是产品开发最后失败的根源。 八八八八、、、、产品行为问题的归纳总结产品行为问题的归纳总结产品行为问题的归纳总结产品行为问题的归纳总结 任何软件都需要关注引发期望结果的行为。期望结果往往是某种行为的功能,所以在软件需 求模型中,可能有三种类型的问题需要书写:  自发行为自发行为自发行为自发行为:这些行为在问题域中发生,比如:按下复印机按钮。  直接行为直接行为直接行为直接行为:软件能直接初始化的行为,比如复印机加电。直接行为是共享现象,它同时 是软件的行为,也是问题域的行为。  间接行为间接行为间接行为间接行为:由其它行为引发的行为。 相同的行为并不总是引发相同的结果,比如打印机滚筒可能成功地输送一张纸,也可能失败。 “行为”和“事件”还是有区别的,时间表达的是一个很短暂的行为,通常反映了某种触发。而 行为是更宽泛的,具有明确的开始和结束,每个行为需要书写的信息如下:  因果关系类型因果关系类型因果关系类型因果关系类型:不是使用诸如自发、直接、间接这些稍微深奥的词汇,而是简单的把每 种类型的行为组合到一起,比如:微处理器可以产生下列行为。  行为中涉及的所有类型对象行为中涉及的所有类型对象行为中涉及的所有类型对象行为中涉及的所有类型对象:实施那个行为的对象是什么?被行为影响的对象是什么?  行为所具有的参数行为所具有的参数行为所具有的参数行为所具有的参数:行为的属性从一个实例到另一个实例有所不同,它需要的参数是什 么?会有哪些状态发生改变?  在间接行为的情况下在间接行为的情况下在间接行为的情况下在间接行为的情况下,,,,引发此行为的条件或事件引发此行为的条件或事件引发此行为的条件或事件引发此行为的条件或事件:什么时间发生?什么对象引发的?  如果只有一个特定的条件为真如果只有一个特定的条件为真如果只有一个特定的条件为真如果只有一个特定的条件为真,,,,此行为就一直发生此行为就一直发生此行为就一直发生此行为就一直发生:可以描述:“只要……就发生。”  行为的持续时间行为的持续时间行为的持续时间行为的持续时间:除非时间足够短可以忽略,就需要表述行为持续时间。  行行行行为的所有可能结果为的所有可能结果为的所有可能结果为的所有可能结果:对于每一种被影响的对象,可以有什么效果?  如果可能存在一个以上的结果如果可能存在一个以上的结果如果可能存在一个以上的结果如果可能存在一个以上的结果:软件是如何或能否监测到哪个行为真正发生? 从本质上看,前面所讨论过的用例文档就是描述一种行为(也是一种功能),其中前置条件 与后置条件具备某种定义参数和状态的作用。如果某些情况下需要专注于行为的描述,也可以单 独写出行为文档,比如: 收集货物收集货物收集货物收集货物 参与者 货物收集者,货物,存储位置 参数 一个或多个货物清单以及它们的存储位置 什么时候发生 已打印的订单(显示获取清单和存储位置)是在打印机 A 处,以及货物收集者 分遣订单 持续时间 从货物收集者分遣订单开始少于 5 分钟,在多数情况下如果超过 10 分钟就有 问题。 可能的结果 1)货物收集者找到货物并带他们到包装站。 2)货物收集者寻找货物,但没有找到。 3)货物收集者没有找到订单/或没有搜索货物。 10 分钟后,可以认为 2 或者 3 发生了。 描述行为应该简明扼要抓住重点,在行为比较复杂情况下也可以使用顺序图或者状态图来表 达,并辅以相应的文档。我们没有必要为设计师呈现一个犹如智力拼图一般的行为描述。事实上 在功能需求与非功能需求的描述中,已经定义了大部分行为问题。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 121 - 4.4 设计设计设计设计模型模型模型模型和实现模和实现模和实现模和实现模型型型型 一一一一、、、、从概念从概念从概念从概念模型到设计模型模型到设计模型模型到设计模型模型到设计模型 设计模型是对概念模型的精练,它涉及了可执行平台的特殊细节,因此设计模型必须对许多 重要的细节进行规划,它必须满足多种实现语言和技术的需求。它需要组织跨越多个处理节点间 的系统部署,由于设计模型比概念模型更加复杂,因此,把概念模型与设计模型分开是明智之举, 一般用概念模型来定义高层结构,用设计模型来细化结构、合并细节。 由于设计模型是对概念模型的细化,因此希望概念模型中的结构继续在设计模型中得以保 持,这也意味着在设计元素结构中的包与分析元素结构中的包相对应,同时也可以发现设计模型 中的类也是派生于概念模型中的类。同样,在分析阶段标识的用例切片也会在设计阶段细化。 事实上,概念模型中的结构都是与平台无关的,但是设计模型的结构可以分成两个部分:  最小化设计:对应于与平台特性无关的概念模型部分,是系统能力的基本表达。它包含 相应的边界类、控制类和实体类。  平台相关部分:与平台技术有关的部分。 这种清晰的分离,对于摆脱平台的限制极其重要,它也将改进系统的可移植性。另外,在设 计模型中还会包括在边界类、控制类和实体类之间传递消息或者数据的类,以及负责处理异常的 类等,因此,设计模型会比概念模型有更多的设计元素。 上面提到的边界类、控制类和实体类,或者它们的组合,将演变为设计中的构件,这些构件 将拥有来自于概念类职责的接口。我们称这些接口为最小化设计接口最小化设计接口最小化设计接口最小化设计接口。 而对于平台相关部分,将拥有平台相关接口平台相关接口平台相关接口平台相关接口,比如 web 的 HTTP 接口,或者 Web Service 的 远程接口等。 二二二二、、、、用例模型横切于模型用例模型横切于模型用例模型横切于模型用例模型横切于模型 当我们实现一个用例的时候,必须表示出所需要的类,以及这些类的特性(属性、方法和关 系),如果我们引进一个“用例切片用例切片用例切片用例切片”的概念,这个切片把功能实现部分功能实现部分功能实现部分功能实现部分放在一个模型中(设计 模型),而通用的和复用通用的和复用通用的和复用通用的和复用的部分则保存在一种非特定用例部非特定用例部非特定用例部非特定用例部分分分分中,把所有这些切片的叠加将构成 系统的设计模型。这种方式,将会使问题比较清晰,避免不必要的混乱和遗漏。我们针对上面的 用例切片的概念,可以进一步细化和规范。一个用例切片应该包含三个方面的内容:  协作协作协作协作:我们知道,用例是从系统的外部来描述系统的,但是协作是从内部视角来描述系 统,在描述上可以表达类与类之间具体的协作关系。同时还必须对它命名,一般来说, 协作的名字需要和用例一致,以防止理解上的歧义。  类类类类:这些类是特定于该用例实现,是本质上与其它用例分离的部分。  方面方面方面方面:是该用例实现的时候对原有类的扩展,也是可能发生缠绕的部分。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 122 - 用例切片可以层次化的组成设计模型,这个概念最好的比喻就是投影仪,切片类似于叠加在 一起的幻灯片,设计模型类似于屏幕,我们可以独立的制作每个幻灯片,但需要一些协调工作, 以保证它们内容的一致性,每个幻灯片的内容可以不同,但显示的位置应该在规定的地方。软件 开发是围绕着模型的构建进行的,大概的步骤如下:  首先通过用例模型捕获涉众关注点。  然后把用例模型提炼为概念模型,这就是从高层视角对系统的描述。  通过设计模型来决定系统会运行于什么平台。  实现模型就是系统的代码实现。 设计系统应该逐个用例的进行,通过日益增多的模型来获取用例,提炼并且实现它。当完成 一个用例的工作以后,就可以在一个包中(称之为用例模块)交付与这个用例相关的所有工件(包 括分析、设计、实现和测试文档)。一个用例模块由每种模型的用例切片组成。用例模块可以单 独开发,然后再合并成完整的系统。也就是说当构建一个系统的时候,需要逐个用例的进行构建, 首先识别系统用例,然后一次一个地处理用例,详细描述它、分析它、设计它、并且实现它,当 你在不同的模型中构建每个用例的时候,也会在不同的模型中更新相应的用例切片,这些用例切 片如下图中阴影所示。 例如当工作于一个用例切片的时候(例如设计阶段用例切片),应该从这个用例切片的上游 (对应于分析阶段用例切片)开始做一些精化,添加一些内容。因此每个下游用例片都比上游切 片更大也更复杂。模型也同样的是下游模型比上游模型更大而且更复杂,这是因为模型也需要考 虑越来越多的问题。 1,,,,保持用例模型中的结构保持用例模型中的结构保持用例模型中的结构保持用例模型中的结构 通过各种不同的模型逐渐对用例切片进行精化,以完成对系统的开发,我们主要是通过这些 模型的用例切片,使所有的下游模型都保持用例模型中的结构,这样做的理由如下:  帮助我们理解下游模型帮助我们理解下游模型帮助我们理解下游模型帮助我们理解下游模型:可以通过查看相对应的用例,识别每个用例的切片到底完成什 么功能。  便于在前后模型中转换便于在前后模型中转换便于在前后模型中转换便于在前后模型中转换:软件开发并不是从用例到实现的线性过程,而是在前后不同的 模型中转换和更新,保持模型中的结构,有助于在受控环境下无缝的转换模型,先完成 一部分用例描述,接着转入分析来研究交互,然后回过头来完善用例,以澄清原来缺少 的涉众关注点细节。也许在设计的时候,还可能回到需求来澄清该用例描述的特定需求, 在建立系统架构基线的时候,也可能会前后切换。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 123 -  保持模型一致保持模型一致保持模型一致保持模型一致:如果下游模型与上游模型不同,您就可能需要花时间去理解完全不同的 结构,并且维护这种模型之间的映射关系,由于并没有一种形式化的方法解决这个问题, 因此难以在模型中保持一致。 还需要说明的是,并不需要把设计结构引入到用例模型里面去,用例只是用涉众可以理解的 方式结构化了他们的关注点,而设计将反映出涉众如何感知系统,用例驱动了设计模型,这就是 为什么我们把这种开发方式称之为用例驱动开发的原因。 2,,,,用例模块包括用例结构用例模块包括用例结构用例模块包括用例结构用例模块包括用例结构 既然是基于所有针对各个用例的不同模型的切片进行了工作,就可以把这些切片放到一个单 独的包中,我们称这样的包为“用例模快用例模快用例模快用例模快”,这个模块包括了各种模型的切片以及它们的依赖关 系,当开发一个由用例模快组成的系统的时候,我们可以把每个用例模块视为一个单独的项目, 在硬盘或者某个中心开发库中,某根目录对应于用例模块,而子目录对应于用例模块中的每个切 片。 下图是一个用例模块的示意图,而表示的<> 的依赖关系,表明上游模型得出的一个 下游模型存在着一些规则,作为开发团队必须遵循的开发指南和原则。这种用例模块,也可以成 为软件产品线的一个元素。进一步抽取掉模块的领域相关部分,使它具备通用性,并且适当的命 名,加入应用场景和使用案例,就可以发展成一个用例模式用例模式用例模式用例模式。这种用例模式对用例切片的复用极 其有帮助。 在软件开发中,可可可可追朔性追朔性追朔性追朔性是一个很重要的概念,可追朔性表明上游模型元素与下游模型元素 存在着链接,这可以帮助你判断是不是所有的需求都已经实现,需求的变化会对什么产生影响, 通常上游元素的修改会影响到下游元素。 如果上游和下游元素遵循着不同的结构化原则,结果就会花费很多精力维护模型间的可追朔 性,这在大型项目中显得尤其困难,但在我们保持模型结构的原则下,可追朔性的维护工作将显 著减少,这是因为模型本身就是基于相同结构的。 过去的设计方法,易变形和可维护性问题的解决,是在分析之后的设计过程中完成的,造成 了设计模型与概念模型之间的可追朔性很差。但在面向方面的软件设计理念中,这个问题的解决 是在分析的时候就提出来了,通过模型的一致性达到了问题解决模型的可追朔性,这就大大提升 了软件设计的质量。 3,,,,用例模块之间的关系用例模块之间的关系用例模块之间的关系用例模块之间的关系 正如用例之间存在关系一样,用例模块之间也存在类似的关系,用例模块之间的关系于用例 之间的关系相同,也就是泛化、包含与扩展。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 124 - 用例模块的关系可以用于两个互补的方面,从正向工程的视角,可以从用例之间的关系派生 出用例模块之间的关系,换句话说,用例模块保留了用例之间的关系,具备了关系的一致性。 其次,用例模块之间的关系,可以作为检测访问违例的手段,也就是检查某个用例模块中的 一个元素与另一个模块的另一个元素之间是不是存在非法关系。通过观察用例模块之间的关系, 可以尽早发现违例,也可以尽早进行必要的修改。 4,,,,合并和配置用例模块合并和配置用例模块合并和配置用例模块合并和配置用例模块 完成了基于各个用例模块的工作以后,就可以把它们合并到各个发布版本中,假定我们为某 次发布版本分配了三个用例模块,就可以有一个名字为 build1 的合成用例模块。 如果由于某种原因发现“处理等候列表”有许多缺陷,可以去除这个模块,构成一个发布版 本 build2 先供用户使用,而我们单独对 build1 进行排查和测试。 在传统的做法中,是直接修改原有类的代码,可能会把废弃的代码留在系统中,也会使原有 类引入更多的混乱,同时也可能引入更多的错误。 用例模块是从不同的视角描述系统,这种从一个模型到另一个模型构造的关系,有助于维护 模型的一致性,也更容易指导下游模型元素的导出。请注意,不要逐个模型的构造系统,而应该 逐个用例模块的构建,用例模块是软件开发工作的一个单位,它把各个不同模型中与某个用例相 关的元素局部化于一个单一包中,可以采用迭代的方式独立的开发。 4.5 使用方面技术解决关注点分离问题使用方面技术解决关注点分离问题使用方面技术解决关注点分离问题使用方面技术解决关注点分离问题 在软件开发中,使关注点保持分离是十分重要的,它可以帮助你把复杂的问题分解为更小的 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 125 - 部分,并独立的解决它们。当它发展为大型系统的时候,这是构建它的唯一方法。如果没有办法 使关注点保持分离,随着系统的演化,复杂性将会不断增加,另一方面,通过保持关注点分离, 系统也会变得更易于理解、维护和扩展。 新的模块化技术应该能够包括分析、设计、编码和测试的整个生命周期中,使横切关注点保 持分离,要达到这种模块性,就需要有两个方面的技术:关注点分离技术关注点分离技术关注点分离技术关注点分离技术,,,,以及关注点合成技术以及关注点合成技术以及关注点合成技术以及关注点合成技术。 这两种技术的合理应用,是面向方面架构设计的关键。 使用用例切片和用例模块进行系统开发,能够对横切关注点进行清晰的分离,在演化和扩展 中,使每个切片更容易复用,而且这种切片之间不会互相干扰,使用这一方法,将获得更好的可 维护、可扩展、以及更高的性能。 事实上在我们架构设计中,一直在强调封装变化、基于扩展的设计等等,但面向方面把这些 思想进一步提升,变成了一种方法论和技术实现的结合,而且,面向方面的编程(AOP),又使 这种思想的实现变得容易和可能,但是,这种分离与合成的广泛使用,如果没有仔细的、经过慎 重考虑的、无歧义的设计描述相结合,将会使整个产品结构变得极其混乱更加难以处理,所以我 们必须从方面的视角,从分析到设计全面的、规范化的思考和描述问题,也就是说,面向方面的 问题需要全方位的解决才可能发挥作用,这就需要仔细研究面向方面解决问题的基本思路。 同样,这种研究对于提升普通的语言系统设计质量也非常有意义。 一一一一、、、、使关注点相互分离使关注点相互分离使关注点相互分离使关注点相互分离 尽管在分析的时候我们强调了关注点分离,但是,一旦进入设计,这种分离仍然会不可不可 避免地造成交叠,我们下面来研究两种横切关注点及其问题: 1,,,,对等对等对等对等((((peer ))))关注点关注点关注点关注点 所谓对等关注点,就是关注点相互独立,其重要性也不能相互比较。比如 ATM 机中,存款、 转账和取款都是对等关注点。又例如一个简单的例子,酒店管理系统预订房间(Reserve Room )、 登记入住(Check In Customer )、结账离开(Check Out Customer )都是对等关注点。 对等关注点本身并不互相依存,但在系统中实现对等关注点的时候,就会出现明显的交叠, 如下图所示。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 126 - 这张图可以看出构件在保持对等关注点分离方面的局限性,结果就是结构的混乱状态和分 散。这种交叠又包括两种情况: 1))))缠绕状态缠绕状态缠绕状态缠绕状态((((tangling ):):):):一个构件包含满足不同关注点的实现(亦即编码),比如上图中, Room 包含了三个不同关注点的实现,这就意味着开发人员需要理解一组不同的关注点,构件也 变得更加不易被理解。注意不要把缠绕状态与复用混为一谈,复用是指相同的代码和行为在不同 的上下文中使用,而一个构件中缠绕着多个关注点,使这种模块更加难以复用。 2))))分散分散分散分散((((Scattering ):):):):一个关注点的实现分散在多个构件中,比如:关注点登记入住(Check In Customer ),在四个构件中添加了额外的行为,如果需求发生变化,或者设计发生修改,就必 须修改多个构件。更重要的,分散使系统内部组织不易理解,例如,不容易通过阅读一个(或者 几个)构件的源代码来理解系统,如果一个关注点类发生变化,会有多个类需要修改,更不容易 进行改进,对大型系统尤其如此。 2,,,,扩展扩展扩展扩展((((extension )))) 在用例的结构化分析中,有时候会引入扩展,这就是第二种需要研究的横切关注点。扩展是 在基础(base )构件上定义的构件,用来表示附加的服务或者功能。 例如,酒店管理系统中有一个功能“待分配房间的预订人等候名单”,如果没有房间,系统 就会把客户放进这个“等候名单(Waiting list )”,因此,这个“Waiting list ”就是“预订房间(Reserve Room )”的扩展。把扩展分离出来,可以使问题容易理解,这就就不至于被过多的问题所纠缠。 但是,虽然描述问题的时候这种扩展很有意义,但实现的时候却有问题。 下图把 Reserve Room 作为基础组件,在添加 Waiting list 扩展组件的时候,还需要在 Reserve Room 中增加一个代码段,来连接或者调用 Waiting list ,这个代码段被称为粘合代码(glue code ), ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 127 - 结果,在发生更改的时候,这种粘合代码对原来的代码段形成很大的干扰,比如,如果需要 添加新的扩展,在原来的程序中适当地方就要加入对应的粘合代码,如果系统设计的不合理,系 统的扩展性就会受到很大影响。 3,,,,分离关注点的解决方案分离关注点的解决方案分离关注点的解决方案分离关注点的解决方案 人们一直在研究关注点分离的技术,要达到这种模块性,需要两方面的基础: 关注点分离技术关注点分离技术关注点分离技术关注点分离技术((((Concern Separation Technique )))):为了使关注点保持分离,必须对关注点 进行建模和结构化,同时还需要一种在设计和实现阶段保持分离的技术。 关注点合成机制关注点合成机制关注点合成机制关注点合成机制((((Concern Composition Mechanism )))):某些时候,需要把关注点合成,这 需要一些执行自动化方法,比如需要的时候,转而执行扩展。 现有的类、模块等技术可以在一定程度上使关注点保持分离,但是出现贯穿多个类和组件的 横切关注点的时候,就需要另外的方法来实现模块化了。 二二二二、、、、使用方面技术解决关注点分离问题使用方面技术解决关注点分离问题使用方面技术解决关注点分离问题使用方面技术解决关注点分离问题 面向方面是使横切关注点更好的分离的一组技术,所以有时候也称高层关注点分离。而 AOP 提供了面向方面的实现基础,比较典型的是 Java 语言的一个扩展 AspectJ ,它提供了关注点分离 和模块化的新构造,主要是: Intertype (自动排版):声明允许把新的特性(属性、方法及关系)合成到已有的类中。 Advices (通知):提供了一种在 AOP 中通过 pointcut (切割点)指定的扩展点上扩展现有操 作的手段。 Aspects (方面):只是一种构造快,用于组织 Intertype 声明和 Advices 。Aspects 可以是其它 Aspects 的泛化。 AOP 的合成机制使你可以在类的外部定义方法,这个概念在大多数人来说还是新的,但是 如果不加限制的在现有操作中插入 Advices ,或者在现有类中加入 Intertype 声明,最终反而会得 到一个难以扩展的、缝补出来的系统。 AOP 提供的强大的合成机制,可以更好的分离关注点,更好的实现模块化。因此,必须认 真构思,到底是把行为设计成类还是方面呢?也就是说,如何分清关注点是源于哪个方面或者类。 这就需要在分析和设计中仔细思考 三三三三、、、、通过叠加用例切片来构建系统通过叠加用例切片来构建系统通过叠加用例切片来构建系统通过叠加用例切片来构建系统 为了确保从分析、设计到实现全的过程中观注点保持分离,我们必须有一种形式化的方法来 表达这种分离结构,并且能够利用这种表达方式无歧义的研究、调整以及实现。从整体上看,我 们把整个系统分为元素结构与用例结构两部分,也就是说一个模型可以包括两个结构: 1))))元素结构元素结构元素结构元素结构 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 128 - 这个结构用来标识元素与组织元素。一个弹性架构的设计模型,是由层次化的元素组成(类、 接口、包),这个层次结构可以称之为元素结构。 2))))用例结构用例结构用例结构用例结构 用来定义这些元素内容的结构。用例结构描述哪个用例切片(或者叠加图)放在第一位,哪 个放在下一个等,这将会构成一个结构,它是用例切片的层次化结构, 我们可以使用例切片与元素结构相互分离,它构建在元素结构之上,用来定义扩展的内容, 这种合成如下图所示。 在图的右边,可以看到用例结构,为简单起见,这里只列出了一个用例切片,它可以用构造 型《use-case slice 》表示。用例切片包含要合成(通过方面编织)到模型(包含到元素结构的引 用)中的元素扩展,用方面(aspect )来表示。 正如图中所看到的那样,元素结构仅仅是一种标识元素位于模型何处的空盒子,它的具体内 容(例如行为),将在合成的时候通过用例切片来填充。 如果说早期的概念,开发人员必须从不同的用例中收集每个类的职责,然后才能开发这个类 的话,必然引发缠绕和分散,现在把用例结构从元素结构中分离,就可以使用例的分离得以保持。 四四四四,,,,使用对等用例保持分离使用对等用例保持分离使用对等用例保持分离使用对等用例保持分离 基于上面所讨论的分析方法,在对用例进行分析以及建立用例切片的时候,就需要非常清晰 的提取出每个用例到底有哪些类是该用例独占的,哪些类只是部分定义的,需要表达成扩展(方 面)。我们还是讨论关于宾馆的的例子,现在可以通过一个矩阵研究三个用例中的切片使用情况, 我们得到了 ReserveRoom 切片、CheckInCustomer 切片和 CheckOutCustomer 切片,以及它们相 应的概念类。 类 用例切片 顾客顾客顾客顾客 屏幕屏幕屏幕屏幕 工作人工作人工作人工作人 员屏幕员屏幕员屏幕员屏幕 预订预订预订预订 房间房间房间房间 登记登记登记登记 入住入住入住入住 结账结账结账结账 离开离开离开离开 记录记录记录记录 信息信息信息信息 房间房间房间房间 Customer Screen Staff Screen Resrver Room Check In Check Out Reservati on Room 1 预订预订预订预订 房间房间房间房间 Reserve Room ×××× ×××× ×××× ×××× 2 登记登记登记登记 入住入住入住入住 Check In Customer ×××× ×××× ×××× ×××× 3 结账结账结账结账 离开离开离开离开 Check Out Customer ×××× ×××× ×××× 缠绕状态缠绕状态缠绕状态缠绕状态 1 2,3 1 2 1 1,2 1,2,3 每个用例切片都包含针对用例实现的、只是其中一部分的类定义(也就是类扩展),如果你 想得到完整的类定义,需要做的事情就是合并类扩展(把同名字的类合并在一起)。比如三个切 片都有个 Room 扩展,那么可以合并成一个扩展的 Room 类。 五五五五,,,,使用扩展用例保持分离使用扩展用例保持分离使用扩展用例保持分离使用扩展用例保持分离 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 129 - 也可以使用扩展使用例保持分离。假如需要在“预定房间”的用例中加入一个功能,当预定 房间的时候如果没有空房间,则把客户加入等候列表,以此生成一个用例“处理等候列表”(Handle Waiting List ),这时候可以使用扩展用例来实现,也就是说“处理等候列表”从基用例“预定房 间”扩展。 基用例必须声明若干新的规则扩展点(Extension Points ),扩展用例是在不改变基用例的前 提下,向它的一组扩展点中添加行为。 扩展用例会插入一组活动,这组活动就是扩展用例事件流,它负责把客户放进等候列表,相 对于基用例,是一个独立的关注点。 过去这种思考方式由于难以实现而不太常用,但在 AOP 中可以很好的实现这种模型,在 AOP 中,join point(连接点)是与扩展点相对应的概念,join point 是使用事先定义的语法(调用方法、 异常等)说明程序执行流的一个点。而 pointcut 是比扩展点更大的概念,可以一次在多个类中定 义多个扩展点(也就是 join point )的引用,这对于一些横切于多个类的基础机制(例如认证、 日志等)特别有用。 在 AOP 中,一个扩展用例事件流将被实现为 advice(通知),它等价于用例切片中的术语“操 作扩展”,这是对原有操作的模块化扩展,用于执行不同于操作主要职责的行为。 对于上面的用例,可以画出切片图如下,在元素结构中多了一个 WaitingList 类,它是 HandleWaitList 用例实现所需要的类。 类 用例切片 顾客顾客顾客顾客 屏幕屏幕屏幕屏幕 工作人工作人工作人工作人 员屏幕员屏幕员屏幕员屏幕 预订预订预订预订 房间房间房间房间 房间房间房间房间 等待等待等待等待 列表列表列表列表 Customer Screen Staff Screen Resrver Room Room Waiting List 1 预订预订预订预订 房间房间房间房间 Reserve Room ×××× ×××× 2 处理处理处理处理 列表列表列表列表 Handle Waiting List √√√√ √√√√ ×××× ×××× ×××× 缠绕状态缠绕状态缠绕状态缠绕状态 1,2 1,2 1 在补充位置定义切割点在补充位置定义切割点在补充位置定义切割点在补充位置定义切割点 2 2 表中 HandleWaitList 用例切片还有两个操作扩展,分别定义在类 CustomerScreen 和 ReserverRoom 中,如果想完成类定义,或者完整的类的操作,就需要合并这两个类的切片。 4.6 基于用例切片使对等用例保持分离基于用例切片使对等用例保持分离基于用例切片使对等用例保持分离基于用例切片使对等用例保持分离 下面,我们需要对用例模块和用例切片的描述作更深入的讨论。在前面分析阶段产品建模中, 我们已经对涉众关注点进行了正确的建模,接下来就希望在设计和实现阶段保持这种分离,前面 我们已经引入一种新的“用例切片”(use case slice )的模块化单元来保持这种分离,用例切片包 括每个用例实现特定的类,以及现有的类的扩展。 在这样的设计模型下,我们就会有一个元素模型,这个模型事实上是一个弹性架构,包括层、 包、子系统的分级元素组织,而且具备最基本的组织和关系。另外我们还会有一个用例结构,利 用用例切片在元素模型的元素上叠加各种行为。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 130 - 过去,开发人员直接基于元素结构来实现系统,元素结构内包括各种行为,其结果就是关注 点与这些元素互相缠绕。基于用例切片的概念,开发人员就可以互相独立的实现每个用例,而让 开发环境来组合用例切片,构成设计模型中完整的元素集。对这个问题的深入理解,即使不使用 面向方面的编程语言,也同样可以实现这些思想。 我们已经讨论过,基于用例可以在需求阶段使关注点保持分离,但这还不够,我们的问题是 必须在设计和实现阶段继续保持这种分离。上面我们已经分析了,虽然对等用例之间并没有什么 关系,但它们的实现会调用一些共享的类,或者在类的某些部分在用例之间共享和复用,这样就 发生了缠绕。 用例切片是对模型中特定于某个用例的部分进行模块化,它使用方面作为合成机制,在这些 共享的类的基础上,扩展了特定于某个用例的特性(属性、关系和操作)。 一一一一、、、、实现对等用例实现对等用例实现对等用例实现对等用例 从用例建模的角度,对等用例之间没有任何关系(即不存在包含、扩展与泛化关系),这样, 就有可能把细化对等用例交给不同的人,但是,当实现对等用例的时候,会发现它们影响了相同 的类(这也是称之为“对等”的原因)。这中间就会出现协调上的问题,所以,当开始实现对等 用例的时候,我们仍然希望能够独立的、并行的工作。 1,,,,通过协作实现用例通过协作实现用例通过协作实现用例通过协作实现用例 用例的实现一般建模为协作协作协作协作,用例是从系统外部的视角来描述参与者与用例的交互,而并不 描述系统内部的类是如何通过协作来实现用例的。协作定义了一组类的实例,通过协作进行建模, 使它们相互协作完成任务,这个任务可以是具体应用的实现,也可以是基础结构的实现。所以用 例和协作是事物的两个方面,用例是从外部视角描述系统,而协作是从内部视角来描述系统。 下图中“内部系统视图”代表一个实现,协作用虚线箭头表示,表示一个实现关系。 一个协作标识了扮演实现用例的的不同角色的类,例如下图的角色由 Room 类来扮演,协作 需要类的一组特性(属性、方法及关系)来扮演这些角色,我们也可以在 Room 的基础上加入新 的操作(行为),比如:更新提供数据(updateAvailability() )、取回数据(retrieve() )等。 实现一个用例,需要标识出参与实现的类及所需的角色,通过分析用例文档中的每一步骤, 判断需要哪些类,为不同的类规定角色和职责,并且分析是如何调用其它类的实例的,也可以通 过交互图表达消息传递的顺序。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 131 - 2,,,,对等用例实现之间的交迭对等用例实现之间的交迭对等用例实现之间的交迭对等用例实现之间的交迭 在对等用例的情况下,如果有的类只是针对自己,是不存在交迭操作的。但是前面已经讨论 过,以“登记入住”和“预订房间”两个用例讨论,其中有的类是针对自己的,比如 ReserveRoomHandler (预定房间处理类)和 CheckInHandler (登记入住处理类)。但是,也有的 类是共享的,比如 Room (房间类),这样就构成了交迭(交迭类)。另外,Room 类的扩展中的 “取回数据”操作(retrieve ())也将用于所有的协作,这也构成了交迭(或者称之为交迭操作)。 在没有面向方面的技术的时候,开发人员必须在实现每个类之前,整理它的扩展,这些类有 些是原有的类,有的需要在其中添加新的特性。有的是新的类,原先还没有标识出来,因而设计 人员就需要在设计模型适当的包中创建它。结果就使用例的实现变得分散,类模型变得混乱,正 是这种状况,就需要通过一种方法,使特定于每个用例的部分保持分离。 二二二二、、、、使用例特定部分保持分离使用例特定部分保持分离使用例特定部分保持分离使用例特定部分保持分离 为了使模型中的用例在实现的时候仍然保持模块化,需要引入一种新的模块化单元,它包含 了特定于某个用例实现的元素,我们把这种模块化单元称之为用例切片。用例切片的特点是它的 内容针对的是某个用例,是对模型(这里是设计模型)的切割或者切片。 我们已经说明了用例切片应该包括以下个内容:  描述用例实现的一个协作协作协作协作。  特定于该用例实现的类类类类。  特定于该用例实现的对原有类的扩展类的扩展类的扩展类的扩展。 为了把这个概念搞清楚,我们用“预定房间”这个用例来举例说明。对于每个用例而言,在 设计模型中都有一个用例切片,用例“预定房间”就有相应的“预定房间”用例切片,如下图所 示。 它是一个特定类型的包,用构造型<> 表示,用例切片的名字与对应的用例相 同,协作的命名规范也是相同的,在具体的描述时,需要用图形把各个类之间的协作关系表达出 来。 “预定房间”用例切片包含 ReserveRoomHandle 类和 Room 类扩展,在类中标示出相应的行 为,不论是类还是方面,一般都有多个。这就需要一个机制把 Room 类的片断合并到原有的 Room 类中去,这可以通过 AOP 的方面机制来完成,例如,通过 intertype 声明和 advice 来实现。 用例切片和幻灯片很近似,而且组合也是简单的把它们叠在一起。 对软件设计而言,设计模型的命名空间可以确保用例切片正确组合,下面我们将讨论怎么把 用例切片合并到设计模型里面去。 1,,,,合并特定于某用例的类合并特定于某用例的类合并特定于某用例的类合并特定于某用例的类 以 ReserveRoomHandler 类为例,看一看特定于某个用例实现的类是如何合并到设计模型中 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 132 - 的。假定我们使用层(layer )和包来组织设计模型,则可能有一个应用层,它包含一个客户应用 包,ReserveRoomHandler 类应该添加在这个包内。 在设计元素结构中,唯一的标识出 ReserveRoomHandle 类(图中的 1),此时这个类还是空 的,只有名字,我们可以给设计模型设置“命名空间”或者包名来赋予全名称,接下来可以利用 合成机制,把用例切片(图中的 2)中的 ReserveRoomHandle 合并到设计元素结构中。 在 AspectJ 中,可以很容易的在类的外部定义方法,然后通过 Intertype 声明把新的特性(属 性、方法及关系)合成到已有的类中。另一方面,后面我们要讨论的设计模式,也有一些实现这 种思想的模式,这都是值得我们去研究的。 2,,,,合并用例特定的类扩展合并用例特定的类扩展合并用例特定的类扩展合并用例特定的类扩展 下面要考虑如何在设计元素结构中现有类上合并类的扩展。在 AOP 之前,传统编程语言并 没有提供这个功能,现在 AOP 可以通过 intertype 声明,向原有的类添加特性,要添加在原有类 的特性,收集在一个类的扩展中,在下图中用“合并”箭头说明。 在我们的例子中,Room 类就是在设计元素结构中的原有类,它原本已经叠加在了设计元素 结构中,现在我们要叠加上额外的类扩展。假定,Room 类位于领域层的 room 包中,并表示出 了 Room 类,我们希望把“预定房间”用例切片的 Room 类扩展叠加到实际元素结构中的 Room 类中。 在 AOP 中,可以使用带 intertype 声明的 aspect 实现,这个 aspect 应用在用例“预定房间” 的实现中,所以我们把它命名为 ResverRoom ,下面表达了在现行的 Room 类中是如何添加这个 操作的。 //AspectJ 中的方面中的方面中的方面中的方面"预定房间预定房间预定房间预定房间"::::ReserveRoom.java // ReserveRoom 方面是位于应用层的包 app.customer 中(与 ReserveRoomHandler 等类在一个 包) ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 133 - package app.customer; import domain.room.Room; public aspect ReserveRoom{ // 它扩展的是 Room 类,、 // 并且在设计结构元素的 Room 类中添加了一个操作 updateAvailability() 。 public void Room.updateAvailability(){ // 具体代码, } } 在这个例子中,aspect (方面)包含了一个 intertype 声明,它是只针对一个类的,但是,通 常需要把多个操作合并到多个类中去,这就需要对这些 intertype 操作进行组织,也就是把相同类 添加的特性归集到一起,这种做法很有效。 另外,对每个独立的类所拥有的全部 aspect (方面)进行可视化和理解十分重要,对每个原 有类的 advice (通知)和 intertype 声明进行准确的建模极其有利,同时在标识出协作中的角色的 时候,这些类的扩展也会自然的派生出来。 4.7 使用使用使用使用 Pointcut 使扩展保持分离使扩展保持分离使扩展保持分离使扩展保持分离 在关注点分离问题上,除了考虑对等用例以外,还需要考虑扩展用例的问题。 扩展用例是用例的一种,它可以使额外的功能与现有的用例保持分离。扩展用例的实现需要 在原有的操作中定义额外的行为,这在传统的技术中往往会引起混乱。通过面向方面的技术,用 例切片可以使操作扩展与现有的操作保持分离,并且在编译或者执行期间由 Pointcut 在所执行的 点上执行。Pointcut 是可以参数化的,它可以在一个或者多个点中执行相同的扩展,这种参数化 可以与模版化结合起来,以解决更大范围内的问题。下面我们来讨论有关问题。 一一一一、、、、实现扩展用例实现扩展用例实现扩展用例实现扩展用例 在用例分析的时候,我们已经知道扩展(extend )的目的是在用例指定的扩展点处,添加额 外的行为(称之为扩展事件流),通过扩展 pointcut 来指定扩展点,我们可以使扩展在需求阶段 保持分离。 下面的简单例子是一个“记录日志”的用例,这是一个基础结构机制,它对用例“预定房间” 和“登记入住”进行扩展,如下图所示。 扩展“记录日志”的目的是,统计不同类型房间的请求次数,以及请求请求是否成功,这些 信息可以为后面分析酒店不同类型的房间受欢迎的程度提供基础。 用例的事件流如下图所示。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 134 - 二二二二、、、、使扩展用例的实现保持模块化与扩展使扩展用例的实现保持模块化与扩展使扩展用例的实现保持模块化与扩展使扩展用例的实现保持模块化与扩展 1,,,,用例实现保持模块化用例实现保持模块化用例实现保持模块化用例实现保持模块化 在扩展用例 Logging 的实现中,需要具备两个角色,一个是记录者记录者记录者记录者(logger ),另一个角色是 目标目标目标目标(target ),两个角色的工作解释如下。 记录者记录者记录者记录者(logger ):扮演这个角色的类是 LogStream ,负责把申请房间的次数保持到持久化的 数据库中去,它中间包含一个保留数据的 log() 操作。 目标目标目标目标(target ):在 LogStream 类把信息保留之前,首先需要获得请求信息,这需要由 ReserveRoomHandler 类来处理,它属于目标角色。只需要在其中添加新的扩展,就可以获得请 求信息。 现在要做的就是要识别出具体的执行点,把这个执行点上的操作合并到目标上,下图表达了 用例实现“预定房间”的交互信息。 当某个客户调用 ReserveRoomHandler 对象中的 makeReservation() 操作以后,将调用 retrieve() 操作。这也表明了扩展将在 makeReservation() 操作之中进行。 在这个交互的基础上,我们叠加了“记录日志”操作扩展,上图中用一个方框标记了这一部 分,这个方框也说明了额外的行为会在什么位置执行。它是在完成 Room. retrieve() 调用之后,在 makeReservation() 操作中发生的。实际上,不管 Room 对象中的“取出数据”(retrieve() )操作是 否完成,操作扩展都将调用 LogStream 对象中的 log() 操作,完成请求信息的存储工作。 下面我们来描述在面向方面的设计技术中,如何来标记这个扩展过程。 2,,,,操作扩展操作扩展操作扩展操作扩展 一个操作扩展是由位于某个操作内、特定于某个用例实现的行为组成的。在这个例子中,操 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 135 - 作扩展只用于调用 LogStream 对象中的 Log() 操作,操作扩展对应于 AOP 中的 advice 。 在标识执行操作扩展的位置时,必须注意两件事,也就是结构上下文和行为上下文。 结构上下文结构上下文结构上下文结构上下文:这个上下文是对调用方的描述,它描述的是操作扩展是叠加到元素结构中的什 么位置,也就是在哪个包、哪个类以及哪个操作上执行。在这个例子中,操作扩展是在 ReserveRoomHandler 类的 makeReservation() 操作中执行。 行为上下文行为上下文行为上下文行为上下文:这个上下文是对扩展方的描述,它在操作扩展中标示出一个执行点,表明操作 扩展是在什么位置对原有操作进行扩展。这个执行点与用例建模中的扩展点,以及 AOP 中的连 接点(join points )的概念是相对应的。在这个例子中,只关注了 ReserveRoomHandler 类的 Log() 调用,也可能你还会关注还有哪些类调用了这个操作。通常,执行上下文会牵涉到调用堆栈的概 念。 操作扩展声明操作扩展声明操作扩展声明操作扩展声明::::正是这样一些要求,操作扩展的声明应该表明操作扩展会对哪个现有的操作 进行扩展,所以操作扩展的描述应该包括以下三个部分: 结构上下文结构上下文结构上下文结构上下文++++行为上下文行为上下文行为上下文行为上下文++++操作扩展操作扩展操作扩展操作扩展 所以在上面的图中,操作扩展“记录日志”的声明就应该是: makeReservation() {after call (Room.retrieve()) logData } 说明: 第一部分 makeReservation() :说明操作扩展在哪个现有操作中执行,我们不需要把它表示成 ReserveRoomHandler. makeReservation() ,这是因为操作扩展本身就是放在一个 aspect 里的类扩 展中,所以只要标明操作扩展属于该类中的哪个操作就行了。 第二部分 call (Room.retrieve() Room retrieve :表示扩展点是位于 Room 类中的 retrieve() 操作。 这个表达式就是 AOP 中的 pointcut 。关键字 after 是一个修饰符,相对于 pointcut ,用来定义操作 扩展是在什么地方执行。这个操作扩展的语义是:其执行点在 makeReservation() 操作中,具体的 说就是 makeReservation() 调用 Room 实例中的 retrieve() 操作之后。 第三部分是 logData :说明操作扩展实际上是完成什么功能。 下图描述了完整的扩展用例切片“记录日志”,其中包括 Logging 协作,LogStream 类,以及 名字为 Logging 的 aspect 。注意,LogStream 类是位于 aspect Logging 之外的,这是因为, LogStream 类是为了增加日志功能而新加的。 3,,,,Pointcut 我们再仔细看一下表达式 call(Room.retrieve()) ,在调用 Room 类中的 retrieve() 操作的时候, 它直接引用了现有的 makeReservation() 操作中的扩展点,但这种直接引用是易变的,例如,如果 retrieve() 操作改名为 read() ,那么所引用的扩展点将失效。因此,这样对扩展点的直接引用并不 好。 更好的方法是,为扩展点起一个有意义的名字,说明当执行操作扩展的时候,是位于哪个原 有的操作,然后再单独定义一个实际扩展点。在这个例子中,主要是跟踪对于 Room 类的调用, 因此命名为 roomCall 更有意思。而你可以在扩展点的定义中使用这个名字。扩展点的命名实际 上就是定义 pointcut 。因此,扩展表达式声明就成为。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 136 - makeReservation() {after call () logData } 符号{} 说明,在操作扩展声明中的 roomCall 是一个参数,它被绑定为不同的值。这可以用 aspect 中的 pointcut 表达式来实现。另一方面,这个参数会应用在整体用例切片中,在这种情况 下用例切片就成为一个模版。 下图展示了修订后的“记录日志”用例切片,它采 pointcut 来引用位于 call(Room.retrieve()) 的连接点(也就是扩展名),名字为 roomCall 的 pointcut 的定义如下所示: roomCall= call(Room.retrieve()) 包含 pointcut 的用例切片的“日志记录”如下图所 示。 上图中的 Logging 的 aspect 实现如下: //AspectJ 中的方面中的方面中的方面中的方面"记录日志记录日志记录日志记录日志"::::Logging.java //Logging 方面是位于基础结构层的包 logging 中 package infra.logging.; // 这个方面也引用了 ReserveRoomHandle 类,因此需要把它 import 进来。 import app.customer.ReserveRoomHandle; // roomCall 包括到一个 Room 类的引用,因此需要把它 import 进来。 import domain.room.Room; public aspect Logging{ // 定义了一个名字为 roomCall 的 pointcut 。 pointcut roomCall(); // 下面是 roomCall 的定义,是用&& 连接符定义的更小的 pointcut 。 // makeReservation 中可能的连接点。 withincode (void ResveRoomHandler.makeReservation()) && call (void Room.retrieve()); // Room 中 retrieve() 操作可能的调用。 // 把操作扩展实现一个 advice , // 关键字 after 说明了操作扩展的位置在名字为 roomCall 的 pointcut 之后。 after ():reooCall(){ // 具体代码, } } 用例切片和方面技术为模型驱动架构提供了有效的解决方案,而面向方面的软件开发的理论 和实践,现在仍然在发展之中。在项目的早期建立一个弹性架构至关重要,其目标是让系统健壮 并且减少需求变化所带来的系统大量修改。它同时也让系统容易理解,从面向方面的观点来看, 一个弹性系统让你更容易定义 pointcut ,因为需要扩展的所有类和职责都被局部化了。 建立描述系统的模型结构是迭代的,首先从一些与平台无关的结构开始,然后逐个分析架构 重要的用例,这样做的时候,会逐渐补充和完善已经存在的结构,并且把平台相关的元素融合到 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 137 - 结构中,当遍历了所有的架构重要用例以后,就可以建立一个相当有弹性的架构了。 即使不使用面向方面的语言,我们也可以从面向方面的讨论中获取重要的营养,使我们的系统构 建的更加健壮而且有弹性。 4.8 从产品模型从产品模型从产品模型从产品模型到测试到测试到测试到测试模型模型模型模型 模型驱动的开发不仅仅影响到分析与设计,更影响到测试。模型驱动的测试方案,可以以更 小的成本实现更有效的测试,从而大大提高产品质量。在敏捷开发中,由于测试是在每个迭代过 程中快速完成的,这种测试策略将显得更有效。 在传统过程模型上,测试人员在很晚的时间才进入开发过程,他们得到一个规格说明的最小 集合,对被测系统他们得到的是一个未知的“黑盒”,在论证任务的时候,他们往往会问:  到底系统要做什么?以什么顺序做?  怎样创建和记录一组测试场景来检查系统的功能?  我怎么知道什么时候已经完整的测试了系统?  还有没有什么其它的事情应该做或者不应该做? 到最后他们还会问一个问题:“有什么办法使测试工作早一点开始,以便早一点发现问题?” 在敏捷开发模型下,这个矛盾将更加突出,因为迭代周期很短,甚至很多工作都不是专职的测试 人员来做,任何把测试工作向前的移动都是有意义的。 如果测试团队采用测试用例来做这件事,除了黑盒以外,还可能发现如下资产:  一个完整地用例集合,记录着有序的事件顺序,这些事件记录着系统如何与用户交互, 并向用户提交结果。  一个用例模型,记录着系统所有用例,以及它们如何交互,什么参与者驱动这些用例。  在一个用例场景中,基本事件流和备选事件流定义了系统“如果……那么”的行为。  前置条件和后置条件的描述。  定义系统非功能性需求的补充规格说明。 这就可以看出来,用例技术构建了一组可以用来驱动测试过程的资产,无论是开发效率和产 品质量都可以由此得到很大的提高。 一一一一、、、、测试用例的概念测试用例的概念测试用例的概念测试用例的概念 用例本身并不是测试用例,但是用例驱动了测试过程,为了转化这些资产为系统测试打下了 基础,仍然需要一些严肃地分析工作,首先让我们定义一些名词。 1111,,,,公共测试名词公共测试名词公共测试名词公共测试名词  测试计划测试计划测试计划测试计划:包括项目测试的目的和目标信息,此外测试计划确定实现和执行测试的策略 和所需要的资源。  测试用例测试用例测试用例测试用例:一组为了特定目标(如执行特定程序路径或验证符合特定需求)开发的测试 输入、执行条件和预期结果。  测试流程测试流程测试流程测试流程:一组为给定测试用例的安装、执行和结果估计的详细指令。  测试脚本测试脚本测试脚本测试脚本:一个自动化执行测试程序(或测试程序的一部分)的软件脚本。  测试覆盖测试覆盖测试覆盖测试覆盖:定义了一次测试或者一组测试,解决给定系统或组件所制定测试用例的程度。  测试项测试项测试项测试项:一个测试对象的构建(Build)。  测试结果测试结果测试结果测试结果:在测试执行中获取的数据集,用于计算测试的不同关键度量。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 138 - 2222,,,,测试工件的关系测试工件的关系测试工件的关系测试工件的关系 可以看出来,为了实现和管理一个全面的测试过程,需要相当多的测试工件,下图展示了这 些工件之间的关系。 测试过程的基础是测试计划,其中包含了测试策略,并且引用或者包含测试用例本身。用例 是潜在测试用例的来源。对每个测试用例来说,都有一个或者多个测试流程定义如何执行特定的 测试用例。测试用例的执行可以是手工的,也可以运行一套测试脚本。测试的结果记录在测试的 结果集中。 3333,,,,测试用例的作用测试用例的作用测试用例的作用测试用例的作用 测试活动的中枢是测试用例。测试用例的创建和执行本身是测试活动组成的一大部分,它们 设计和执行的质量,以及测试的彻底性,决定了最终结果的质量。  测试用例决定了设计和开发测试流程的基础。  测试的“深度”与测试用例的数量成正比。  测试工作量的规模与测试用例的数量成正比。  测试设计和开发以及所需的资源,很大程度上由所要求的测试用例控制。 在项目的先启、精化和构建阶段中创建的用例,可以作为这个过程的输入,由于测试的大部 分工作量在预定义和实现测试用例,这就实现了把测试工作向前移动的目的。 二二二二、、、、从用例得到测试用例从用例得到测试用例从用例得到测试用例从用例得到测试用例 用例和测试用例有不同的起源,并服务于尽管相关但却不同的目的,所以从用例到测试用例 并不简单,但还是有合理的步骤,首先我们定义一下场景的概念: 场景场景场景场景:或用例的一个实例,是一个用例的执行,其中特定用例以特定方式执行该用例。 场景可能有多个,如下图所示,用户可能走主事件流,也可能走备选事件流 1 和 2,然后异 常退出。每个路径都可以是被执行和测试的场景或实例。 既然我们已经定义了用例场景的概念,就可以提出一个四步的过程来完成这个目标。 1111,,,,第一步第一步第一步第一步::::确定用例场景确定用例场景确定用例场景确定用例场景 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 139 - 因为用例和场景之间是一对多关系,我们可以把基本流域备选流之间的关系用一个矩阵表达 出来,假定已经有上面的用例,可以写出场景矩阵。 场景矩阵场景矩阵场景矩阵场景矩阵 场景编号场景编号场景编号场景编号 开始流开始流开始流开始流 备选流备选流备选流备选流 下一个备选流下一个备选流下一个备选流下一个备选流 下一个备选流下一个备选流下一个备选流下一个备选流 1 基本流 2 基本流 备选流 1 3 基本流 备选流 1 备选流 2 4 基本流 备选流 3 5 基本流 备选流 3 备选流 1 6 基本流 备选流 3 备选流 1 备选流 2 7 基本流 备选流 4 8 基本流 备选流 3 备选流 4 注意到我们描述的用例还不是太复杂,就产生了相当数量的场景。在很多情况下,测试人员 需要设计一个既认识到测试所有的场景不现实,同时又有足够测试的测试策略。在烤炉策略的时 候,首先列出所有的场景是必要的。 另外,测试人员也要认识到,并不是所有的场景在原来的用例中都有描述,场景发现的过程 要与开发团队交互地进行,这样做有两个原因:  用例开发是用于实现的,没有百分之百穷尽,其详细程度对测试来说可能不够。  测试团队的审查过程将通过执行用例创建新的发现场景,有的甚至在设计的时候都没有 考虑到,所以就会发生修改设计。 这也是我们在生命周期方法中选择迭代模型的原因之一,因为它允许我们有效的计划和管理 这个过程。测试团队审查用例并发现漏洞,或者附加备选流程将可能产生更好的系统。 2222,,,,第二步第二步第二步第二步::::确定测试用例确定测试用例确定测试用例确定测试用例 公司的测试过程千差万别,但测试用例都应该包括要实施的测试参数,包含测试的条件和预 期的结果。下面的表就是一个公共的格式,使用一个矩阵,表达场景、条件、数据、预期和实际 值。 测试特定场景的矩阵测试特定场景的矩阵测试特定场景的矩阵测试特定场景的矩阵 测试用例编号测试用例编号测试用例编号测试用例编号 场景场景场景场景////条件条件条件条件 数值数值数值数值 1111 数值数值数值数值 2222 数值数值数值数值 NNNN 预期结果预期结果预期结果预期结果 实际结果实际结果实际结果实际结果 1 场景 1 2 场景 2 3 场景 2 注意,上面的表中一个场景可能产生多个测试用例(见用例 2,3),这是因为一个场景可能 会有多种逻辑成分。假定有一个关于自定义照明策略的用例: 户主为一周的每天输入最多 7 种照明序列,系统用一个蜂鸣声确认每个输入。 这个简单步骤将产生两个测试用例,如下表所示。 测试用例编号测试用例编号测试用例编号测试用例编号 场景场景场景场景////条件条件条件条件 描述描述描述描述 预期结果预期结果预期结果预期结果 1 场景 6 少于 7 个序列的输入 保存序列 系统蜂鸣提示 2 场景 6 尝试输入 8 个序列 出错 此外,这个过程中我们还发现了一个歧义性必须解决:“如果户主想输入多于 7 个,系统将 怎样解决?”于是,测试团队和开发团队一起来讨论这件事情,这就是我们迭代发现过程的本质。 3333,,,,第三步第三步第三步第三步::::确定测试条件确定测试条件确定测试条件确定测试条件 下一步是在测试用例中确定引发执行这个测试用例的特定条件。也就是考虑一下,什么条件 引起用户在一个用例中执行特定事件的序列呢? 在这个过程中,测试人员要搜索用户步骤,发现引发特定测试用例的特定数据条件、分支等。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 140 - 每发现一个条件,测试人员都在矩阵中输入一个新的列表示这样的条件。 在这个过程中,只要简单的创建一个列,表明对于这个条件将发生哪些状态(有效、无效、 不可用)就足够了。  有效(V):为执行基本流,这个条件必须为真。  无效(I):这个条件将激活备选流,引发特定场景。  不可用(N/A):所确定的条件无法应用于测试用例。 我们来看一个简单的“控制灯”的用例,这里有三个改变系统行为的条件要考虑:  按下按钮少于 1 秒。  按下按钮超过 1 秒。  按下按钮超过 1 秒后松开。 它们将分别触发场景 1,2,3。下面列出这个用例描述。 用例举例用例举例用例举例用例举例::::控制灯控制灯控制灯控制灯 用例用例用例用例名名名名: 控制灯 用例类型用例类型用例类型用例类型:::: 业务参与者业务参与者业务参与者业务参与者: 住户,灯排 描述描述描述描述:::: 这个用例规定了开灯和关灯的方式,以及如何根据用户按下开关的时间长 短来变明或变暗。这里“按钮”指的是“开/关/变暗”按钮。 前置条件前置条件前置条件前置条件:::: 按钮必须是能变暗的,同时能被编程控制某个灯排。 后置条件后置条件后置条件后置条件:::: 系统记住了按钮的明暗程度 基本流程基本流程基本流程基本流程: 当住户按住按钮的时候,开始基本流。 当住户按住按钮但在定时周期结束前松开按钮,系统按下面方式改变灯的 状态: 1,如果灯是开着的,那么关灯。 2,如果灯是关着的,那么把灯开到最近记忆的明亮程度上。 备选备选备选备选流程流程流程流程:::: 当住户按下按钮超过 1 秒钟,启动备选流。 当住户按住按钮的时候: 1,灯的明亮程度以最大值的 10% 的速度平滑增加。 2,当到最大值以后,灯的明亮程度以最大值的 10% 的速度平滑减弱。 3,当到最小值以后,重复步骤 1。 当住户松开按钮的时候: 4,用例终止,并把亮度停留在当前等级。 特殊需求特殊需求特殊需求特殊需求: 性能:对于住户任何可能动作,从控制板到系统的响应时间小于 50 毫秒。 现在我们创建一个测试矩阵,把每种情况预期的结果记录下来。 有确定条件的控制灯测试用例有确定条件的控制灯测试用例有确定条件的控制灯测试用例有确定条件的控制灯测试用例 测试测试测试测试 用例用例用例用例 编号编号编号编号 场景场景场景场景 描述描述描述描述 条件条件条件条件:::: 按下按钮按下按钮按下按钮按下按钮 少于少于少于少于 1111 秒秒秒秒 条件条件条件条件:::: 按下按钮按下按钮按下按钮按下按钮 超过超过超过超过 1111 秒秒秒秒 条件条件条件条件:::: 按下按钮按下按钮按下按钮按下按钮 超过超过超过超过 1111 秒秒秒秒 后松开后松开后松开后松开 条件条件条件条件 预期结果预期结果预期结果预期结果 1 1 基本流:按下按钮 后在 1 秒之内松 开按钮 V I 不适用 灯开 灯灭了 2 1 基本流:按下按钮 后在 1 秒之内松 开按钮 V I 不适用 灯灭 灯亮了 3 2 备选流:持续按下 按钮超过 1 秒 I V 不适用 N/A 明 亮 程 度 连 续 上 升 或下降 4 3 备选流:按下按钮 持续超过 1 秒以 后松开按钮 I I V 不适用 灯 停 留 在 最 后 的 亮 度 4444,,,,第四步第四步第四步第四步::::增加数据值完成测试用例增加数据值完成测试用例增加数据值完成测试用例增加数据值完成测试用例 我们已经有了很好的进展,现在来确定完全的测试一个用例所需要的所有条件。用例只是对 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 141 - 条件、场景、路径的描述,并没有具体的值,所以还需要到补充规范去找到一些有效的数据范围、 接口协议等等信息。 这恰恰也是利用测试用例解决当初的用例定义的需求的时候了,这也包括把最大/最小性能、 最大/最小数据范围、最大/最小负载的定义和自行期间的数据量结合起来。 一旦确定了数据范围,就可以把它填入测试用例的矩阵,如下表所示。 有确定条件的控制灯测试用例有确定条件的控制灯测试用例有确定条件的控制灯测试用例有确定条件的控制灯测试用例 测试测试测试测试 用例用例用例用例 编号编号编号编号 场景场景场景场景 描述描述描述描述 条件条件条件条件:::: 按下按钮按下按钮按下按钮按下按钮 少于少于少于少于 1111 秒秒秒秒 条件条件条件条件:::: 按下按钮按下按钮按下按钮按下按钮 超过超过超过超过 1111 秒秒秒秒 条件条件条件条件:::: 按下按钮按下按钮按下按钮按下按钮 超过超过超过超过 1111 秒秒秒秒 后松开后松开后松开后松开 条件条件条件条件 预期结果预期结果预期结果预期结果 1 1 基本流:按下按钮 后在 1 秒之内松 开按钮 <1 秒 以 0.1 秒 的间隔 I 不适用 灯开 灯灭了 2 1 基本流:按下按钮 后在 1 秒之内松 开按钮 <1 秒 以 0.1 秒 的间隔 I 不适用 灯灭 灯亮了 3 2 备选流:持续按下 按钮超过 1 秒 I 1~60秒 不适用 N/A 明 亮 程 度 连 续 上 升 或下降 4 3 备选流:按下按钮 持续超过 1 秒以 后松开按钮 I I V 不适用 灯 停 留 在 最 后 的 亮 度 三三三三、、、、管理测试覆盖管理测试覆盖管理测试覆盖管理测试覆盖 显然,即使简单的用例也可能产生大量的测试用例,所以许多应用程序彻底的测试不太可行。 但是在高要求的系统中,不但要求彻底测试,还需要返回一次。如果没有工具的话,就会带来很 大的工作量。下面的几条指南可以提供一些帮助:  选择最恰当、最重要、最关键的测试用例进行最彻底的测试。通常这些用例是主要的用 户接口。另外从架构上看很重要,或者对用户来说呈现一种冒险或困难,也可能存在没 有发现的缺陷的地方。  在成本、风险和验证该用例的必要性之间进行权衡,并据此选择要测试的用例。  使用在迭代计划中已经使用的优先级算法来确定对用户的相关重要性。 4.9 通过优先级评价发现设计通过优先级评价发现设计通过优先级评价发现设计通过优先级评价发现设计重点重点重点重点 当我们致力于提升自己能力的时候,就发现会经历这样几个阶段:首先是熟悉情况,了解各 种各样的问题,在这个过程中满满的获得成功和失败的经验。其次是仔细总结和思考,想办法把 各种各样的问题和经验进行梳理,把问题的逻辑过程进行条理性的描述,这样对很多问题的解决 就提供了有效的方法。 很多人只做到了这一步,但是真正有能力的人会再仔细考虑,所有这些问题哪些是最重要 的?哪些是最关键的?哪些是可以先放一放的?哪些是必须优先解决的?抓住了重点,就可能抓 住了纲,使事情解决起来更加的快速有效。 作为设计也是这样的,仅仅发现和列出了所有的功能这只是第一步,进一步的挖掘我们需要 对所有的功能优先级评价,从而发现最重要的功能,这也是分析和设计的精化过程,对软件设计 来说是很重要的一步。 在确定重点功能的时候,粒度也是一个问题,面对大批的事无巨细的功能,耗费巨大的精力 对每个细小的功能进行优先级排序是得不偿失的,我建议优先级讨论的粒度应该粗一些,比如若 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 142 - 干功能组合成的能力(或者说是主题),这样的归纳本身也是一个对需求进行梳理的过程 一一一一、、、、确定能力的价值确定能力的价值确定能力的价值确定能力的价值 能力的价值其实很难确定,当产品负责人提出“要根据业务价值确定优先级”的伟大建议的 时候,业务价值到底是什么?因此需要有更确切的指导原则,我们必须考虑 4 个因素:  获得这些能力所带来的经济价值。  开发新能力所需要的成本。  开发新能力需要学习知识的量及重要性。  开发这些能力所减少的风险。 由于多数项目都考虑节约开支与赚钱,所以前两个因素往往比较受到重视,但为了最优的确 定优先级,对于学习与风险的考虑也是重要的。 1))))价值价值价值价值 确定优先级的第一个因素是能力的经济价值,获得了能力的能力,可以让公司获得或者节省 多少钱?这本身就具有“要根据业务价值确定优先级”的含义。 确定能力价值的理想方法,使估计它在一段时间内(几个月或者几年)所带来的经济影响。 确定能力的经济回报是很困难的事情,它通常要求对新产品的销售数量、每次销售的平均价格(包 括后续销售和维护协议)、销售上升的时机等都进行估计,这种估计难度颇高。因此常常用一些 替代方式对价值进行估计,由于现有用户合意性常常与价值有关,所以常用的方法是用合意性的 非经济度量来表示价值,这是后面我们会讨论的问题。 2))))成本成本成本成本 很自然的,开发能力的成本是优先级的重要考虑因素。很多能力看起来不错,但了解倒它的 成本之后可能会改变主意。由一个常常被忽视的问题,就是成本会随时间变化。假定开发某个能 力现在需要 4 周的时间,但 6 个月以后我们会由于所获得的知识的改变,还需要花额外的 3 周对 已经开发的能力进行改变,那就需要等一等。或者 6 个月之后预计我们会发现一种更简单的方法 来开发这个能力,那为什么不等一等呢? 3))))新知识新知识新知识新知识 在很多项目中,整体工作中的大部分是花在对新知识的追寻上,重要的是承认这种努力,并 把它看作是项目的一个重要组成部分。获取新知识很重要,因为项目开始的时候,我们并不知道 项目结束前我们所需要知道的所有的事情。在开发过程中所形成的知识包括两类:  关于产品的知识关于产品的知识关于产品的知识关于产品的知识:这是关于将要开发的能力的知识,一个小组关于产品的知识越多,就 能更好的作出产品特性和能力的决策。  关于项目的知识关于项目的知识关于项目的知识关于项目的知识:这是关于如何建立产品的知识,例如将要使用的技术、技能、小组共 同工作所需的相关知识。 4))))风险风险风险风险 与新知识的概念密切相关的是风险。几乎所有的项目都蕴藏着大量风险。风险指的是目前尚 未发生但是可能发生,而且会妨碍或者限制项目成功的事情,在项目中不同的风险包括:  进度风险  成本风险  能力风险 此外,风险还可以分为技术风险和商业风险。能力上的高风险和高价值存在着典型的竞争, 要在它们之间做出选择先要考虑每种方法的缺点。风险驱动的开发可能花了很多努力,到最后才 发现这个能力其实是不必要的。价值驱动的开发首先开发了很多高价值能力,到最后才在一个不 经意的地方发生危及整个项目的风险,造成项目崩溃。这两种情况其实都有可能发生。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 143 - 解决的办法是不要让风险也不要让价值在确定优先级的时候占绝对主要地位。我们考虑一下 下面的关于风险价值关系 4 象限图。开发的优先级是从高风险高价值开始,沿着下图所示的曲线 安排比较合理。 图中表达的方法是要“避免”高风险低价值的能力,但这个避免不是绝对的,今天被认为是 要避免的,6 个月后随着开发的进展,或者风险降低,或者价值提高,这个能力可能会处于首先 要处理的位置。 要综合 4 个优先级因素,基本的工作方法是这样的: 首先考虑能力的价值与现在就开发它所需的成本之间的关系,这会给您一个初始的优先级顺 序,具有高价值/成本比的主体应该首先完成。 其次,考虑其它优先级因素把主体向前或者向后移动。假定一个能力根据价值/成本比具有 中等优先级,但发现这个能力有很大的技术风险,这会导致这个能力在进度表上往前移动。 初始排序并不是很正式的,甚至只是产品负责人在一张纸上的涂鸦,然后产品负责人向开发 小组提出自己的想法,小组作出恰当的评估,最后由产品负责人确定最后的顺序 二二二二、、、、确定经济优先级确定经济优先级确定经济优先级确定经济优先级 经济上的考虑往往是确定优先级的重要考虑因素。如果我们可以估计每个能力所能产生或者 节省的金钱数目,那就可以用它来帮助确定优先级。预测能力的经济价值是产品负责人的责任, 但也是小组其他成员共同承担的。很多情况下,产品负责人还需要通过销售和市场推广部门获得 商业信息和预测。 比较好的方法,是召开一次或者多次有尽可能多的这类人参加的会议,这种能力评估会议的 目的是为每个能力填写一张如下显示的表格。 能力能力能力能力 – 收入工作表收入工作表收入工作表收入工作表 季度季度季度季度 新收入新收入新收入新收入 增量收入增量收入增量收入增量收入 留存收入留存收入留存收入留存收入 操作效率操作效率操作效率操作效率 1 2 …… 总计 这种会议可以分成两阶段来开,第一次会议说明情况,介绍下面就要讨论的各个列标题的含 义,然后休会再等待 2~3 天让所有会议成员私下进行研究和讨论,最后集中起来讨论。如果这 一项没有收入,这个数据可以定为 0。 讨论的时候需要表达这些数字是从哪里来的?理想的数据应该来自于启动这个项目的商业 案例对市场的研究,至少表达要求实现这个能力的人能够量化开发它的原因。如果没有这方面的 量化数据,那就要考虑这个项目启动本身是否有意义了,“因为他有了所以我也要有!”这往往就 是项目失败的根本原因。 三三三三、、、、确定合意性优先级确定合意性优先级确定合意性优先级确定合意性优先级 产品的价值很大程度上来自于客户满意度,因此在考虑合意性优先级的时候,我们需要研究 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 144 - 产品能力与用户满意度的关系。 1))))客户满意度的客户满意度的客户满意度的客户满意度的 Kano 模型模型模型模型 最先提出为新产品发布提供优先级指导的,是日本管理学家狩野纪昭(Noriaki Kano ),他的 方法是把能力分成 3 类:  作为阈值的能力,或者说必需的能力。  线性能力。  兴奋点和惊喜点。 这几种能力与客户满意度的关系见下图,这个图称之为 Kano 模型。 阈值能力是产品要成功必须具备的那些能力,也常常也称之为必需的能力。改善阈值能力和 增加阈值能力的数量对客户满意度并没有多大的影响。 由于产品要有那些必需的能力才能在市场上生存,应该强调优先开发所有那些阈值能力。但 并不是一定要在第一次迭代中就开发所有产品必须能力,但是由于用户把这些能力看成强制性 的,他们必须要在发布产品之前变成可用。从图上可以看出来,有的时候只需要满足必须能力的 部分实现就可以了,因为对必须能力一定程度的支持以后,客户满意度的上升幅度就趋于平缓。 比如用记事本写文章,它只有单次撤销,当然有象 Word 那样的多次撤销能力也很好,没有也可 以,即使加上了多次撤销客户对记事本的能力满意度也不会提高多少。 线性能力是一个处于“越多越好”状态的能力。这里客户满意度会随着这部分内容的上升而 直线上升,而产品的价格也与线性特性相关。 最后,兴奋点和惊喜点是那些提供了很高的满意度,并常常可以为产品增加额外价格的那些 能力。但是缺少兴奋点和惊喜点并不会让客户满意度降到中性以下。兴奋点的特点是,用户会喜 欢它,但如果没有它用户也不会拒绝这个产品。实际上兴奋点和惊喜点也被称之为未被了解的需 求,因为在大家没有很好的研究它的时候,并不知道自己需要它。 一般来说的优先级是:首先是阈值能力,其次是现行能力,最后的兴奋点能力。 2))))用用用用 Kano 模型评估模型评估模型评估模型评估能力能力能力能力 在敏捷开发项目中使用 Kano 模型最简单的方法,是考虑每个能力,对它所属的类型作出有 根据的猜测。不过更好的办法是与客户或者用户进行讨论,确定每个能力的类型。一般来说,只 需要向少至 20 ~30 个人进行一次书面问卷,就可以准确的知道需求的优先级。Kano 建议通过两 个问题来确定一个能力的分类。 第一个问题是能力存在形式:如果一个产品中有这个能力用户会怎样? 第二个问题是能力缺失形式:如果一个产品中没有这个能力用户会怎样? 每个问题都可以通过 5 点的度量方式进行回答: (1)我希望这样 (2)我预期就是这样 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 145 - (3)我没有意见 (4)我可以忍受这样 (5)我不希望这样 例如,我们正考虑要建立一个运动员网站 SwimStats ,假定我们正在考虑 3 个新能力:  查看一张图,显示一个运动员在过去的赛季中某个项目的成绩能力。  让运动员发布自传简历的能力。  让注册的网站成员上传照片的能力。 要确定这些能力属于哪个类别,就需要对用户进行调查,可以问他们以下问题: 合意性调查表合意性调查表合意性调查表合意性调查表 题目题目题目题目 1 2 3 4 5 1 如果可以绘制一个运动员在过去赛季中某个项目上的成绩,您 觉得怎么样? √ 2 如果不可以绘制一个运动员在过去赛季中某个项目上的成绩, 您觉得怎么样? √ 3 如果有让运动员发布自传简历的能力,您觉得怎么样? √ 4 如果没有让运动员发布自传简历的能力,您觉得怎么样? √ 5 如果有让注册的网站成员上传照片的能力,您觉得怎么样? √ 6 如果没有让注册的网站成员上传照片的能力,您觉得怎么样? √ 说 明 1,我希望这样 2,我预期就是这样 3,我没有意见 4,我可以忍受这样 5,我不希望这样 对这一叠调查表,需要一种方法,对用户的观点做出评估。 2))))将答案分类将答案分类将答案分类将答案分类 我们可以使用一个分析矩阵对每个人的回答做出判断,如下表所示。 答案分析答案分析答案分析答案分析 能力能力能力能力缺失缺失缺失缺失 希望希望希望希望 预期预期预期预期 无意见无意见无意见无意见 忍受忍受忍受忍受 不希望不希望不希望不希望 能力能力能力能力 存在存在存在存在 希望希望希望希望 Q E E E L 预期预期预期预期 R I I I M 无意见无意见无意见无意见 R I I I M 忍受忍受忍受忍受 R I I I M 不希望不希望不希望不希望 R R R R Q M 必须的必须的必须的必须的 L 线性的线性的线性的线性的 E 兴奋点兴奋点兴奋点兴奋点 R 反对的反对的反对的反对的 Q 存在疑问的存在疑问的存在疑问的存在疑问的 I 无所谓的无所谓的无所谓的无所谓的 在这个分析表上,如果用户对这个能力的存在认为他预期、无意见、可以忍受,但是不希望 没有,这一般可以认为是阈值能力。如果用户希望有这个能力,而且不希望没有这个能力,那么 这是一个线性能力,客户满意度会随着这样的能力增多而增加,这个能力也是强制性的,例如上 面关于绘图的回答。如果客户希望有这个能力,但对没有这个能力它的反映是预期、无意见、可 以忍受,那这一般是一个兴奋点能力。 最后把每个人的分析统计在一张表上,就可以看出每个能力的性质了,如下表所示。 用户调查结果分析用户调查结果分析用户调查结果分析用户调查结果分析 能力能力能力能力 E L M I R Q 类别类别类别类别 对项目成绩作图 18.4 43.8 22.8 12.8 1.7 0.5 线性 上传照片 8.3 30.9 54.3 4.2 1.4 0.9 必须 发布自传简历 39.1 14.8 36.6 8.2 0.2 1.1 兴奋点,必须 可见,上传照片是必须的,对成绩作图是线性能力。对于发布自传简历,一部分人认为是必 须,还有一部分人感觉是一个兴奋点,这就需要对结论进一步分析。 但是用户可能会有不一致的回答,比如第一个问题是:我不希望这样;对第二个问题他也回 答:我不希望这样。这就比较难处理了。如果是个别情况,就可以把它作为奇点去掉,如果是比 较普遍的情况,您可以考虑按照某些因素对客户分组,比如把小公司与大公司分开,或者按照客 户在公司的角色进行区分,就会发现这类回答是有一定群体性的,再来分析他们的回答得到更加 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 146 - 深入的结论。 3))))相对权重方法相对权重方法相对权重方法相对权重方法 某些情况下也可以利用专家来判断,开发小组共同对下一次发布所考虑的每个能力进行评 估,通过价值和成本的分析来订出不同能力的权重。和描述点估计一样,这里对收益和惩罚的估 计也是使用 1~9 的尺度进行相对度量,下面对同样的运动员网站显示这种方法的工作方式。 确定优先级的相对权重方法确定优先级的相对权重方法确定优先级的相对权重方法确定优先级的相对权重方法 能力能力能力能力 相对相对相对相对 收益收益收益收益 相对相对相对相对 惩罚惩罚惩罚惩罚 总价值总价值总价值总价值 价值价值价值价值 百分比百分比百分比百分比 估计值估计值估计值估计值 成本成本成本成本 百分比百分比百分比百分比 优先级优先级优先级优先级 对项目成绩作图 8 6 14 42 32 53 0.79 上传照片 9 2 11 33 21 34 0.97 张贴自传简历 3 5 8 25 8 13 1.92 总计 20 13 33 100 61 100 对成绩作图的相对收益被估计为 8,比上传照片能力价值略低,但是比让运动员发布自传的 价值高许多。至于缺少这些能力带来的惩罚,用户不能上船照片带来的损失很小,这就是说,这 可能是一个兴奋点,用户会喜欢,但没有它用户也不会拒绝这个产品。但是,如果不能对成绩作 图或者不能上传照片,惩罚要大得多。 对每个能力,他的相对收益和相对惩罚被加在一起输入到总价值列里。总价值列的和(这里 为 33 )代表着交付所有能力的总价值,要计算每个能力的相对价值,需要把该能力的总价值除 以总价值一列的和。例如对项目成绩作图的相对价值:14/33=0.42 。 接下来“估计值”指的是描述点的估计值,这同样需要一个总和。成本百分比的计算: 32/61=0.53 。 最后一列优先级,是把价值百分比除以成本百分比得到的,取值越高,说明优先级越高,因 为它对投入产出了更高的价值。从上表中张贴自传简历可以看出这一点,这个能力价值只有对成 绩作图的一半(总价值 8 与 14 相比),但这个价值所需的成本只有四分之一(估计值 8 与 32 相 比)。由于这具有很高的价值成本比,所以张贴简历成为优先级最高的能力。 在优先级的合意性评估中,相对惩罚评估非常重要,因为从不同的角度看问题是很有用的。 4.10 设计文档编写的若干建议设计文档编写的若干建议设计文档编写的若干建议设计文档编写的若干建议 一一一一、、、、为什么要书写文档为什么要书写文档为什么要书写文档为什么要书写文档 很常见的情况是,开发人员对于书写文档有种种非议,但是一个正规的软件项目,即使带来 了表面上的工作效率降低,也必须花费精力书写文档,为什么呢? 任何时候,只要是两个人以上参与项目,都离不开口头交流,但是每个人在传递这个口头交 流的内容的时候,都会把原来的意思歪曲一点点,人类的记忆能力就是这样的。一个软件项目永 远离不开口头交流,我们不必要去停止这种交流方式,但是当开发人员比较多的时候,就可能带 来很多麻烦,某些情况需要大家都知道,需要其他人员与之交互等等,解决的办法就是把所有的 情况记录下来,或者是要强调书写文档。利用文档记录有五个好处: 1))))拓展人脑所掌握的记忆范围拓展人脑所掌握的记忆范围拓展人脑所掌握的记忆范围拓展人脑所掌握的记忆范围 在任何一个大到必须编写文档的项目中,信息的含量都会超过一个人可以掌握的规模,书面 文字可以供将来参考,而不会像记忆一样慢慢的褪去。 2))))为项目团队所有成员提供相同的信息为项目团队所有成员提供相同的信息为项目团队所有成员提供相同的信息为项目团队所有成员提供相同的信息 书面文档阅读的时候内容是相同的,所有的人员都阅读相同的材料,这是任何人对人的口头 交流无法达到的效果。 3))))减少项目人员流动的困难减少项目人员流动的困难减少项目人员流动的困难减少项目人员流动的困难 项目中发生人事变动是正常的,当老成员离开,新成员加入的时候,任何口头交流都很难让 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 147 - 人很快地掌握情况,一份很好的文档可以让他花更少的时间融入到团队中去。 4))))保护智力资产保护智力资产保护智力资产保护智力资产 在大多数情况下,项目中只有少数的人理解问题域,他们知道是否需要变更,软件是否存在 某些漏洞,内心中对软件是不是有什么新观点。如果这些人把他们掌握的信息以正规的方式记录 下来,项目对他们的依赖就会减小,如果他们获得了更好的机会,他们的智力资产也不会被带走。 5))))帮助书写人员更好的理解问题域帮助书写人员更好的理解问题域帮助书写人员更好的理解问题域帮助书写人员更好的理解问题域 以书面形式描述设计方案,将促使相关人员花精力以比口头交流更标准和更准确的方式表 达。人们在写作过程中可以发现自己对某些问题的理解存在漏洞,甚至概念上也是不完整的。难 怪人们说:“文档本身并不重要,重要的是写文档的过程。”当然,这样写出来的文档对别人也是 重要的。 很多公司实际上是很严肃地对待文档的,但他们并没有从文档中得到好处,相反在实践中口 头交流仍然是主要手段,其关键原因是所书写的文档并不是为了开发而是为了应付检查。从格式 上看很漂亮,内容面面俱到,但在文档组织上把信息分散到各个地方,不容易查找,使文档提供 的好处尽失。如果我们希望项目开发小组很好的使用文档中的信息,就需要考虑什么内容是开发 小组最关注的,怎么组织文档才是对开发来说最容易查找,如何编写才是最容易阅读和有效的 二二二二、、、、设计设计设计设计文档编写的建议文档编写的建议文档编写的建议文档编写的建议 1,,,,防止编写成智力拼图防止编写成智力拼图防止编写成智力拼图防止编写成智力拼图 许多文档写成了类似智力拼图的玩具,很多不同的相关定义与内容散落在文档的不同地方。 读者不会预料到在文档的其它地方会不会还有相关说明。为了阅读这样的文档,人们需要把一切 都记在脑子里,智力拼图是把散落在各处的小块拼在一起,但人的大脑很难做到这一点。 要记住,任何大的文档都不同程度的具有智力拼图性质,但我们的任务是减少智力拼图块的 数量。不要让阅读者在一个地方看到的信息块做出了推论,而在另一个地方才发现这个理解是错 误的,这就很危险。 组织文档的原则,大多数情况下是防止出现智力拼图的情况,如果需要,可以把文档中对某 个名称的定义和描述都找出来,统一放在某个固定的、唯一的位置上,必要的时候还可以建立术 语表。如果无法将相关信息都集中在一个章节,就需要增加引用页面,这样读者就不会迷惑了。 我们这个课程讲义的编写本身就是一个文档化过程,除了定义清楚问题域以外,整个课程的 思考方式是基于问题、研究对策、寻找解决方案,而不是依据于某个人说过什么,某个标准是什 么(何况标准也是在变化的)。在书写方式上,以读者为中心,力求通俗易懂、内容连贯、条理 清楚。在内容组织上,力图减少不必要的拼图游戏,减少不必要的前后查阅。相似的概念和名词, 只要在各个章节中,都力图自成逻辑体系,使得读起来省力清晰。 这些特征,在文档的编写中都是需要注意的。 2,,,,防止编写成鸭叫文档防止编写成鸭叫文档防止编写成鸭叫文档防止编写成鸭叫文档 有些文档编写出来为什么相关人员不愿意看呢?为什么自认为似乎已经描述的很清楚的文 档,在读者感觉模糊不清难以阅读呢?我们来看看下面的描述:“机票预定数据验证函数应该验 证机票预定数据”。这样的句子确实使人莫名其妙,这到底是什么意思?如何测试它?这很容易 使人联想到一种鸭叫演讲,单调、温顺、平庸、符合官方标准,但是叫人提不起精神,看起来描 述了很多,但实际上什么也没有描述。 把自己写的句子大声念出来,如果发现自己写了很多毫无疑义的句子,只是一味的迎合标准 要求,就要考虑重新框定问题,使文档写出来具有精神,读者读起来也有精神。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 148 - 3,,,,不要为了文档而文档不要为了文档而文档不要为了文档而文档不要为了文档而文档 软件质量保证使每个开发组织都很关注的,为了保持一致的软件质量,我们需要一套独立定 义的质量准则,所以,过程的定义与文档规范就成为很多组织很关注的内容。但也会发生文档的 使用与功能正确实现无关的情况,由于文档记录要与文档标准保持一致,结果使用起来将非常繁 琐而散乱。 这种书写文档的理念只是为了应付检查,而不是促进效率与更好的开发,是为了文档的本身, 是为了文档而文档的书写方式。书写这种文档的目的只有一个,那就是证明我们正在做和官方保 持一致的事情,如果有什么差错,那不是我的责任。至于项目能不能完成,那不是我考虑的问题。 很多文档都没有被任何人阅读,其原因是人们无法理解他,不适合相关人员的习惯,也没有 办法把文档与自己的开发工作联系起来。当然,确实很多评审只关注文档格式而不是文档有效性, 只关注文档字数多少而不是内容的思想性。这种东西不是单靠开发单位能解决的,如果这样的评 审是一定存在的,那一个建议就是:开发两套文档,一套用于评审,一套用于开发小组内部。这 看起来多费了时间,但所费的时间是值得的,总比把应付评审的格式化文档用于实际开发从效率 上要高的多。试试看,就会发现这个建议的意义。 4,,,,图与文的协调图与文的协调图与文的协调图与文的协调 谈到设计文档就免不了使用图形表达,不过在图和文的协调上需要下点功夫。图比较适合说 明关系,而文字比较适合表达细节。这两者是不可互相替代的。作为设计,如果没有图,相关人 员对问题域就可能不能得到一个整体的概念。但是没有详尽的文字表达就不可能定义开发,也不 可能使开发指向我们所需要的方向。图不需要面面俱到只需要表达重点需要表达的内容,而文字 的详细程度应该足以定义开发的关键点,但也要给开发人员留出适当的发挥空间。 有的单位热衷于把类图直接生成代码,这是一个误会,也是把图用错了地方。代码是事无巨 细的,而且与编码员个人的特征有很大关系。图没有必要画得那么细,也不可能画的那么细。 画图要表现出聪明,要说明每个结构策略的原因,也要说明所采用的策略所带来的负面影响, 仅仅按照某个模式来做设计那是初学者的事情,一个高级设计人员的思维方式,是突破一切框框, 目的就是一切。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 149 - 第五章 适应业务单元变化的架构策略 在考虑变与不变在考虑变与不变在考虑变与不变在考虑变与不变分离的时候,应该看到很多情况下业务流程具有相似性,而业分离的时候,应该看到很多情况下业务流程具有相似性,而业分离的时候,应该看到很多情况下业务流程具有相似性,而业分离的时候,应该看到很多情况下业务流程具有相似性,而业 务单元却务单元却务单元却务单元却各自独立的变化,这在小粒度架构设计的时候尤其普遍。各自独立的变化,这在小粒度架构设计的时候尤其普遍。各自独立的变化,这在小粒度架构设计的时候尤其普遍。各自独立的变化,这在小粒度架构设计的时候尤其普遍。 在基础架构设计中,我们已经详尽的讨论了为达到系统的质量目标而采取的架构策略,但是 这一策略的实现,必须依靠小粒度架构的良好设计。 小粒度设计阶段考虑的主要是模块结构设计小粒度设计阶段考虑的主要是模块结构设计小粒度设计阶段考虑的主要是模块结构设计小粒度设计阶段考虑的主要是模块结构设计,,,,它着力于它着力于它着力于它着力于解决如下问题解决如下问题解决如下问题解决如下问题:::: 1,给出软件结构中各模块的内部过程描述。 2,模块的内部过程描述也就是模块内部的算法设计。 3,模块设计有时需要导出一些算法设计表示,由此可以直接而简单地导出程序代码。 模块结构设计直接对应于实现编码,因此设计质量直接影响着软件的质量。为了合理的规划 模块之间的关系以及模块内部的各个类之间的关系,就需要提出一些原则,这就是设计模式提出 的原因和背景。设计模式也称之为微观架构模式,这种小尺度的对象和框架的设计,有时候也需 要资深程序人员参与。 5.1 软件重构技术软件重构技术软件重构技术软件重构技术与设计模式与设计模式与设计模式与设计模式 关于系统可维护、可集成性的实践,引发了人们对于这类问题理论上研究的兴趣,一个直接 的成果就是软件重构技术的提出。软件重构技术是对软件的内部结构所作的一种改变,这种改变 在可观察行为不变的条件下使软件更容易理解,而且修改更廉价。 一一一一、、、、为什么要研究重构技术为什么要研究重构技术为什么要研究重构技术为什么要研究重构技术 在软件开发与维护的长期实践中,人们普遍认识到的一个事实是代码太容易变坏。 坏的代码总是趋向于有更大的类、更长的方法、更多的开关语句和更深的条件嵌套。特别是 那些初看相似细看又不同的代码泛滥于整个系统:条件表达式,循环结构、集合枚举…。 全部代码都以非常密集的样式被书写,你根本看不到这种代码还有什么良好的设计。 当项目进行或者完成之后,需求被改变,功能被增加,程序员改变代码,所有这些情形都倾 向于在非常紧的工期下发生。这些因素意味着代码不被维护,只是被扩展,导致难于阅读和理解 的复杂代码。随着时间的漂移,代码中只有很少的一部分代表了系统的设计。 如果要在这样一种系统上添加功能,第一个反应应该是拒绝。因为这样的代码难以理解,更 不要说对它加以修改。存在的第二种可能性是抛弃现在的系统,然后从头编写。如果项目的规模 较大,或者系统已经在运行,不可能进行重新编写,这时人们就得面对第三种选择,硬着头皮进 行维护,进行修改和扩充。 维护人员通常采取一种快速和消极的方法。如果系统有问题,那么就尽快地找到问题,直接 修改它。如果要增加一项新功能,就会从原来的系统中找到一块相近的代码,拷备出来,作些修 改,再粘贴进去。这样一来,系统变得越来越难以理解,维护越来越困难、越来越昂贵。系统变 成了一个十足的大泥潭。每个人都不愿意看到这种情况,但奇怪的是,这样的情形却一次又一次 地在现实中不断地出现。 所有维修都倾向于破坏结构,增加了系统的凌乱。在修理原始设计缺陷上所花的时间越来越 少,越来越多的时间是花在修理由早期修理所引入的错误上。随着时间的流逝,系统变得越来越 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 150 - 混乱。迟早修理变得不可能而停止下来。 软件重构技术的动机是研究主要包括软件重用、软件维护、和软件的重新组织(Software Restructuring )。 1,,,,软件重用软件重用软件重用软件重用((((Software Reuse )))) 为了降低开发软件的高成本,软件重用的研究是为了使一个系统开发的知识再次容易地用于 另一个软件系统的开发。然而,可重用软件经常需要很多设计迭代。使软件更容易改变将使设计 迭代更简单。这样的软件将更可重用。 抽象、封装、继承、多态和模块性这样的面向对象程序设计特性给软件再利用提供了基础结 构,以鼓励再利用现有的代码,而不是从头开始编码。这样,通过添加新类或者在现存类上添加 操作能对软件作出某些改变,而使软件的大部分保持不变。 除代码级再利用之外,设计级的再利用也是研究的内容。而且,从长期的观点来看,人们认 识到设计级的再利用更为重要。面向对象应用框架(framework )是这种研究努力的结果。框架 是抽象和具体类的集合。从而,能够添加新的子类对它进行重定义。因此,框架支持抽象级,并 允许部分的规范说明。 2,,,,软件维护软件维护软件维护软件维护((((Software Maintenance )))) 软件重用与软件维护紧密相关。维护是软件生产所有方面中最为困难的。主要的理由在于维 护包容了软件过程所有其他阶段的各个方面内容。在软件生命周期中,在维护上所花的时间比任 何其它阶段都更多。实际上,现行软件的维护工作量能占到全部开发工作量的 60 % 以上。软件 维护经常需要重新组织软件。 3,,,,软件重新组织软件重新组织软件重新组织软件重新组织((((Software Restructuring )))) 不适当的设计方法学,缺乏开发和维护标准等诸如此类的很多因素都能导致拙劣的软件结 构。只存在一些不对代码进行更改的方法。例如,人们能够在软件再工程期间从代码和现有的文 档出发,重新创建软件的结构。 二二二二、、、、重构的定义重构的定义重构的定义重构的定义 重构是以各种方式对一对象设计进行重新安排使之更灵活并且/或者可重用的过程。效率和 可维护性可能是进行重构最重要的理由。 重构定义为名词形式和动词形式两部分: 重构(Refactoring ,名词):是对软件的内部结构所作的一种改变,这种改变在可观察行为 不变的条件下使软件更容易理解,而且修改更廉价。 重构(Refactor ,动词):应用一系列不改变软件可观察行为的重构操作对软件进行重新组织。 这些定义中最重要的方面是不改变软件系统的可观察行为,并且改变软件结构是朝着更好的 设计和更能理解,而且可重用的方向进行。 重构的名词形式就是说重构是对软件内部结构的改变,这种改变的前提是不能改变程序的可 观察的行为,这种改变的目的就是为了让它更容易理解,更容易被修改。 动词形式则突出重构是一种软件重构行为,这种重构的方法就是应用一系列的重构操作。 三三三三、、、、重构的原则重构的原则重构的原则重构的原则 1,,,,一个时刻只做一件事情一个时刻只做一件事情一个时刻只做一件事情一个时刻只做一件事情 如果你使用重构开发软件,你把开发时间分给两种不同的活动:增加功能和重构。 增加功能时,你不应该改变任何已经存在的代码,你只是在增加新功能。这个时候,你增加 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 151 - 新的测试,然后让这些新测试能够通过。当你换一顶帽子重构时,你要记住你不应该增加任何新 功能,你只是在重构代码。你不会增加新的测试。只有当重构改变了一个原先代码的接口时才改 变某些测试。 在一个软件的开发过程中,你可能频繁地交换这两件工作。 关于两件工作交换的故事不断地发生在日常开发中,但是不管你做哪件工作,一定要记住一 个时刻只做一件事情。 2,,,,小步前进小步前进小步前进小步前进 保持代码的可观察行为不变称为重构的安全性。重构的另一个原则是小步前进,即每一步总 是做很少的工作,每做少量修改,就进行测试,保证重构的程序是安全的。如果你一次做了太多 的修改,那么就有可能介入很多的错误,代码将难以调试。 这些细小的步骤包括:确定需要重构的位置,编写并运行单元测试,找到合适的重构并进行 实施,运行单元测试,修改单元测试,运行所有的单元测试和功能测试等。如果按照小步前进的 方式去做重构,那么出错的机会可能就很小。 小步前进使得对每一步重构进行证明成为可能,最终通过组合这些证明,可以从更高层次上 来证明这些重构的安全性和正确性。 四四四四、、、、重构的目标和本质重构的目标和本质重构的目标和本质重构的目标和本质 从概念上讲重构的目标是对软件系统的设计进行重新组织,使系统满足某些通用设计的准 则,并具有容易扩展系统功能的结构或者形状。在重构应用与实践中,设计模式(design pattern ) 为重构提供了一个明确的目标。 重构的实质是在保持可观察行为不变的前提下,为提高软件的可理解性,可扩展性和可重用 性而对软件进行的修改。 五五五五、、、、重构的组成与步骤重构的组成与步骤重构的组成与步骤重构的组成与步骤 重构由许多小的步骤组成。当一次对系统作了很多改变时,在此过程中也极有可能引入许多 错误。但产生这些错误的时间和地点是不可再现的。如果以小步前进的方式实现对系统的改变, 并在每一步后运行测试的话,错误就有可能在它引入系统后的测试中立即表现出来。然后对每步 的结果进行检查,如果有问题,可撤消此步所作的改变。在复原之后,可以采取更小的步骤前进。 重构软件系统采取的步骤如下: 1.确定需要重构的位置。可以通过理解,扩展,或者重新组织系统来发现问题,或者通过 查看实际代码中的代码味道(code smell )来确定位置,或者通过某些代码分析工具。 2.如果所考虑的代码的单元测试存在的话,就运行该单元测试看看能否正确的完成。否则, 编写所有必要的单元测试并运行它们。 3.通过思考或者查看重构分类目录,找出能够明显被应用的重构操作。 4.遵循小步前进的原则实现重构操作。 5.在每步间运行测试以保证行为未被改变。 6.如有必要,对作过改变的接口修改测试代码。 7.当重构操作被成功地用于重新组织代码,再次运行测试,集成并运行全部的单元测试和 功能测试。 六六六六、、、、重构的优点重构的优点重构的优点重构的优点 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 152 - 重构增加了具有某种缺点的程序的价值。难读的程序难修改。具有重复逻辑的程序难修改。 带有复杂条件逻辑的程序更难修改。重构虽然需要更多的“额外工作”,但是它给我们带来的各 种好处显然值得我们做出这样的努力。 1,,,,简化测试简化测试简化测试简化测试 一个好的重构实现能够减少对新设计的测试量。因为重构的每一步都保持可观察的行为,也 就是保持系统的所有单元测试都能顺利通过。所以只有发生改变的代码需要测试。这种增量测试 使得所有的后续测试都建立在坚实的基础之上,整个系统测试的复杂性大大降低。 2,,,,增进软件可理解性增进软件可理解性增进软件可理解性增进软件可理解性 程序编写是人的活动,人首先要理解才能行动。所以,源代码的另一个作用就是用于交流的 工具。其他人可能会在几个月之后修改代码,如果连理解代码都做不到,又如何完成所需的修改 呢? 我们通常会忘掉源代码的这种用处,尽管它可能是源代码更重要的用处。不然,我们为什么 发展高级语言、面向对象语言,为什么我们不直接使用汇编语言甚至是机器语言来编写程序? 如果一个人够理解我们的代码,他可能只需要一天的时间完成一个增加功能的任务,而如果 他不理解我们的代码,可能需要花上一个礼拜或更长的时间。 这里的问题是,我们在编写代码的时候不但需要考虑计算机 CPU 的想法,更要把以后的开 发者放在心上,除非,你写代码的唯一目的就是把它丢掉。 重构可以使得你的代码更容易理解。原因在于重构支持更小的类、更短的方法、更少的局部 变量、更小的系统耦合,重构要求你更加小心自己的命名机制,让名字反映出你的意图。如果哪 一块代码太复杂以至于难于理解,你都需要对它进行重构。 3,,,,改善软件设计改善软件设计改善软件设计改善软件设计 大部分的软件工程师认为代码仅仅是设计的附属物。但我们必须正视的情况是,程序设计在 实现阶段完成的正是代码,而不是你脑袋里或纸上的设计。而绝大多数的故障也产生于编码阶段。 在当时或者以后找出这些故障被事实证明是非常昂贵的。 很多方法学强调把注意力放在分析和设计阶段,把实现定义为按照设计进行编码,以努力防 止产生故障。这些方法学认为通过分析和设计的严格化就能产生更高质量的软件。然而,这仅仅 是理论,实际上是不可能的。 另一方面,即使一开始的设计是完好的,随着用户对系统使用的深入,新的需求可能会被加 入,旧的需求会被修改、删除。设计不可能完全预料到这些变化。一旦实现开始偏离最初的设计, 那么它的代码将不受控制,从而不可避免地开始腐化(decay )。代码加入越多,腐化的速度越快。 如果没有办法让设计尽可能地与实现保持一致,那么这种腐化的最后结果就是代码不得不被抛 弃。 在传统的软件方法中,一旦开发到了实现阶段,就很难对设计做出变化。由于程序员编写的 代码倾向于腐化。它慢慢地偏离最初的设计,代码偏离最初的设计越远,反映设计的代码就越难 被看到,代码就越容易变坏。重构有助于把贬值的代码变得有用,并获得好的设计。 4,,,,利于找出错误利于找出错误利于找出错误利于找出错误 软件调试中最困难和最费时间的工作是定位错误。重构不仅帮助理解代码,同时也可以帮助 发现错误。因为故障隐藏在系统中一些非常微妙的地方,清理设计和代码使故障被带到明处。 重构使得程序代码的结构更清晰,每一个时刻可以更集中关注专一的数据和行为,这会使你 的工作量大大减少,效率大大提高。同时,由于重构要求小步前进,并且对每一步都进行严格的 测试,这样会使得错误很容易地浮现出来。所以重构期间进行的测试对于捕捉由于测试不足而早 期未被发现的故障是很有用的。 5,,,,加速开发加速开发加速开发加速开发 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 153 - 重构的以上优点是明显的,重构能够加速开发的优点却不那么明显。人们甚至认为重构会减 慢开发速度。因为需要编写额外的单元测试,还要去重构那些本来就能够工作的代码。 事实上,重构确实能够加快软件的开发速度。一个好设计的基本前提就是允许更快的软件开 发。如果没有一个好的设计,一开始可能可以开发得很快,但是随着功能的增加,代码逐渐腐化, 结构渐渐失去。每次需要加入新功能时,必须花大量的时间去理解原来的代码,修改原来代码的 错误,改变一项功能需要花费越来越长的时间。 重构支持好的结构、设计和理解性,它让人们更快地开发软件。因为它可以防止软件的腐化, 甚至用于改进设计。 6,,,,有助于代码复审有助于代码复审有助于代码复审有助于代码复审 重构也能对代码复审做出贡献。它能被用于使复审的代码更容易理解,这种范围更广的理解 导致更有用的建议。当把这些建议作为展开重构的起点,由复审者即刻进行实现的话,代码复审 就能交付具体的结果。通过重构,重建设计的结构,代码变得更容易阅读,代码复审变得更成功。 7,,,,增加灵增加灵增加灵增加灵活性活性活性活性 在传统的开发中,需要的灵活性必须预设并体现在系统的设计中,这使得灵活性成为必需付 出较高代价的事情。如果使用重构技术,灵活性不再是开发过程中强加的约束。 重构降低初始设计的复杂程度。模式有其成本(间接性、复杂化),因此设计应该达到需求所 要求的灵活性,而不是越灵活越好。 如果在设计期间试图介入太多以后可能需要的灵活性,就会产生不必要的复杂和错误。重构 能够以多种方式扩展设计。他鼓励为手头的任务建立刚好适合的解决方案,当新的需求来到时, 可以通过重构扩展设计。 七七七七、、、、重构的不足和风险重构的不足和风险重构的不足和风险重构的不足和风险 1,,,,重构的成本重构的成本重构的成本重构的成本 重构的成本取决于所处的环境支不支持某些重构的基本原则。因为重构极大地依赖于每一小 步后的测试,因而拥有一套可靠的单元测试工具就能大量地降低手工测试成本。 应用重构涉及改变接口、名字、参数表等等,更新项目文档的成本不应低估。这就导致整个 系统设计的改变。由于在重构步骤之间老接口首先被保持,以后逐渐被新的所扩展,所以,全部 测试都不用改变,应该在整个重构期间内运行。之后当老的接口被取消,而支持新接口时,测试 也必须被更新。 2,,,,重构的风险重构的风险重构的风险重构的风险 由重构引起的软件失效可能具有严重的后果。因此不应该轻易地对待重构,必须小心奕奕, 并在在头脑中想着可能发生的问题。小步前进,每步后进行测试,以可预见的方式作改变。 重新组织不要与添加新功能混在一起工作,这些重构原则使得不可能在重构时引入错误。尽 管如此,程序员不可能不犯错误。 因此在重构应用于系统之后,系统不仅应该通过必不可少的单元测试,而且也应该通过回归 功能测试,以表明重构后的系统与上一次的运行没有差别。即使在重构时引入了错误,由于重构 使系统具有良好结构,不带有重复代码,清晰的层次,以及小的单元,所以追踪这些错误(一旦 它们表现出来)将变得容易得多。 事实上,重构的一个重要的风险在于,需要一支高水平的程序员队伍,他们必须对重构技术 有深入的理解,而不至于使系统不受控的偏移到不希望的方向,这对过程控制的水平提出了更高 的要求。 八八八八、、、、设计模式设计模式设计模式设计模式 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 154 - 在模块设计阶段,最关键的问题是,用户需求是变化的,我们的设计如何适应这种变化呢?  如果我们试图发现事情怎样变化,那我们将永远停留在分析阶段。  如果我们编写的软件能面向未来,那将永远处在设计阶段。  我们的时间和预算不允许我们面向未来设计软件。过分的分析和过分的设计,事实上被 称之为“分析瘫痪”。 如果我们预料到变化将要发生,而且也预料到将会在哪里发生。这样就形成了几个原则:  针对接口编程而不是针对实现编程。  优先使用对象组合,而不是类的继承。  考虑您的设计哪些是可变的,注意,不是考虑什么会迫使您的设计改变,而是考虑要素 变化的时候,不会引起重新设计。 也就是说,封装变化的概念是模块设计的主题。解决这个问题,我们需要研究一下软件重构 技术。而重构技术影响最大而且最成功的应用,要数 GoF 的 23 种设计模式,在 GoF 中,把设计 模式分为结构型、创建型和行为型三大类,从不同的角度讨论了软件重构的方法。本课程假定学 员已经熟悉这 23 个模式,因此主要从设计的角度讨论如何正确选用恰当的设计模式。整个讨论 依据三个原则: 1)开放-封闭原则; 2)从场景进行设计的原则; 3)包容变化的原则。 下面的讨论会有一些代码例子,尽管在详细设计的时候,并不考虑代码实现的,但任何架构 设计思想如果没有代码实现做基础,将成为无木之本,所以后面的几个例子我们还是把代码实现 表示出来,举这些例子的目的并不是提供样板,而是希望更深入的描述想法。另外,所用的例子 大部分使用 Java 来编写,这主要因为希望表达比较简单,但这不是必要的,可以用任何面向对 象的语言(C#、C++ )来讨论这些问题。 九九九九、、、、封装变化与面向接口编程封装变化与面向接口编程封装变化与面向接口编程封装变化与面向接口编程 设计模式分为结构型、构造型和行为型三种问题域,我们来看一下行为型设计模式,行为型 设计模式的要点之一是“封装变化”,这类模式充分体现了面向对象的设计的抽象性。在这类模 式中,“动作”或者叫“行为”,被抽象后封装为对象或者为方法接口。通过这种抽象,将使“动 作”的对象和动作本身分开,从而达到降低耦合性的效果。这样一来,使行为对象可以容易的被 维护,而且可以通过类的继承实现扩展。 行为型模式大多数涉及两种对象,即封装可变化特征的新对象,和使用这些新对象的已经有 的对象。二者之间通过对象组合在一起工作。如果不使用这些模式,这些新对象的功能就会变成 这些已有对象的难以分割的一部分。因此,大多数行为型模式具有如下结构。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 155 - 5.2 处理类或者接口的变化处理类或者接口的变化处理类或者接口的变化处理类或者接口的变化 有一些变化包括类的变化或者接口的变化,当预测这种变化可能存在的时候,可以使用外观 模式或者适配器模式。 一一一一、、、、外观模式外观模式外观模式外观模式((((Facade )))) 1,,,,意图意图意图意图 外观模式定义了一个把子系统的一组接口集成在一起的高层接口,以提供一个一致的处理方 式,其它系统可以方便的调用子系统中的功能,而忽略子系统内部发生的变化。 2,,,,使用场合使用场合使用场合使用场合 1)为一个比较复杂的子系统,提供一个简单的接口。 2)把客户程序和子系统的实现部分分离,提高子系统的独立性和可移植性。 3)简化子系统的依赖关系 3,,,,结构结构结构结构 下图是外观模式的结构,由于该模式的引入,所以外界访问通过这个统一的接口进行,系统 的复杂性得以降低。 4,,,,使用效果使用效果使用效果使用效果 外观模式为用户提供了使用子系统组件的统一的接口,使用户减少了处理对象的数目,并且 使子系统使用简单。使用外观模式使子系统和客户之间实现松散耦合关系,由于用户针对接口编 程,因此子系统的变化不会影响到客户的变化,而且有助于分层架构的实现。 如图所示某信息系统的总体体系结构图,其中客户端使用了分层结构。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 156 - 在这个结构中,应用层与领域层的连接关系非常混乱,领域层中类的任何变化都可能引起应 用层大量的未知变化,使系统的升级时成本很高,可维护性大幅降低。由于这种混乱是在类的(或 者模块)的层面上,为了解决这个问题,我们可以使用外观模式,如下图所示。 这种模式一个显而易见的好处,本来一个类的修改可能会影响一大片代码,而加了外观类以 后只需要修改很少量的代码就可以了,这就使系统的高级维护成为可能。 二二二二、、、、适配器模式适配器模式适配器模式适配器模式((((Adapter )))) 在系统之间集成的时候,最常见的问题是接口不一致,很多能满足功能的软件模块,由于接 口不同,而导致无法使用。在这种情况下可以使用适配器模式。 1,,,,意图意图意图意图 适配器模式的含义在于,把一个类的接口转换为另一个接口,使原本不兼容而不能一起工作 的类能够一起工作。 2,,,,结构结构结构结构 适配器有类适配器和对象适配器两种类型,二者的意图相同,只是实现的方法和适用的情况 不同。类适配器采用继承的方法来实现,而对象适配器采用组合的方法来实现。 1))))类适配器类适配器类适配器类适配器 类适配器采用多重继承对一个接口与另一个接口进行匹配,其结构如下。 这种方式有一个限制,由于大部分面向对象语言不承认多重继承,所以当 Adapter 类需要继 承多个类的时候就没有办法实现,这时,可以使用对象适配器。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 157 - 2))))对象适配器对象适配器对象适配器对象适配器 对象适配器采用对象组合,通过引用一个类与另一个类的接口,来实现对多个类的适配。 3,,,,使用场合使用场合使用场合使用场合 当需要使用一个已经存在的类,但接口与设计要求不符合的时候,使用适配器模式可以是一 个解决方案。例如,如图所示某信息系统的总体体系结构图,其中“三维图形处理组件”是外包 开发,该组件接口随着版本升级可能发生改变。由于接口变化将会引起客户端多处发生改变,使 系统的升级时成本很高,可维护性大幅降低。 可以使用设配器模式来屏蔽接口的变化,如下图所示。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 158 - 5.3 封装封装封装封装业务单元的业务单元的业务单元的业务单元的变化变化变化变化 设计可升级的架构,关键是要把模块中不变部分与预测可变部分分开,以防止升级过程中对 基本代码的干扰。如果我们遇到的是业务流程不变,但是业务单元可能改变,这种分离可以有多 种方式,一般来说可以从纵向、横向以及外围三个方面考虑。 一一一一、、、、模板方法模板方法模板方法模板方法((((Template Method )))) 1、、、、意图意图意图意图 软件复用的关键是寻找相似性,在很多情况下,相似性表现为业务流程相似,但是业务单元 具有特殊性。如果是这种情况,可以定义一个操作中的业务流程骨架,而将一些业务单元的实现 延伸到子类中去,使得子类可以不改变一个业务流程的的结构,即可重新定义该业务流程的某些 特定业务单元。这里需要复用的是业务流程的结构,也就是操作步骤,而步骤的实现(或者是业 务单元的实现)可以在子类中完成。 2、、、、使用场合使用场合使用场合使用场合 1)一次性实现一个业务流程的不变部分,并且将可变的行为留给子类来完成。 2)各子类公共的行为应该被提取出来并集中到一个公共父类中以避免代码的重复。首先识 别现有业务的不同之处,并且把不同部分分离为新的操作,最后,用一个调用这些新的操作的模 板方法来替换这些不同的代码。 3)控制子类的扩展。 3、、、、结构结构结构结构 模板方法的结构是使用一个抽象类,在抽象类中定义模板方法的关键是: 在一个非抽象方法中调用调用抽象方法,而这些抽象方法在子类中具体实现。 代码: public abstract class Payment{ private double amount; public double getAmount(){ return amount; } public void setAmount(double value){ amount = value; ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 159 - } public String goSale(){ String x = " 不变的流程一 "; x += Action(); // 可变的流程 x += amount + ", 正在查询库存状态"; // 属性和不变的流程二 return x; } public abstract String Action(); } class CashPayment extends Payment{ public String Action(){ return " 现金支付"; } } 测试: public class Test{ public static void main (String[] args){ Payment o; o = new CashPayment(); o.setAmount(555); System.out.println(o.goSale()); } } 假定系统已经投运,用户提出新的需求,要求加上信用卡支付和支票支付,可以这样写: ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 160 - public class CreditPayment extends Payment{ public String Action(){ return "信用卡支付,联系支付机构"; } } class CheckPayment extends Payment{ public String Action(){ return "支票支付,联系财务部门"; } } 调用: public class Test{ public static void main (String[] args){ Payment o; o = new CashPayment(); o.setAmount(555); System.out.println(o.goSale()); o = new CreditPayment(); o.setAmount(555); System.out.println(o.goSale()); o = new CheckPayment(); o.setAmount(555); System.out.println(o.goSale()); } } 二二二二、、、、简单工厂简单工厂简单工厂简单工厂模式模式模式模式((((Simpleness FactoryFactoryFactoryFactory)))) 1、、、、意图意图意图意图 简单工厂的作用是实例化对象,而不需要客户了解这个对象属于哪个具体的子类。 在 GoF 的设计模式中并没有简单工厂,而是把它作为工厂方法的一个特例加以解释,可以 这样来理解,简单工厂是参数化的工厂方法,由于它可以处理粒度比较大的问题,所以还是单独 列出来比较有利。 2、、、、使用场合和效果使用场合和效果使用场合和效果使用场合和效果 简单工厂实例化的类具有相同的接口,类的个数有限而且基本上不需要扩展的时候,可以使 用简单工厂。 使用简单工厂的优点是用户可以根据参数获得类的实例,避免了直接实例化类的麻烦,降低 了系统的耦合度。缺点是实例化的类型在编译的时候已经确定,如果增加新的类,需要修改工厂。 简单工厂需要知道所有要生成的类型,在子类过多或者子类层次过多的时候并不适合使用。 3、、、、结构结构结构结构 通常简单工厂不需要实例化,而是采用静态方法来实现。下面是一个简单工厂的基本框架, Factory 类为工厂类,里面的静态方法 PaymentFactory 决定了实例化哪个子类,而在这个方法里 面,通过多分支语句来控制具体实现的子类。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 161 - // Payment.java public abstract class Payment{ private double amount; public double getAmount(){ return amount; } public void setAmount(double value){ amount = value; } public String goSale(){ String x = " 不变的流程一 "; x += Action(); // 可变的流程 x += amount + ", 正在查询库存状态"; // 属性和不变的流程二 return x; } public abstract String Action(); } class CashPayment extends Payment{ public String Action(){ return " 现金支付"; } } // CreditPayment.java public class CreditPayment extends Payment{ public String Action(){ return " 信用卡支付,联系支付机构"; } } ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 162 - class CheckPayment extends Payment{ public String Action(){ return " 支票支付,联系财务部门"; } } // 这是一个工厂类 Factory.java public class Factory{ public static Payment PaymentFactory(String PaymentName){ Payment mdb=null; if (PaymentName.equals(" 现金")) mdb=new CashPayment(); else if (PaymentName.equals(" 信用卡")) mdb=new CreditPayment(); else if (PaymentName.equals(" 支票")) mdb=new CheckPayment(); return mdb; } } 调用: public class Test{ public static void main (String[] args){ Payment o; o = Factory.PaymentFactory(" 现金"); o.setAmount(555); System.out.println(o.goSale()); o = Factory.PaymentFactory(" 信用卡"); o.setAmount(555); System.out.println(o.goSale()); o = Factory.PaymentFactory(" 支票"); o.setAmount(555); System.out.println(o.goSale()); } } 三三三三、、、、桥接模式桥接模式桥接模式桥接模式((((Bridge )))) 模板方法是利用继承来完成切割,当对耦合性要求比较高,无法使用继承的时候,可以横向 切割,也就是使用桥接模式。 我们还是通过上面的关于支付的简单例子可以说明它的原理。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 163 - 显然,它具备和模版方法相类似的能力,但是不采用继承。 public class Payment{ private double amount; public double getAmount(){ return amount; } public void setAmount(double value){ amount = value; } private Implementor imp; public void setImp(Implementor s){ imp=s; } public String goSale(){ String x = "不变的流程一 "; x += imp.Action(); //可变的流程 x += amount + ", 正在查询库存状态"; //属性和不变的流程二 return x; } } interface Implementor{ public String Action(); } class CashPayment implements Implementor{ public String Action(){ return "现金支付"; } ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 164 - } 调用: public class Test{ public static void main (String[] args){ Payment o=new Payment(); o.setImp(new CashPayment()); o.setAmount(555); System.out.println(o.goSale()); } } 假定系统已经投运,用户提出新的需求,要求加上信用卡支付和支票支付,您将怎样处理呢? public class CreditPayment implements Implementor{ public String Action(){ return "信用卡支付,联系支付机构"; } } class CheckPayment implements Implementor{ public String Action(){ return "支票支付,联系财务部门"; } } 调用: public class Test{ public static void main (String[] args){ Payment o=new Payment(); o.setImp(new CashPayment()); ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 165 - o.setAmount(555); System.out.println(o.goSale()); o.setImp(new CreditPayment()); o.setAmount(555); System.out.println(o.goSale()); o.setImp(new CheckPayment()); o.setAmount(555); System.out.println(o.goSale()); } } 这样就减少了系统的耦合性。而在系统升级的时候,并不需要改变原来的代码。 四四四四、、、、装饰器模式装饰器模式装饰器模式装饰器模式((((Decorator )))) 有的时候,希望实现一个基本的核心代码快,由外围代码实现专用性能的包装,最简单的方 法,是使用超类,但是超类使用了继承而提升了耦合性。在这样的情况下,也可以使用装饰器模 式,这是用组合取代继承的一个很好的方式。 1、、、、意图意图意图意图 事实上,上面所要解决的意图可以归结为“在不改变对象的前提下,动态增加它的功能”, 也就是说,我们不希望改变原有的类,或者采用创建子类的方式来增加功能,在这种情况下,可 以采用装饰模式。 2、、、、结构结构结构结构 装饰器结构的一个重要的特点是,它继承于一个抽象类,但它又使用这个抽象类的聚合(即 即装饰类对象可以包含抽象类对象),恰当的设计,可以达到我们提出来的目的。 假定我们已经构造了一个基于支付的简单工厂模式的系统。现在需要每个类在调用方法 goSale() 的时候,除了完成原来的功能以外,先弹出一个对话框,显示工厂的名称,而且不需要 改变来的系统,为此,在工厂类的模块种添加一个装饰类 Decorator ,同时略微的改写一下工厂 类的代码。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 166 - //装饰类 public class Decorator extends Payment{ private String strName; public Decorator(String strName){ this.strName = strName; } private Payment pm; public void setPm(Payment value){ pm = value; } public String Action(){ //在执行原来的代码之前,显示提示框 System.out.println(strName); return pm.Action(); } } 而工厂类: //这是一个工厂类 public class Factory{ public static Payment PaymentFactory(String PaymentName){ Payment mdb=null; if (PaymentName.equals("现金")) mdb=new CashPayment(); else if (PaymentName.equals("信用卡")) mdb=new CreditPayment(); else if (PaymentName.equals("支票")) mdb=new CheckPayment(); //return mdb; Decorator m=new Decorator(PaymentName); m.setPm(mdb); return m; } } 可以说,这是在用户不知晓的情况下,也不更改原来的类的情况下,改变了性能。 5.4 利用观察者模式处理业务单元的变化利用观察者模式处理业务单元的变化利用观察者模式处理业务单元的变化利用观察者模式处理业务单元的变化 当需要上层对底层的操作的时候,可以使用观察者模式实现向上协作。也就是上层响应底层 的事件,但这个事件的执行代码由上层提供。 1、、、、意图意图意图意图:::: 定义对象一对多的依赖关系,当一个对象发生变化的时候,所有依赖它的对象都得到通知并 且被自动更新。 2、、、、结构结构结构结构 传统的观察者模式结构如下。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 167 - 3,,,,举例举例举例举例:::: // PaymentEvent.java 传入数据的类 import java.util.*; public class PaymentEvent extends EventObject { private String Text; // 定义 Text 内部变量 // 构造函数 public PaymentEvent(Object source,String Text) { super(source); this.Text=Text; // 接收从外部传入的变量 } public String getText(){ return Text; // 让外部方法获取用户输入字符串 } } // 监听器接口 //PaymentListener.java import java.util.*; public interface PaymentListener extends EventListener { public String Action(PaymentEvent e); } // Payment.java 平台类 import java.util.*; public class Payment{ private double amount; public double getAmount(){ return amount; } public void set(double value){ amount = value; } //事件编程 private ArrayList elv; //事件侦听列表对象 //增加事件事件侦听器 public void addPaymentListener(PaymentListener m) { //如果表是空的,先建立它的对象 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 168 - if (elv==null){ elv=new ArrayList(); } //如果这个侦听不存在,则添加它 if (!elv.contains(m)){ elv.add(m); } } //删除事件侦听器 public void removePaymentListener(PaymentListener m) { if (elv!= null && elv.contains(m)) { elv.remove(m); } } //点火 ReadText 方法 protected String fireAction(PaymentEvent e) { String m=e.getText(); if (elv != null) { //激活每一个侦听器的 WriteTextEvett 事件 for (int i = 0; i < elv.size(); i++) { PaymentListener s=(PaymentListener)elv.get(i); m+=s.Action(e); } } return m; } public String goSale(){ String x = "不变的流程一 "; PaymentEvent m=new PaymentEvent(this,x); x = fireAction(m); //可变的流 x += amount + ", 正在查询库存状态"; //属性和不变的流程二 return x; } } 调用:Test.java import java.util.*; public class Test { //入口 public static void main(String[] args) { Payment o1 = new Payment(); Payment o2 = new Payment(); Payment o3 = new Payment(); ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 169 - o1.addPaymentListener(new PaymentListener(){ public String Action(PaymentEvent e){ return e.getText()+" 现金支付 "; } }); o2.addPaymentListener(new PaymentListener(){ public String Action(PaymentEvent e){ return e.getText()+" 信用卡支付 "; } }); o3.addPaymentListener(new PaymentListener(){ public String Action(PaymentEvent e){ return e.getText()+" 支票支付 "; } }); o1.set(777); o2.set(777); o3.set(777); System.out.println(o1.goSale()); System.out.println(o2.goSale()); System.out.println(o3.goSale()); } } 5.5 利用策略与工厂模式实现通用的框架利用策略与工厂模式实现通用的框架利用策略与工厂模式实现通用的框架利用策略与工厂模式实现通用的框架 一一一一、、、、应用策略模式提升层的通用性应用策略模式提升层的通用性应用策略模式提升层的通用性应用策略模式提升层的通用性 1、、、、意图意图意图意图 将算法封装,使系统可以更换或扩展算法,策略模式的关键是所有子类的目标一致。 2、、、、结构结构结构结构 策略模式的结构如下。 其中:Strategy (策略):抽象类,定义需要支持的算法接口,策略由上下文接口调用。 二二二二、、、、示例示例示例示例::::利用反射实现通用框架利用反射实现通用框架利用反射实现通用框架利用反射实现通用框架 目标: ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 170 - 构造一个 Bean 容器框架,可以动态装入和构造对象,装入类可以使用配置文件,这里利用 了反射技术。 问题: 如何动态构造对象,集中管理对象。 解决方案: 策略模式,XML 文档读入,反射的应用。 我们现在要处理的架构如下: Bean.xml 文档的结构如下。 说明 内容 ……….. 应用程序上下文接口:ApplicationContext.java 只有一个方法,也就是由用户提供的 id 提供 Bean 的实例。 package springdemo; public interface ApplicationContext{ public Object getBean(String id) throws Exception; } 上下文实现类:FileSystemXmlApplicationContext.java ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 171 - package springdemo; import java.util.*; import javax.xml.parsers.*; import org.w3c.dom.*; import java.io.*; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.*; public class FileSystemXmlApplicationContext implements ApplicationContext{ // 用一个哈西表保留从 XML 读来的数据 private Hashtable hs=new Hashtable(); public FileSystemXmlApplicationContext(String fileName){ try{ readXml(fileName); } catch(Exception e){ e.printStackTrace(); } } // 私有的读 XML 方法。 private void readXml(String fileName) throws Exception{ // 读 XML 把数据放入哈西表 hs=Configuration.Attribute(fileName,"bean","property"); } public Object getBean(String id) throws Exception{ // 由 id 取出内部的哈西表对象 Hashtable hsb=(Hashtable)hs.get(id); // 利用反射动态构造对象 Object obj =Class.forName(hsb.get("class").toString()).newInstance(); java.util.Enumeration hsNames1 =hsb.keys(); // 利用反射写入属性的值 while (hsNames1.hasMoreElements()) { // 写入利用 Set 方法 String ka=(String)hsNames1.nextElement(); if (! ka.equals("class")){ // 写入属性值为字符串 String m1="String"; ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 172 - Class[] a1={m1.getClass()}; // 拼接方法的名字 String sa1=ka.substring(0,1).toUpperCase(); sa1="set"+sa1+ka.substring(1); // 动态调用方法 java.lang.reflect.Method fm=obj.getClass().getMethod(sa1,a1); Object[] a2={hsb.get(ka)}; // 通过 set 方法写入属性 fm.invoke(obj,a2); } } return obj; } } // 这是一个专门用于读配置文件的类 class Configuration{ public static Hashtable Attribute(String configname, String mostlyelem, String childmostlyelem) throws Exception{ Hashtable hs=new Hashtable(); // 建立文档,需要一个工厂 DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance(); DocumentBuilder builder=factory.newDocumentBuilder(); Document doc=builder.parse(configname); // 建立所有元素的列表 Element root = doc.getDocumentElement(); // 把所有的主要标记都找出来放到节点列表中 NodeList elemList = root.getElementsByTagName(mostlyelem); for (int i=0; i < elemList.getLength(); i++){ // 获取这个节点的属性集合 NamedNodeMap ac = elemList.item(i).getAttributes(); // 构造一个表,记录属性和类的名字 Hashtable hs1=new Hashtable(); hs1.put("class",ac.getNamedItem("class").getNodeValue()); // 获取二级标记子节点 Element node=(Element)elemList.item(i); // 获取第二级节点的集合 NodeList elemList1 =node.getElementsByTagName(childmostlyelem); for (int j=0; j < elemList1.getLength(); j++){ // 获取这个节点的属性集合 NamedNodeMap ac1 = elemList1.item(j).getAttributes(); String key=ac1.getNamedItem("name").getNodeValue(); NodeList node1=((Element)elemList1.item(j)).getElementsByTagName("value"); ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 173 - String value=node1.item(0).getFirstChild().getNodeValue(); hs1.put(key,value); } hs.put(ac.getNamedItem("id").getNodeValue(),hs1); } return hs; } } 做一个程序实验一下。 首先做一个关于交通工具的接口:Vehicle.java package springdemo; public interface Vehicle { public String execute(String str); public String getMessage(); public void setMessage(String str); } 做一个实现类:Car.java package springdemo; public class Car implements Vehicle { private String message=""; private String x; public String getMessage(){ return message; } public void setMessage(String str){ message = str; } public String execute(String str){ return getMessage() + str+" 汽车在公路上开"; } } Bean.xml 文档。 Spring Quick Start ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 174 - hello! 测试:Test.java public class Test{ public static void main (String[] args) throws Exception{ springdemo.ApplicationContext m= new springdemo.FileSystemXmlApplicationContext("d:\\Bean.xml"); // 实现类,使用标记 Car springdemo.Vehicle s1=(springdemo.Vehicle)m.getBean("Car"); System.out.println(s1.execute(" 我的")); } } 基于接口编程将使系统具备很好的扩充性。 再做一个类:Train.java package springdemo; public class Train implements Vehicle { private String message=""; public String getMessage(){ return message; } public void setMessage(String str){ message = str; } public String execute(String str) { return getMessage() + str+" 火车在铁路上走"; } } Bean.xml 改动如下。 Spring Quick Start hello! ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 175 - haha! 改动一下 Test.java public class Test{ public static void main (String[] args) throws Exception{ springdemo.ApplicationContext m= new springdemo.FileSystemXmlApplicationContext("d:\\Bean.xml"); // 实现类,使用标记 Car springdemo.Vehicle s1=(springdemo.Vehicle)m.getBean("Car"); System.out.println(s1.execute(" 我的")); springdemo.Vehicle s2=(springdemo.Vehicle)m.getBean("Train"); System.out.println(s2.execute(" 你的")); } } 我们发现,在加入新的类的时候,使用方法几乎不变。通过上面的讨论,我们当对框架实现 技术有了一个基本的理解。 5.6 单件模式的应用问题单件模式的应用问题单件模式的应用问题单件模式的应用问题 有时候,我们需要一个全局唯一的连接对象,这个对象可以管理多个通信会话,在使用这个 对象的时候,不关心它是否实例化及其内部如何调度,这种情况很多,例如串口通信和数据库访 问对象等等,这些情况都可以采用单件模式。 1、、、、意图意图意图意图 单件模式保证应用只有一个全局唯一的实例,并且提供一个访问它的全局访问点。 2、、、、使用场合使用场合使用场合使用场合 当类只能有一个实例存在,并且可以在全局访问的时候,这个唯一的实例应该可以通过子类 实现扩展,而且用户无需更改代码即可以使用。 3、、、、结构结构结构结构 单件模式的结构非常简单,包括防止其它对象创建实例的私有构造函数,保持唯一实例的私 有变量和全局变量访问接口等,请看下面的例子: class CShapeSingletion{ private static CShapeSingletion mySingletion=null; // 为了防止用户实例化对象,这里把构造函数设为私有的 private CShapeSingletion() {} // 这个方法是调用的入口 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 176 - public static CShapeSingletion ShapeInstance(){ if (mySingletion==null){ mySingletion=new CShapeSingletion(); } return mySingletion; } private int intCount=0; // 计数器,虽然是实例方法,但这里的表现类同静态 public int Count(){ intCount+=1; return intCount; } } // 调用代码: public class MySingletion{ public static void main (String[] args){ CShapeSingletion m1; CShapeSingletion m2; CShapeSingletion m3; CShapeSingletion m4; m1=CShapeSingletion.ShapeInstance(); m2=CShapeSingletion.ShapeInstance(); m3=CShapeSingletion.ShapeInstance(); m4=CShapeSingletion.ShapeInstance(); System.out.println(m1.Count()); System.out.println(m2.Count()); System.out.println(m3.Count()); System.out.println(m4.Count()); } } 4、、、、效果效果效果效果 单件提供了全局唯一的访问入口,因此比较容易控制可能发生的冲突。 单件是对静态函数的一种改进,首先避免了全局变量对系统的污染,其次它可以有子类,业 可以定义虚函数,因此它具有多态性,而类中的静态方法是不能定义成虚函数的。 单件模式也可以定义成多件,即允许有多个受控的实例存在。 单件模式维护了自身的实例化,在使用的时候是安全的,一个全局对象无法避免创建多个实 例,系统资源会被大量占用,更糟糕的是会出现逻辑问题,当访问象串口这样的资源的时候,会 发生冲突。 5、、、、单件与实用类中的静态方法单件与实用类中的静态方法单件与实用类中的静态方法单件与实用类中的静态方法 实用类提供了系统公用的静态方法,并且也经常采用私有的构造函数,和单件不同,它没有 实例,其中的方法都是静态方法。 实用类和单件的区别如下: 1)实用类不保留状态,仅提供功能。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 177 - 2)实用类不提供多态性,而单件可以有子类。 3)单件是对象,而实用类只是方法的集合。 应该说在实际应用中,实用类的应用更加广泛,但是在涉及对象的情况下需要使用单件,例 如,能不能用实用类代替抽象工厂呢?如果用传统的方式显然不行,因为实用类没有多态性,会 导致每个工厂的接口不同,在这个情况下,必须把工厂对象作为单件。 因此何时使用单件,要具体情况具体分析,不能一概而论。 5.7 代理模式的应用代理模式的应用代理模式的应用代理模式的应用 一一一一、、、、代理模式简述代理模式简述代理模式简述代理模式简述 代理模式的意图,是为其它对象提供一个代理,以控制对这个对象的访问。 首先作为代理对象必须与被代理对象有相同的接口,换句话说,用户不能因为使不使用代理 而做改变。其次,需要通过代理控制对对象的访问,这时,对于不需要代理的客户,被代理对象 应该是不透明的,否则谈不上代理。下图是代理模式的结构。 二二二二、、、、案例案例案例案例::::在团队并行开发中使用代理模式在团队并行开发中使用代理模式在团队并行开发中使用代理模式在团队并行开发中使用代理模式 软件开发需要协同工作,希望开发进度能够得到保证,为此需要合理划分软件,每个成员完 成自己的模块,为同伴留下相应的接口。 在开发过程中,需要不断的测试。然而,由于软件模块之间需要相互调用,对某一模块的测 试,又需要其它模块的配合。而且在模块开发过程中也不可能完全同步,从而给测试带来了问题。 假定在我们设计的订单处理子系统中, Ordre (订单)类在计算订购项目金额总合的时候, 需要由客户服务子系统根据客户级别和销售策略来计算实际收取的费用,而客户服务系统由 OrderItme (订单项)类提供这个服务,显然这是由另一个开发组完成。 其中:Ordre 包括若干 OrderItme ,订单的总价是每个订单项之和。 在两个开发组共同完成这个项目的情况下,如果 OrderItme 没有完成,Ordre 也就没有办法 测试。一个简单的办法,是 Ordre 开发的时候屏蔽 OrderItme 调用,但这样代码完成的时候需要 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 178 - 做大量的垃圾清理工作,显然这是不合适的,我们的问题是,如何把测试代码和实际代码分开, 这样更便于测试,而且可以很好的集成。 如果我们把 OrderItem 抽象为一个接口或一个抽象类,实现部分有两个平行的子类,一个是 真正的 OrderItem ,另一个是供测试用的 TestOrderItem ,在这个类中编写测试代码,我们称之为 Mock 。 这时,Order 可以使用 TestOrderItem ,测试。当 OrderItem 完成以后,有需要使用 OrderItem 进行集成测试,如果 OrderItem 还要修改,又需要转回 TestOrderItem 。 我们希望只用一个参数就可以完成这种切换,比如在配置文件中,测试设为 true ,而正常使 用为 false 。 这些需求牵涉到代理模式的应用,现在可以把代理结构画清楚。 这就很好的解决了问题,实例: 在配置文件 Bean.xml 中加一个元素: 编写抽象的定单类: package demo; // 这是统一的接口 public abstract class AbstractOrderItem{ private String goodName; private long count; private double price; public void setGoodName(String m_GoodName){ goodName=m_GoodName; } public String getGoodName(){ return goodName; } public void setCount(long m_Count){ count=m_Count; ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 179 - } public long getCount(){ return count; } public void setPrice(double m_Price){ price=m_Price; } public double getPrice(){ return price; } // 价格求和,这个计算方式是另外的人编写的 public abstract double GetTotalPrice() throws Exception; } 编写订单代码: package demo; // 处理订单代码 public class Order{ public String Name; //public DateTime OrderDate; private java.util.ArrayList oitems; public Order(){ oitems=new java.util.ArrayList(); } public void AddItem(AbstractOrderItem it){ oitems.add(it); } public void RemoveItem(AbstractOrderItem it){ oitems.remove(it); } public double OrderPrice() throws Exception{ AbstractOrderItem it; double op=0; for (int i=0;i 定义了一个 SOAP 包封,它包装了 SOAP 消息的全部内容。 2))))SOAP 头头头头:::: SOAP 头是选配的,使用头必须构造一个继承于 SoapHeader 的类。它的用途是要包括对于 应用程序可能是非常重要的非方法调用的附加信息,比如验证、事务等等。它包含同步一个双方 提交情境中一些合作伙伴的消息。 下面是一个请求的 SOAP 信息: string 而响应信息可以是这样的格式: string 其中: 为 SOAP 头标记 为 SOAP 体标记 使用头元素必须遵循以下几个规定:  头元素是选配的,但一旦使用,就必须放在整个包封的第一个,也就是 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 203 - 标记的后面,否则就是错误的。  头元素必须用命名空间修饰,比如: 3))))SOAP 体体体体:::: SOAP 体中包含了双方需要传递的数据,由于 GetPassWord 方法是个无参数请求,所以请求 中并不需要代参数,对于 Sum 方法而言,请求中就需要加入参数: 5 6 对应的响应格式如下: 11 2,,,,解读解读解读解读 WSDL WSDL 是一个契约,你必须用某种方式向客户解释如何通过编程来调用这些服务,至少, 您的客户需要知道它必须传递什么参数,以及可以得到什么返回结果。也需要知道 Web 服务的 实际地址,以及如何访问这个 Web 服务。这就需要一个机制,WSDL 协议就是为了解决这个问 题而设立的。为了了解 WSDL,我们可以解读上面的例子生成的 WSDL 文档。 1))))definitions ((((定义定义定义定义)))) 整个 WSDL 封装在 definitions 下。它首先定义了文档其它部分使用的最通用的协议、数据 类型、模式的命名空间。必须说明,命名空间并不是必需的,但它有助于消除一些含糊之处,并 可以避免名字上的冲突。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 204 - 2))))类型部分类型部分类型部分类型部分 类型部分定义了在 Web 服务中使用了哪些数据类型,以及可以从那里找到这些数据类型。 事实上,这很象一般编程上的数据类型定义,也执行先定义后使用的原则。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 205 - 3))))消息部分消息部分消息部分消息部分 消息部分通过把特定消息的组成部分组成在一起,抽象的定义了送入和送出的消息。,基本 上它是作为类型部分显式定义的参数具体化为参数传递的方式。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 206 - 4))))接口部分接口部分接口部分接口部分 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 207 - ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 208 - 显然,WSDL 作为契约足够确切而且无歧义。 五五五五、、、、面向服务的架构面向服务的架构面向服务的架构面向服务的架构 1,,,,面向服务架构的思考方式面向服务架构的思考方式面向服务架构的思考方式面向服务架构的思考方式 面向服务的架构(SOA)是一种设计方式,它指导业务服务在其生命周期(从构思开始,直 到停止使用)包括创建和使用的方方面。面向服务的架构也是一种定义和提供 IT 基础设施的方 式,它允许不同的应用相互交换数据、参与业务流程,而不必关心它们各自背后使用的是何种操 作系统,或者采用什么编程语言。 SOA 可以被看成一种构建 IT 系统的方案,它把业务服务(也就是一个组织向顾客、客户、 合作伙伴直接或间接提供的服务)作为协调 IT 系统与业务需求的关键组织原则。相反,早期构 建 IT 系统的方案,大多数受限于特定的使用环境来处理业务问题,结果造成 IT 系统依赖于具体 执行环境的特点和性能。 SOA 的概念本身并不新,新颖之处在于它能够混合搭配各种执行环境,这样,IT 部门就可 以为新的或者现有的应用选择最佳的执行环境,并且采用一致的架构方式把它们结合起来,这与 原先基于一种执行一种环境和技术的 SOA 是有区别的。 关于“接口与实现分离”的思想,是早已在 J2EE 、COM 中实现了并得到充分检验了的。但 是,SOA 主要是通过文本文件来实现服务与其执行环境的分离,而且分离的更明确、也更完全。 SOA 的这种能力是新增的,它主要借鉴于 Web 的概念和技术。传统的接口事先并没有考虑到这 种“松散的”分离,因为这会影响它的效率。然而在许多情况下,易于获得互操作性的能力要比 效率更重要。我们需要注意到,互操作性是业界一直在努力争取的,但至今仍然没有彻底实现的 目标。 使用基于 WSDL 这样的 XML 文本方式描述接口和契约,就可以建立标准化的特性描述,任 何语言和技术都可以利用某些符合标准的生成软件,基于软件来自动生成相应语言特征的映射层 代理,而不需要人工来完成,这就比利用与技术相关的方式来描述 Interface 更具通用性。 SOA 是否成功,并不在于 Web 服务给 IT 软件的形式带来什么样的变化,而是依赖于处理问 题的方法发生转变。Web 服务中接口与执行环境的进一步分离,促进了工作职责的分离,企业可 以根据服务描述对业务运营问题的实现进行考虑,以及进行 IT 的投资。而依赖于具体产品的接 口和服务描述的方案则不能这么做。这样,服务描述所定义的特征与功能就可以通过任何技术来 实现。 这样的风格,只有转变了对 IT 的思考方式的企业才能实现这一点。当然,这种风格的转变 并不是说完全不顾领域的特点,而是考虑在本领域中业务操作以更加快速而灵活的方式来实现。 同时,在支持 SOA 的环境中,企业可能不仅仅要考虑不同执行环境中的服务,也要知道如何使 用来自各个厂商的组件来组装系统。 SOA 的真正优点体现在部署以后的各个阶段中,一旦实现了用可重用服务来组合新业务, ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 209 - 就可以实现降低成本,以实现最大的投资回报率(ROI),但实现这一点是需要时间的,并且需 要在服务开发上作相当大的投资。 重用通用的业务服务(比如客户姓名查找、邮政编码验证以及信用检查等)的好处是容易理 解的。在出现 SOA 以前,这些通用功能是在可重用代码库或者类库中完成的。但是在 SOA 中, 这些通用的功能以及常见的系统功能(比如安全检查、事务协调、审核)是通过服务来实现的。 成功的 SOA 项目应该保证可重用的软件服务于实际的业务流程完全一致,业务流程与它的 软件实现良好的配合与协调,令业务的运营流程可以根据外界的环境的变化作出快速变化,从而 使组织能够适应发展的需要。 使用服务,不仅减少了需要部署的代码量,而且集中化的代码部署和管理还减轻了管理、维 护和支持方面的负担。但是要注意到的是,访问服务的效率是需要经过评估的,因为通常使用服 务比使用通用代码库要消耗更多的计算和网络资源。 2,,,,协调不同的服务领域间的异构数据模型协调不同的服务领域间的异构数据模型协调不同的服务领域间的异构数据模型协调不同的服务领域间的异构数据模型 服务领域,指的是一组特定行业(比如金融、销售、市场)相关的服务集合。某一服务领域 中的所有服务应该采用一个通用的数据模型进行通讯。 在使用来自不同服务领域的服务的时候,应该理解不同领域模型间的语义与结构之间的差 异。下图的服务请求者使用了来自不同服务领域的服务(服务 A 和服务 B),两个服务都定义了 “ship data ”,但是各自具有不同的含义。 3,,,,面向服务的集成面向服务的集成面向服务的集成面向服务的集成((((SOI)))) SOI 是在 SOA 的环境下使用 web 服务进行的集成,SOI 是战略性的、系统的使用 Web 服务 来解决集成与互操作的问题。如果希望在某一个集成架构上做巨大的投资,那么 SOI 将是一个 不错的选择。 实现 SOI,应该第一个项目集成之前就开始,在启动 SOI 阶段:  定义 SOA 框架、过程、准则、模型和工具等;  对服务领域进行形式化建模。虽然这些模型不必要做到毫无遗漏,但应该是别处组织用 到一些关键数据类型的、服务契约和过程等。  定义一个服务分类层次,以便各个集成项目可以对服务进行意志的分类和编目,以促进 将来的重用;  如果 Web 服务平台为完成相似项目提供了多种选择,应该选择一种标准化的契约,比 如 WSDL。 在这个框架下,SOI 项目通常包括:  改进现有数据模型,以适应当前集成项目;  根据服务契约对传统系统进行包装,创建当前集成系统所需要的新服务; ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 210 -  定义用于“进行不同数据模型的映射”的数据转换,以便数据能够跨越不同的数据领域 边界;  为 Web 服务平台配置执行环境,以支持并实施企业级的服务质量(如事务需求、有保 证的消息传递、恢复策略等)。 下图显示了 SOI 是如何随着时间的推移而逐步发展的。 最近的经验表明,解决“集成所面临的无数难题”的一个比较好的办法,是为已有的和新的 系统增加一个抽象层,在 Web 服务中是 XML 层。而在这里,WSDL 在服务契约的定义上担任 了关键角色。数据被转换为各个系统都能理解的 XML 形式,在发送时,本地数据被转换为 XML 形式,而接收消息时,XML 被转换为本地数据格式。 在开始阶段,用建模工具创建服务领域的初始数据、服务和过程模型,然后把他们保存在一 个元数据仓库中。各个集成项目都是使用和改进同样的项目开始,所以使用诸如“客户”、“账户” 的定义是一致的,尽管他们在各个传统系统的内不会有不同。IT 系统运行的时候,通过服务注 册库(比如 UDDI)查找服务,然后直接调用服务。 这就形成了集中而又分散的 SOI 架构。 集中集中集中集中:所有的集成项目都访问相同数据。 分散分散分散分散:任何系统都直接使用另一个系统提供的服务,而不需要通过集中控制来访问它。 Web 服务与 SOA 的组合,能够提供一种快速集成方案,它关注共享数据与可重用的服务, 而不是专有的集成产品,因此能够更快、更轻松的确保 IT 投入与企业战略规划保持一致。 下面我们通过一个例子来展示面向服务的集成所带来的优点。 假定有三种比较典型的金融业数据库的应用,它们分别用于支持个人金融业务、公司金融业 务以及共同基金投资等操作。用传统的三层架构来开发应用,将区分表示层、业务逻辑层和数据 库逻辑三个层次。 在系统发展的历程中,这种传统的三层架构可以被重用为一个面向服务的应用,也就是在业 务逻辑层创建服务,利用服务总线把上述应用于其它应用集成,如下图所示。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 211 - 面向服务的一个优点是,如果业务逻辑层支持服务的话,就更容易实现表示逻辑与业务逻辑 的分离,而不论客户端是 GUI 设备还是移动设备。表示逻辑置于一个特定的设备上(比如移动 设备),而通过服务总线与业务逻辑通信,而不必为每种客户端构造紧耦合的表示逻辑层。 使用在业务逻辑层定义的 Web 服务,与其它集成技术相比,更有利于数据交换,因为 XML 可以独立的定义数据类型与结构,它体现了各种软件的公共标准。 在业务逻辑层进行面向服务入口点的开发,使业务流程管理引擎可以启动一个或者多个服务 的自动执行流程。 服务总线实际上是一个公共的业务逻辑层,它通过“服务仓库”来存放和获取服务描述,如 果一个新的应用要使用已有的服务,可以通过查询服务仓库获得服务描述(以 WSDL 表达),然 后通过服务描述生成与该服务交互的 SOAP 消息。 6.3 SOA 与业务流程管理与业务流程管理与业务流程管理与业务流程管理 一一一一、、、、业务流程管理的基本概念业务流程管理的基本概念业务流程管理的基本概念业务流程管理的基本概念 业务流程(business process )是一种现实世界中的活动,它由一系列逻辑上相关的任务组成。 如果根据恰当的顺序和正确的业务规则来执行这些任务,就可以产生业务效果。 我们在“需求抽取与业务建模”一章中定义的需求过程,就是一个典型的业务流程。 业务流程管理(Business Process Management ,BPM)关注的是组织如何识别、建模、开发、 部署和管理业务流程(其中也包括 IT 系统与人交互的过程)。 BPM 的主要目标与优点如下:  减少业务需求与减少业务需求与减少业务需求与减少业务需求与 IT 系统的失配系统的失配系统的失配系统的失配:通过允许业务用户对业务流程进行建模,然后由 IT 部门提供执行和控制这些业务流程的基础设施。  提高员工的生产力提高员工的生产力提高员工的生产力提高员工的生产力,,,,降低运营成本降低运营成本降低运营成本降低运营成本:通过把业务流程自动化和流畅化。  提高组织的机动性和灵活性提高组织的机动性和灵活性提高组织的机动性和灵活性提高组织的机动性和灵活性:通过把业务逻辑与其它业务规则显式分离,并且用一种“易 于随业务需求的变化而修改”的形式来表示业务流程。这样,组织将更具机动性,能够 针对市场的变化做出更快的响应,并快速取得竞争优势。  降低开发成本降低开发成本降低开发成本降低开发成本:通过使用一种高层的、图形化的编程语言,令业务分析师和开发人员可 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 212 - 以在特定的问题领域中快速构建和更新 IT 系统。 业务流程自动化,就是把组织中原来需要由人来处理的活动变成企业级的、高度自动化的系 统。业务流程自动化一般会涉及到业务流程的跟踪,也就是文档、信息或者任务在参与者之间传 递,以确保活动遵守业务规则。 业务流程管理(BPM)一个显著的特点,就是把业务流程逻辑从其它规则中分离出来,这 与早期业务流程深嵌在代码中的情况形成明显的对比。 二二二二、、、、业务流程管理系统业务流程管理系统业务流程管理系统业务流程管理系统 所谓业务流程管理系统(BPMS)提供实现一个或者多个核心 BPM 功能的技术。 业务流程管理系统的名称可能是多种多样的,比如工作流、过程自动化、过程集成、B2B 、 服务合成、编制和编排等,它提供了一种流程建模工具,允许用图来定义流程。下图显示了 BPMS 的基本组件。 三三三三、、、、组合组合组合组合 BPM、、、、SOA 与与与与 Web 服务服务服务服务 1,,,,组合组合组合组合 BPM、、、、SOA 与与与与 Web 服务的原因服务的原因服务的原因服务的原因 通常,过程流由多个独立的任务组成,其中每个任务都是一个独立的 Web 服务。过程流会 根据一项任务的执行结果,来选择不同的分支继续执行,比如,如果“验证账户信息”是无效信 息的话,将转入“修正账户信息”过程。 在企业信息化的历史变迁中,大多数组织都存在不同历史时期的应用和技术共存的情况,由 于技术平台与数据模型的差异,要在这些硬功之间共享信息是很困难的,如下图所示。 今天的业务流程往往需要根据市场与用户特征发生变迁,但是,由于早期技术过程流嵌入到 具体技术中,修改业务流程是比较困难的。但是 SOA 架构要求通过抽象的契约来暴露任务,这 就有助于灵活的组合业务流程。 转向一种面向服务的架构和 Web 服务,需要加入一个服务层。服务层包含针对特定业务领 域的特定服务、适应于各个业务领域的数据模型以及 Web 服务平台,这样就可以实现一种与下 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 213 - 层应用技术无关的方式来定义和使用业务服务。再上一层是业务流程层,如下图所示。 BPM 作为一个层,有助于简化把解决不同问题的 Web 服务组合起来的任务,这种任务在过 去是非常困难的。如果把服务看成是 IT 系统与业务功能(比如处理订单)的对应,那么,BPM 层就可以看成把多个服务联合在一起完成一定功能的(比如验证订单)的过程流。通过有应用层 代码组成的过程流,业务流程将更易于针对新的应用特性与功能需求,作出变更和更新。 服务层和业务流程层提供了理想的平台,这是因为:  业务运营服务提供了粗粒度的业务功能(一个业务功能提供了对应于业务流程中的一个 任务)。  业务运营服务的服务契约,为访问服务提供了良好定义的、无歧义的接口,因此业务流 程无需了解下层应用技术平台的细节。  服务层的服务注册库与服务发现设施确保了业务流程层可以根据需要动态找到并访问 服务。  服务数据模型是根据服务业务领域定义的,而且是独立于特定应用数据模型的。  由于 XML 与下层应用所用的数据格式无关,任务在与其它任务交换数据或者调用服务 的时候,均采用 XML 作为规范格式。  服务层的安全模型提供了单点登录和基于角色的访问控制,这确保了任务可以获得使用 服务的权限,并且令业务流程层免于处理各种本来是下层技术平台提供的安全接口。  服务层管理模型可以生成有关服务使用状况的运行时统计数据,供业务流程层的 BAM 工具使用。 在过去,没有提供基于 SOA 的 Web 服务的服务层,因为业务流程需要直接访问下层业务应 用,所以 BPM 不但复杂,而且是脆弱的。 说它复杂,是因为业务流程必须通过各个应用定义接口(API、消息队列、数据库表)直接 访问应用,这需要使用者必须精通这些应用接口。这往往需要在业务流程中增加步骤,以弥补不 符合业务需要的应用接口定义。 说它脆弱,是因为流程直接与特定的应用接口紧耦合,这就可能为了一个升级版本,导致所 有访问它的流程出问题。紧耦合也为修改应用增加了困难。例如把一个已有的流程更换为别的厂 商提供的新应用,那么原来的流程必须经过修改才可以访问新的应用,这往往可能导致系统使用 上的困难。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 214 - 2,,,,定义原子与合成服务定义原子与合成服务定义原子与合成服务定义原子与合成服务 为了进一步了解基于 BPM、SOA 及 Web 服务解决方案在设计与实现上的细节,我们可以把 服务分为两种: 原子服务原子服务原子服务原子服务((((atomic service )))):不可在分解为更细粒度的服务。 合成服务合成服务合成服务合成服务((((composite service )))):由其它服务组合而成的服务。 建立 SOA 架构体系,需要有一种形式化的方法,使我们的设计过程规范、清晰、有条理, 而且每个阶段的关注点也比较集中。下面我们通过一个例子,讨论一个原有的传统系统,是如何 发展成新开发的业务运营服务,最终为内部和外部用户提供业务服务的细节。通过这个例子,说 明每个步骤需要考虑的问题以及方法上的重点。 初始系统初始系统初始系统初始系统: 假定,下图所示的初始系统中包括一个传统的人力资源管理系统(HR)、一个传统的财务系 统(CICS)、一个传统的企业安全系统(用于管理用户/角色权利信息,例如 LDAP)以及一个已 有的企业信息管理系统。 HR 系统是用 J2EE 实现的,它包含一个应用数据库和一个应用对象模型,它还提供一个 EJB 接口。财务系统运行于大型机上,它是针对大型机数据库 CICS 事务实现的,客户端可以通过 WebSphere MQ 访问它。 第一步第一步第一步第一步,,,,定义数据定义数据定义数据定义数据、、、、服务与过程模型服务与过程模型服务与过程模型服务与过程模型: 要用一种面向服务的架构(SOA)来提供可重用的服务,第一步就是定义业务领域的数据、 服务与过程模型,如下图所示。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 215 - 服务层数据模型服务层数据模型服务层数据模型服务层数据模型:定义将在服务间交换,以及将提供给服务请求者使用的业务层数据。 服务层数据模型包括数据定义(XML Schema )、数据验证规则(XML Schema 约束以及 XPath ),数据转换规则(XSLT)。在理想情况下,应该根据现有的行业架构来创建服务层数据模 型,但实际上,还需要参考现有的数据结构、对象模型以及数据库模式才能得出服务层数据模型。 因此,数据建模者必须对这些底层的数据定义进行仔细的抽象,才能创建出一个真正与应用和技 术无关的服务层数据模型。 服务模服务模服务模服务模型型型型:定义了业务运营服务的服务契约(例如 WSDL),该服务契约定义了:  服务的输入和输出参数(根据服务层数据模型定义的文档类型)。  服务的安全概要(例如权力、访问控制列表、保密及不可否认性等)。  服务质量(优先级、有保证的递送、事务特征及恢复语义等)。  服务水平协议(例如响应时间、可用率等)。 服务分为原子服务与合成服务两种。与服务层数据模型类似,在理想情况下,应该根据现有 的行业服务定义来创建服务模型,但实际上,可能要根据现有的组件接口(例如 COM/DCOM 类 型库、CORBA IDL 、Java 对象等)、传统消息格式以及现有应用的 API 才能得出比较现实的服务 模型。 过程模型过程模型过程模型过程模型:定义了后面要讨论的采用业务流程执行语言(WS-BPEL)的方案实现的业务流 程。各个过程定义包括:过程任务、任务间的控制流、任务间的数据流一基于过程相关的其它业 务规则,例如,过程或者任务的前提条件、过程或者任务的最后期限、给用户分配任务的规则、 进行路由警报的规则、进行自动调整问题的规则等。过程模型需要根据现有的过程定义,经过再 工程后的过程定义、过程交互的合作协议、行业过程定义等创建的。 在理想情况下,应该先创建服务层数据模型,然后在它的基础上进行服务模型的建立,最后 再根据服务模型来定义过程模型。不过很多情况下,这种定义是以迭代的方式进行的。 第二步第二步第二步第二步,,,,定义原子服务定义原子服务定义原子服务定义原子服务: 下图展示了如何根据数据模型来定义原子服务。在这个例子中,有三个服务是通过为传统系 统提供传统服务包装实现的。传统服务包装的作用是为传统系统提供了一个 Web 服务接口 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 216 - (SOAP 与 WSDL),由它负责接收输入的 SOAP 消息,并把它转换成传统系统能够理解的格式, 然后把请求传递给传统系统(例如调用 EJB 或者往 WebSphere MQ 队列中加一个消息)。在这个 例子中,有一个服务是通过实现一个新的 J2EE 组件,并把它发布为 Web 服务实现的。 上图还展示了 Web 服务平台,它提供了用于定义、注册、保护和管理原子服务的核心设施。 这个 Web 服务平台利用了已有的企业安全系统,包括支持 SAML、XKMS、XACML、WS-Security 等。还考虑了目录服务,这种服务类似于 LDAP 或者 ADS,其中包括用户/角色/权利等信息。 这个 Web 服务平台还利用了已有的支持 WSDM 的企业信息管理系统,WSDM 有例如 Hp Openview 或者 IBM Tivoli 系统,用于监控和管理服务。 第三步第三步第三步第三步,,,,定义合成服务定义合成服务定义合成服务定义合成服务: 下图展示了如何根据原子服务来定义合成服务服务的层次。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 217 - 合成服务指的是那些由其它服务组合而成的 Web 服务,合成服务与其它的 Web 服务类似, 因为它们都有 WSDL 契约,并且都是通过 SOAP 调用的。 我们可以通过直接编程的方式来创建合成服务,例如把一个 EJB 发布为用到其它 Web 服务 的 Web 服务。也可以通过使用 Web 服务编制(Web Service Orchestration )以及 WS-BPEL 来创 建合成服务,在这种情况下,开发者一般使用 Web 服务编制产品来定义合成服务,这种产品提 供了用于合成 Web 服务的图形用户界面,可以通过它生成相应的 WS-BPEL 流程定义,以及一 个执行 WS-BPEL 流程定义的运行时引擎。利用 WS-BPEL 进行 Web 服务的合成具有简单和灵活 的优点,而且修改合成服务不需要引入新的代码,不过需要对效率进行评价。 有时候,采用直接编程的方式也不失为一些好方法,特别是对于那些比较简单或者特别关注 效率的合成服务尤其如此。 Web 服务平台为支持创建合成服务提供了一下设施:  在设计时和运行时发现已有的服务;  注册新的合成服务;  安全的访问已有服务;  保护新的合成服务;  利用 WS-BPEL 编制已有服务;  在访问已有的服务时应用数据验证规则;  在收到已有服务发来的数据之后、或者把数据发送给已有服务之前,进行必要的数据转 换,这种转换包括数据的聚合、过滤与分割。 第四步第四步第四步第四步,,,,定义业务流程定义业务流程定义业务流程定义业务流程: 业务流程实现了复杂的、多步的业务功能,它通常涉及到多个参与者,包括内部用户、外部 用户与合作伙伴等。业务流程的运行时语义是由 WS-BPEL 来定义的,过程引擎负责执行过程任 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 218 - 务并确保根据 WS-BPEL 脚本实施有关的业务规则。 业务流程的各个任务,要不是由 Web 服务来完成,要不就是由用户来完成。如果任务是由 Web 服务来完成的,那么过程引擎要负责找到并调用相应的 Web 服务,如果任务是由用户来完 成的,那么引擎就负责把任务传递给一个被授权的用户。 业务流程还可以定义如何处理事务,发生错误的时候如何恢复语义(例如是否请求 ACID 事 务或者补偿事务)。业务流程本身也可以是一个 Web 服务,这样任何服务请求者(包括其它业务 流程)都可以启动它。 Web 服务平台为创建和执行业务流程而提供的设施包括:  在设计时与运行时发现已有的服务;  把新的业务流程注册为一个业务流程;  安全的访问已有服务;  保护新的业务流程;  用 WS-BPEL 编制已有的服务;  允许业务流程在访问已有服务的时候应用数据验证规则;  允许业务流程对任务间传递的数据执行数据转换(例如数据的聚合、过滤和分割);  生成关于服务使用状况的运行时统计数据,供业务流程层的 BAM 工具使用。 第五步第五步第五步第五步 研究服务消费者访问和使用研究服务消费者访问和使用研究服务消费者访问和使用研究服务消费者访问和使用 Web 服务的方法服务的方法服务的方法服务的方法 作为 SOA 架构的应用考虑,我们必须研究服务消费者访问和使用 Web 服务的方法,使内部 和外部用户可以方便的使用业务服务。消费者使用 Web 服务如下图所示。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 219 - 只要是被授权的服务请求者(包括 IT 系统、最终用户应用以及其它的 Web 服务),应该可 以随时随地访问任何服务,这些服务既包括原子服务,又包括合成服务,当然也包括作为 Web 服务发布的业务流程,而并不能仅仅规定用户只能使用最终组合成功的业务流程。 有时,组织对其外部的服务请求者(比如客户或者合作伙伴)之提供少数可供使用的服务, 而且对外部请求者必须比组织内部服务请求更严格的安全要求。 事实上,无论是对于内部还是外部请求者,组织必须发布业务流程和服务使用的案例,这些 简单又包括所有要素的案例,对于服务和业务的良好应用,都是非常必要的。 四四四四、、、、编制与编排规范编制与编排规范编制与编排规范编制与编排规范 Web 服务正逐渐成为系统架构和实现组织内外的业务流程与业务协作的基础,从上面的讨论 可以看出,实现的关键是需要一种规范、统一、功能强大的服务与业务流程的编排语言,以及与 之对应的产品。目前已经存在两种 Web 服务合成语言:  业务流程执行语言业务流程执行语言业务流程执行语言业务流程执行语言((((Business Process Execution Language ,,,,WS-BPEL)))):这是由 BEA、 IBM、Microsoft 以及 Siebel 制定的,而后被提交给了 OASIS WS-BPEL 技术委员会的标 准。  Web 服务编排语言服务编排语言服务编排语言服务编排语言((((Web Service Choreography Description Language ,,,,WS-CDL)))): 这是由 W3C Web 服务编排工作组制定的。该规范是基于一个由 Sun 、Intalio 、BEA、 SAP 等制定的规范制定的。 上述两种语言的目标都是:以一种面向过程的方式,把多个 Web 服务粘合起来。 1,,,, Web 服务编制与编排的比较服务编制与编排的比较服务编制与编排的比较服务编制与编排的比较 编制编制编制编制(orchestration )和编排编排编排编排(choreography )是用于描述合成 Web 服务的两种术语,虽然它 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 220 - 们有共同之处,但还是有区别的。 Web 服务编制服务编制服务编制服务编制(Web Service Orchestration ,WSO):指的是为业务流程进行的服务合成。它 主要用于重用已有服务的内部过程。 Web 服务编排服务编排服务编排服务编排(Web Service choreography ,WSC):指的是为业务协作(business collaborations ) 而进行的业务合成。它主要定义多个方面如何在一个更大的事务中,通过与交易伙伴及外部机构 (比如供应商与客户)交换信息,进行对等(peer-to-peer )协作。 两种合成方式的区别如下图所示。 WSO 关注于以一种说明性的方式、而不是编程的方式来创建合成服务。它定义了组成编制 的服务,以及这些服务的执行顺序(顺序、并发、条件分支)。因此,可以把编制视为一种简单 的流程,这种流程本身也是一个 Web 服务。WSO 流通常包括分支控制点、并行处理选择、用户 响应步骤以及各种预定义的步骤(例如转换、适配器、电子邮件及 Web 服务等)。 WSC 关注于定义多方如何在一个更大的业务事件中进行合作,它通过各方描述自己如何与 其它 Web 服务进行公共消息交换来定义业务交互,而不是像 WSO 那样描述一方是如何执行某个 具体业务流程的。 这种业务协作定义,需要涉及各方面指定的可观察到的消息行为,而不暴露内部实现细节, 这样做的好处有两个:  组织不愿意把内部业务流程与数据管理暴露给业务伙伴。  公开和私有的流程分离后,内部流程实现上的变化不会影响公开的业务协议。 业务流程执行语言((((WS-BPEL)关注于 WSO 和各个 Web 服务的合成,也可以用于驱动跨 企业边界的 WSC 形式的交互。而 Web 服务编排语言(WS-CDL)关注于 WSC 和企业之间关系 的定义。从另一个方面来说,CDL 不太适合于 WSO 和进行 Web 服务的合成。CDL 可以把 Web 服务端点作为企业之间协作的入口点,但它更关注于定义企业之间的关系。 CDL 定义了自己与 WS-BPEL 的关系,但是 WS-BPEL 并没有定义自己与 CDL 的关系,许 多 WS-BPEL 的支持者认为,这是一种支持各种交互的唯一需要的语言,但还是有很多人认为一 些补充的语言还是需要的,因为一种语言不可能处理所有的事情,尤其是多方协作的细节定义, 多种语言的并存和协调恐怕是未来 SOA 的一种特征。 2,,,,业务流程执行语言业务流程执行语言业务流程执行语言业务流程执行语言((((WS-BPEL)))) Web 服务业务流程执行语言(WS-BPEL)是一种面向过程的服务合成语言,它是面向服务 的,并且依赖于 WSDL。一个 WS-BPEL 过程可以发布为一个 WSDL 定义的服务,可以像其它 Web 服务一样被调用。 另外,WS-BPEL 希望一个 Web 服务合成所包含的全部外部 Web 服务,这都是用 WSDL 服 务契约定义的,这令 WS-BPEL 流程可以调用其它的 WS-BPEL 流程,甚至可以递归的调用自己, 这就具有很好的灵活性。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 221 - WS-BPEL 被称为是对 WSFL 和 XLANG 中优秀成分的组合。 IBM 的 WSFL(Web Service Flow Language ,Web 服务流语言)是一种图结构语言,它主要 依赖于控制链(control links )的概念。而微软的 XLANG(Web Service for Process Design ,用于 业务流程设计的 Web 服务)是一种块结构化语言,它的基本控制流结构有顺序(sequence )、选 择(switch )、循环(while )、并行(all )等。WS-BPEL 允许混合块结构与图结构的过程模型, 但这样一来也可能会引起到底使用什么方式的困惑,所以,WS-BPEL 的开发者如果缺乏经验, 很可能会制造出一种最差的组合。 WS-BPEL 定义了一组创建 Web 服务合成的基本任务基本任务基本任务基本任务:  Invoke 任务任务任务任务:允许业务流程在某一个 Web 服务提供的 portType 上调用单向的或者请求 /响应操作。  Receive 任务任务任务任务:允许业务流程停下来等待消息到来。  Reply 任务任务任务任务:允许业务流程对收到的消息发送一个回复消息。  Wait 任务任务任务任务:通知过程等待一段时间。  Assign 任务任务任务任务:把数据从一处复制到另一处。  Throw 任务任务任务任务:表明发生了某个错误。  Terminate 任务任务任务任务:中止整个编制实例。 另外还定义了一组结构化任务结构化任务结构化任务结构化任务,以便于把原子任务组合为更复杂的过程:  Sequence 任务任务任务任务:定义一个有序的任务序列。  Switch 任务任务任务任务:根据条件选择某个分支。  Pick 任务任务任务任务:停下并等待某一适当的消息到来,或者等到超时继续前进,只要多个触发 器中的一个发生,就执行相应的活动,任务便结束了。  While 任务任务任务任务:定义选环执行,直到满足某一条件的一组任务。  Flow 任务任务任务任务:表明一组应该并行执行的步骤,可以通过建立连接来定义一个特定过程的 执行序列。 由于 XML 描述的标准化,生成 WS-BPEL 就可以应用相应的图形界面产品方便的构造过程, 自动地完成 XML 文档的编制,而不需要自己写任何具体的 XML 语句。生成文档的目的也与 WSDL 一样,是希望借助于某些工具软件自动生成业务流程的映射层代理,所以,WS-BPEL 可 以作为 WSDL 的一部分存在。 尽管实际开发中并不需要直接书写 XML 文档,但是借助一个简单的、有代表性的例子,研 究清楚 WS-BPEL 的 XML 格式应用是有意义的,因为这样可以使我们对问题的理解更深入也更 直观。 下面的例子描述了如下图所示的过程,这个例子展示了 WSDL 如何与 WS-BPEL 关联,并 共同实现利用 Web 服务完成业务流程自动化的目标的。由于 WSDL 版本还在变化,在下面的讨 论中,我们将关注于构建逻辑,可以把描述看成一种伪码。 第一步第一步第一步第一步 定义消息定义消息定义消息定义消息((((message ):):):): ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 222 - 首先对客户信息记录进行定义,在过程流的开始步骤将会用到它。记录结构的定义,既可以 放在 WSDL 文档中,也可以放在相关联的文件中,然后在 WSDL 文档中通过 include 或者 import 元素来引用该文件,消息类型的定义,一般建议使用 XML Schema 。 上面定义的是一个消息类型,在具体的消息中需要与之建立关联。对于导入的数据类型与结 构,一般用命名空间(namespaces )来限定其元素名与属性名. 下面,为模式数据类型 CudtomerInfo 与 WSDL 消息类型 CustomerMessage 建立了关联。 要使用 CustomerInfo 数据类型,需要定义命名空间,比如: xmlns:openA="http://www.bank.com/xsd/AccountManagement" 第二步第二步第二步第二步 为消息定义接口类型为消息定义接口类型为消息定义接口类型为消息定义接口类型:::: 然后,需要为消息定义 portType (丛 WSDL 1.2 开始,portType 改名为 Interface ),它包括操 作名称以及输入/输出消息,例如: 绑定(bindings )属于 WSDL 的物理部分,这里省略了它们,因为采用何种通信协议,并不 影响到 WSDL 的逻辑部分或者 WS-BPEL 的定义。 第三步第三步第三步第三步 定义合作链接定义合作链接定义合作链接定义合作链接:::: 在 WSDL porTypes 的后面,需要添加称之为合作链接(partner links )的结构,它用于连接 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 223 - portType 与 WS-BPEL 流程定义,例如: 这些合作伙伴链接把 WSDL 操作()与 WS-BPEL 过程中的步骤()相关联,并且用于衔 接“服务以及接口的描述”和“使用这些服务的过程流的描述”。 第四步第四步第四步第四步 定义定义定义定义 WS-BPEL 过程过程过程过程:::: 在扩展的 WSDL 中,WS-BPEL 部分是通过一个过程名称(proccess name )来标识的,例如: 一个抽象流程是是不可直接执行的,如果要用 WS-BPEL 来编排而不是编制,就需要设定 abstractProcess="yes"。这部分定义所需要的命名空间就在这部分定义,给出过程名称以后,还要 引用该过程合作者链接(partner links ),以指出该过程所要使用的 WSDL 相应的合作者链接,例 如: 这里列出了过程流中的六个步骤所需的六个合作链接,包括: CollectAccountInfomation :收集账户信息; ValidateAccountInfomation :有效帐户信息; OpenAccount :新建帐户; SendConfirmation :发送确认信息; RepairAccountInfomation :修改帐户信息; DeclineAccountInfomation :降级帐户信息 在合作链接列表之后是变量名称的声明,也就是过程所需的 XML Schema 、XML Simple 以 及 WSDL 消息定义,例如: 如果过程需要错误处理的话,可以进行如下定义: 这个错误处理程序用于捕捉在验证客户数据的过程中产生的错误,比如错误的信用数据,或 者过去与银行发生的问题。 第五步第五步第五步第五步 定义过程流序列定义过程流序列定义过程流序列定义过程流序列:::: 最后,定义过程流序列,把操作放到执行关系中去,例如: ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 225 - 这个例子展示了过程流如何先通过收集客户信息功能(CollectAccountInfomation )接受输入 的客户数据,然后调用验证帐户信息功能(ValidateAccountInfomation )检查数据。根据验证结 果的不同,过程流可以进入批准阶段,也可以进入尝试清除错误阶段。WS-BPEL 为这种分支提 供了多种不同的条件动词,如 switch 、pick 、while 等。 WS-BPEL 关联集(correlation sets )标识在过程流中的多个 Web 服务间共享的数据,补偿处 理程序将执行有可能撤销先前结果的补偿程序(比如关闭前面未正确打开的账户等)。 在设计中需要注意的是,除了把 WS-BPEL 语法合并到 WSDL 所带来的麻烦以外,还必须 把 WS-BPEL 构词与其它技术(比如安全性、可靠性、事务性等)的过程流构词区分开来,以免 在一起使用时发生混淆,这样才能确保整个过程设计的正确工作,当然,这也有赖于 SOA 构词 描述的进一步标准化。 3,,,,以编制为中心的服务合成案例以编制为中心的服务合成案例以编制为中心的服务合成案例以编制为中心的服务合成案例 所谓以编制为中心(orchestration-centric )的服务合成,是因为它使用自上而下的方式定义 整个过程,其中位于顶层的是一个编制,编制中的各个任务(task ),要么是一个 Web 服务,要 么是一个被顶层编制调用的子编制(sub-orchestration )。在子编制中的各个任务,要么是一个 Web 服务,要么是一个被子编制调用的编制,以此类推。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 226 - 下面的例子显示了一个用于“新建账户”(OpenAccount )过程的 WS-BPEL 伪码,这个伪码 使用了 WS-BPEL 关键字,但没有采用 XML 格式,而只是为了显示 Web 服务编制的逻辑。 receive 'OpenAccountRequest' invoke CollectAccountInfo invoke ValidateAccountInfo assign AccountInfoInvalid=ValiDateAccountInfoResponse while AccountInfoInvalid=true invoke RepairAccountInfo pick onRepairAccountInfoCB invoke ValidateAccountInfo assign AccountInfoInvalid=AccountInfoResponse otherwise //timeout-assume AccountInfo can't be repaired invoke DeclineAccountApplication terminate end pick end while invoke OpenAccount invoke SendConfirmation 下图显示了一个 OpenAccount 过程是如何编制一系列 Web 服务请求执行的。 这里,OpenAccount 过程是作为一个 WS-BPEL 编制实现的,这个 WS-BPEL 编制:  在收到 OpenAccountRequest 时启动。  调用 OpenAccount 、CollectAccountInfomation 等 Web 服务来完成编制中的各个步骤。  为 OpenAccount 过程定义业务逻辑,包括控制逻辑(例如控制处理无效账户信息的循环) 和数据流。 虽然图中没有显示出循环机条件处理等构词,但实际上它们是存在的(它们是由编制控制点, 而不是在各个服务内部处理的)。 4,,,,Web 服务编排描述语言服务编排描述语言服务编排描述语言服务编排描述语言 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 227 - W3C Web 服务编排工作组(Web Service Choregraphy Working Group )正在制定 Web 服务编 排描述语言(WS-CDL)。WS-CDL 是作为 WS-BPEL 等补充 Web 服务技术的补充而提出的,它 定义了实现业务编排或者 B2B 场景所需的可执行过程。 常见的 B2B 场景,如 RosettaNet 的合作伙伴信息流程(Partner Information Process ,PIP)涉 及到一个贸易方向另一个或者多个贸易方提交 XML 文档(例如订单)供其执行。WS-BPEL 可 以定义执行流、执行流中的消息及其它活动(比如错误处理程序)。WS-BPEL 的抽象流程是用于 B2B 交互的,而 WS-CDL 提供了更多的能力,比如不同的业务流程引擎之间彼此如何对话,比 方说,一方是 WS-BPEL 引擎,另一方是 RosettaNet 引擎,反之亦然。 WS-CDL 是从“全局的”观点为消息交换定义了公共的排序条件与约束,各个参与者可以 利用这个“全局的”定义来构建并测试解决方案。WS-CDL 不是一种可执行的语言,而是一种 定义交互模式的说明性语言,参与各方都可以把这个交互模式作为协定。 WS-CDL 文档是一组可以被各方引用的、具名的定义集和,它的根元素是 package 元素,其 中包括一个或者多个协作类型定义,package 构词的语法如下: importDefinitions* infomationType* token* tokenLocator* role* relationship* participant* channelType* choreography-Notation* Package 模型定义了协作的参与者、以及各个参与者的角色和相互关系,一旦达成协定, package 就跨越个协作机构的信任边界进行交换,以共享明确的定义。 可以在 package 构词中汇集一些编排定义,其中的 infomationType 、token 、tokenLocator 、role 、 relationship 、participant 、channelType 等元素被重用于当前 package 中定义的所有编排。 4,,,,以编排为中心的服务合成案例以编排为中心的服务合成案例以编排为中心的服务合成案例以编排为中心的服务合成案例 以编排为中心(choreography-centric )指的是通过一系列编排,把所有顶层业务流程中的任 务连接起来,而没有某个处于控制地位的流程执行引擎。在这里,交互是对等的,而不是像以编 制为中心的方法那样用命令来控制。 下图展示了以编排为中心的 OpenAccount 流程 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 228 - 例如,下面这些任务对之间的交互是作为编排定义的: CollectAccountInfomation 与 ValidateAccountInfomation ValidateAccountInfomation 与 RepairAccountInfomation RepairAccountInfomation 与 ValidateAccountInfomation OpenAccount 与 SendConfirmation RepairAccountInfomation 与 DeclineAccountInfomation 从某种意义上说,业务流程的编排描述体现了 Web 服务的大粒度要求,对于某些大粒度、 分布式的业务重用还是很有意义的。 6.4 SOA 的业务效益与构建的业务效益与构建的业务效益与构建的业务效益与构建 一一一一、、、、SOA 的业务效益的业务效益的业务效益的业务效益 SOA 所描述特征的服务,将具有如下业务效益。 1,,,,增强业务的机动性增强业务的机动性增强业务的机动性增强业务的机动性 增强业务的机动性,是到目前为止 SOA 最重要的业务效益。目前对许多机构而言,对新业 务需求与快速响应的业务机动性,是比开发效率还要重要的。业务机动性两个关键要素是速率 (velocity )和灵活性(flexibility )。 速率(velocity ):指的是沿着既定的路线快速前进,更快的产品或者服务的上市速度。SOA 显著降低了利用现有服务和 IT 资产组装新业务应用所需的时间,因而提高了速率。 灵活性(flexibility ):根据需要适应 IT 系统的能力。由于不断变化是业务和软件所必须面对 的现实,而且也是开销的主要源头,因此,在 IT 可以迅速修改现有系统的情况下,业务可以快 速适应新的机遇与竞争威胁。 2,,,,更好的配合业务更好的配合业务更好的配合业务更好的配合业务 当所有的业务都为共同的目标和结果提供支持的话,我们就称之为配合(alignment )。我们 可以而且应该把 IT 系统通过 SOA 提供的服务定义为直接支持组织向顾客、客户、公民与合作伙 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 229 - 伴等提供的服务。 用面向服务的架构做到业务与 IT 的相互配合,可以改善业务的设计与开发,这是通过业务 用户与 IT 技术的要求沟通更加流畅做到的,这也有助于把交流提升到业务层面。 3,,,,改善客户满意度改善客户满意度改善客户满意度改善客户满意度 许多机构都致力于建立一种在不同的服务渠道(面对面、Web 自助服务、移动用户、呼叫中 心、ATM 等)一致的用户体验,如果客户从不同的渠道获得自相矛盾的信息,客户满意度就会 下降。 以客户为中心的 SOA 致力于确保一致的用户体验,通过创建与任何具体技术和最终设备无 关的服务来实现,将更加容易重用于各种服务渠道。 4,,,,降低对厂商的依赖和降低转换成本降低对厂商的依赖和降低转换成本降低对厂商的依赖和降低转换成本降低对厂商的依赖和降低转换成本 传统的 IT 系统中,对厂商技术的依赖发生于各个层面上:  应用平台(如 J2EE 、.NET 框架、Oracle 、 CICS );  套装应用软件(如 SAP、PeopleSoft 等);  中间件技术(如 WebSphere MQ );  特定产品功能(如存储过程、群集缓存)。 我们应该注意到,如果中断与套装应用软件、开发平台、中间件系统的长期关系,是需要付 出很大代价的。 SOA 为机构提供了发展空间以适应未来的发展,并显著降低了对厂商技术的依赖。因为以 SOA 为中心的机构是基于服务契约来构建下层 IT 架构的,该服务契约与业务服务层是一致的, 并且技术中立、与应用无关和不了解中间件的。这种层次结构更容易替换应用程序、技术和中间 件。 5,,,,降低集成成本降低集成成本降低集成成本降低集成成本 SOA 能显著降低集成成本,其原因已经在前面讨论过。 在采用不同的套装应用程序和应用程序的异构环境中,这种成本的降低尤其显著,因为 SOA 提供了一种统一的、一致的技术基础设施,不必为定制集成编写代码,也不用部署和配置许多特 定用途的应用程序适配器。 6,,,,提高现有的提高现有的提高现有的提高现有的 IT 资产投资回报率资产投资回报率资产投资回报率资产投资回报率 面向服务的架构能显著提高现有 IT 资产的投资回报率,因为该架构的 IT 资产被重用为服务, 确定现有系统的关键业务能力,然后把它们作为构建新服务的基础,这样,SOA 有助于最大化 现有 IT 投入的价值,并降低风险。 但是要注意到不是所有的 IT 资产都能够被重用,所以需要一个评估和筛选的过程,这个过 程必须特别注意抽象接口的定义,这样的接口应该能够既体现业务功能的本质,又封装了技术细 节。 二二二二、、、、如何达成如何达成如何达成如何达成 SOA 1,,,,SOA 业务架构的达成业务架构的达成业务架构的达成业务架构的达成 构建一个合理的 SOA 应采用何种开发方法?从前面的部分可以看出,有业务流程、应用程 序和服务。显然,对服务建模是此类方法必须支持的主要任务。另一个重要的方面是确保业务流 程和服务之间的链接。 我们必须搞清楚现有的模型(例如面向对象的分析和设计、企业架构框架和业务流程建模技 术)对 SOA 设计的作用。我们需要将其它方法元素用于 SOA ,例如用于服务标识和聚合的方 法和技术、业务跟踪能力、现有资产的集成和重用。我们还需要进行服务的建模,它是在域分解、 现有系统分析和目标服务建模之类的技术支持下实现的。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 230 - 事实上 SOA 并不仅仅是一个 IT 概念,而是偏重于组织、管理以及商业模式。部署 SOA 不 仅牵涉到 IT 系统的构建模式,同时也涉及到业务流程架构和业务的管理运作模式,因为 SOA 必 须对业务的改变作出迅速反应。达成 SOA 业务需要经过以下几个阶段: 1))))行业业务模型和标准的跟踪行业业务模型和标准的跟踪行业业务模型和标准的跟踪行业业务模型和标准的跟踪、、、、学习学习学习学习:要学习行业业务模型和标准,从做咨询顾问开始我 们的 IT 项目。 2))))融会贯通业务服务化过程融会贯通业务服务化过程融会贯通业务服务化过程融会贯通业务服务化过程:与行业业务专家一起,从划分不同的业务功能域开始,界定 项目范围中都包含那些功能域,每个功能域都有哪些过程组成,注意梳理和分解,整理出可重用 的业务服务,要注意只有重用才提炼出业务服务、进行业务化处理。 3))))完成完成完成完成 SOA 项目是个渐进的过程项目是个渐进的过程项目是个渐进的过程项目是个渐进的过程:在进行整体规划的时候,千万不要与技术层面的 Web 服务、对象、组件混淆起来,和业务人员交流只考虑业务如何组织、流程整合哪些部门和合作伙 伴,业务在哪些方面会重用等问题,此时一定要屏蔽实现细节,专注于粗粒度、松耦合的业务。 SOA 项目是个渐进的过程,并不需要一蹴而就,这就需要对业务的演进有比较深刻的理解。 4))))自上而下形成业务服务的制品自上而下形成业务服务的制品自上而下形成业务服务的制品自上而下形成业务服务的制品:业务服务化的过程是自上而下的,从功能域、业务流程 到业务服务。一般来说,业务服务是稳定的、不易变化的、可重用的业务逻辑部分,是行业业务 模式的共性所在。而业务流程是善变的、随着业务发展而不断变化的,它是企业真正的核心竞争 力的所在。企业业务服务化的过程,就是把变化的部分与稳定的部分区别开来,过程控制变化的 部分,业务服务概括稳定的业务模型部分。 2,,,,SOA 需要解决的问题需要解决的问题需要解决的问题需要解决的问题 作为一个具有发展前景的应用系统架构,SOA 尚处在不断发展中,肯定存在许多有待改进 的地方。随着标准和实施技术的不断完善,这些问题将迎刃而解,SOA 应用将更加广泛。一般 认为,SOA 还是有一定的缺憾的: 1))))可靠性可靠性可靠性可靠性(Reliability )))) SOA 还没有完全为事务的最高可靠性包括不可否认性(nonrepudiation) 、消息一定会被传送且 仅传送一次(once-and-only-once delivery )以及事务撤回(rollback )等做好准备,不过等标准和 实施技术成熟到可以满足这一需求的程度并不遥远。 2))))安全性安全性安全性安全性((((Security )))) 在过去,访问控制只需要登录和验证;而在 SOA 环境中,由于一个应用软件的组件很容易 去与属于不同域的其他组件进行对话,所以确保迥然不同又相互连接的系统之间的安全性就复杂 得多了。 3))))编排编排编排编排 (Orchestration) 统一协调分布式软件组件以便构建有意义的业务流程是最复杂的,但它同时也最适合面向服 务类型的集成,原因很显然,建立在 SOA 上面的应用软件被设计成可以按需要拆散、重新组装 的服务。作为目前业务流程管理(BPM)解决方案的核心,编排功能使 IT 管理人员能够通过已 经部署的套装或自己开发的应用软件的功能,把新的元应用软件(meta-application )连接起来。 事实上,最大的难题不是建立模块化的应用软件,而是改变这些系统表示所处理数据的方法。 4))))遗留系统处理遗留系统处理遗留系统处理遗留系统处理((((Legacy support )))) SOA 中提供集成遗留系统的适配器, 遗留应用适配器屏蔽了许多专用性 API 的复杂性和晦 涩性。一个设计良好的适配器的作用好比是一个设计良好的 SOA 服务:它提供了一个抽象层, 把应用基础设施的其余部分与各种棘手问题隔离开来。一些厂商就专门把遗留应用软件“语义集 成”到基于 XML 的集成构架中。 但是集成遗留系统的工作始终是一种挑战。 5))))语义语义语义语义((((Semantics )))) 定义事务和数据的业务含义,一直是 IT 管理人员面临的最棘手的问题。语义关系是设计良 好 SOA 架构的核心要素。 就目前而言,没有哪一项技术或软件产品能够真正解决语义问题。为 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 231 - 针对特定行业和功能的流程定义并实施功能和数据模型是一项繁重的任务,它最终必须由业务和 IT 管理人员共同承担。不过,预制组件和经过实践证明的咨询技能可以简化许多难题。 采用 XML 技术也许是一个不错的主意。许多公司越来越认识到制定本行业 XML 标准的重 要性。譬如,会计行业已提议用可扩展业务报告语言(XBRL)来描述及审查总账类型的记录。 重要的是学会如何以服务来表示基本的业务流程。改变开发方式需要文化变迁,相比之下, 解决技术难题只是一种智力操练。 6))))性能性能性能性能((((performance )))): 这是 SOA 的第六个缺憾吗?批评 SOA 的人士经常会提到性能是阻碍其采用的一个障碍,但 技术的标准化总需要在速度方面有一些牺牲。这种怀疑观点通常针对两个方面:SOA 的分布性 质和 Web 服务协议的开销。 不可否认,任何分布式系统的执行速度都不如独立式系统,这完全是因为网络的制约作用造 成的。当然,有些应用软件无法容忍网络引起的延迟,例如那些对实时性要求很高的应用软件。 所以在应用 SOA 架构之前,搞清楚它的适用范围就显得很重要了。 除了上述几点之外,我们认为还有几点也颇值得关注: 7))))松耦合和敏捷性要求之间的权衡难题松耦合和敏捷性要求之间的权衡难题松耦合和敏捷性要求之间的权衡难题松耦合和敏捷性要求之间的权衡难题: 服务松耦合设计其实是一把双刃剑,在带来应变敏捷性的同时,也给业务建模和服务划分带 来难题。这就是为什么在 SOA 讨论中,业务建模的争论总是最多的原因。 8))))跨系统集成难题跨系统集成难题跨系统集成难题跨系统集成难题: 面向服务的架构设计将跨越计算机系统,并且还可能跨越企业边界。我们不得不考虑在使用 Internet 时安全性功能和需求,以及如何链接伙伴的安全域。Internet 协议并不是为可靠性(有 保证的提交和提交的顺序)而设计的,但是我们需要确保消息被提交并被处理一次。当这不可能 时,请求者必须知道请求并没有被处理。 . 9))))SOA 与网格计算与网格计算与网格计算与网格计算((((Grid Computing ))))的关系的关系的关系的关系: 网格计算(Grid Computing )是利用互联网技术,把分散在不同地理位置的计算机组成一台 虚拟超级计算机。每一台参与的计算机就是其中的一个“节点”,所有的计算机就组成了一张节 点网——网格。从实质上来说“网格计算”是一种分布式应用,网格中的每一台计算机只是完成 工作的一个小部分,虽然单台计算机的运算能力有限,但成千上万台计算机组合起来的计算能力 就可以和超级计算机相比了。 网格计算基于因特网,提供了资源整合和共享的平台。十分适合作为 SOA 架构的实施平台。 我们来具体地看一下:  SOA 的构建策略的构建策略的构建策略的构建策略:创建一个面向服务的计算 SOC(service-based computing )环境;可 以用类似于 web services 的技术来设计服务:使用 SOAP 通信机制;采用 XML 数据格 式;强调服务的重用和互操作;最大化的应用现有资源;希望有一个类似于网格计算环 境的基础平台。  网格作为平台的基本特点网格作为平台的基本特点网格作为平台的基本特点网格作为平台的基本特点:网格被视为一个由各种计算资源组成的统一环境,其管理软 件将网格整合成一个完整而协调的透明计算整体;网格是一个虚拟的应用服务器;是一 个应用实现和数据处理的理想平台;服务在网格中部署和调用执行;商业逻辑和服务调 用被当成网格程序一样在平台上运行;网格为 SOC 计算的有效性、快速性、灵活性、 伸缩性和计算环境的管理提供便利。 3,,,,SOA 带给企业什么带给企业什么带给企业什么带给企业什么???? 作为需要构建 SOA 应用的企业来说,究竟有些什么好处呢?我们来看一下:  集成现有系统集成现有系统集成现有系统集成现有系统,,,,不必另起炉灶不必另起炉灶不必另起炉灶不必另起炉灶:面向服务的架构可以基于现有的系统投资来发展,而不 需要彻底重新创建系统。通过使用适当的 SOA 框架并使其用于整个企业,可以将业务 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 232 - 服务构造成现有组件的集合。使用这种新的服务只需要知道它的接口和名称。服务的内 部细节以及在组成服务的组件之间传送的数据的复杂性都对外界隐藏了。这种组件的匿 名性使组织能够利用现有的投资,从而可以通过合并构建在不同的机器上、运行在不同 的操作系统中、用不同的编程语言开发的组件来创建服务。遗留系统可以通过 Web 服 务接口来封装和访问。  服务设计松耦合服务设计松耦合服务设计松耦合服务设计松耦合,,,,带来多方面优点带来多方面优点带来多方面优点带来多方面优点:服务是位置透明的,服务不必与特定的系统和特定 的网络相连接。服务是协议独立的,服务间的通信框架使得服务重用成为可能。对于业 务需求变化,SOA 能够方便组合松耦合的服务,以提供更为优质和快速的响应,允许 服务使用者自动发现和连接可用的服务。松耦合系统架构使得服务更容易被应用所集 成,或组成其他服务,同时提供了良好的应用开发、运行时服务部属和服务管理能力。 提供对服务使用者的验证(authenticatio )、授权(authorization ),来加强安全性保障, 这一点也优于其他紧耦合架构。  统一了业务架构统一了业务架构统一了业务架构统一了业务架构,,,,可扩展性增强可扩展性增强可扩展性增强可扩展性增强:在所有不同的企业应用程序之间,基础架构的开发和 部署将变得更加一致。现有的组件、新开发的组件和从厂商购买的组件可以合并在一个 定义良好的 SOA 框架内。这样的组件集合将被作为服务部署在现有的基础构架中,从 而使得可以更多地将基础架构作为一种商品化元素来加以考虑,增强了可扩展性。又由 于面向服务的敏捷设计,在应对业务变更时,有了更强的“容变性”。  加快了开发速度加快了开发速度加快了开发速度加快了开发速度,,,,减少了开发成本减少了开发成本减少了开发成本减少了开发成本:组织的 Web 服务库将成为采用 SOA 框架的组织 的核心资产。使用这些 Web 服务库来构建和部署服务将显著地加快产品的上市速度, 因为对现有服务和组件的新的创造性重用缩短了设计、开发、测试和部署产品的时间。 SOA 减少了开发成本,提高了开发人员的工作效率。 市场和制度的不断变化,要求组织具有相当的灵活性,软件架构师提供一种通用的、基于服务的 解决方案,将有助于实现组织的灵活性,也更容易实现生产力的提高。尤其在为业务流程管理 (BPM)奠定了 SOA 架构基础以后,企业就可以把思想集中于更高层次的问题,比如设计更好 的业务流程,而不是考虑实现业务流程的技术细节。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 233 - 第七章 软件架构设计的其它有关问题 项目成功的关键要素是合理的项目规划,而好的项目规划与软件架构设计具有项目成功的关键要素是合理的项目规划,而好的项目规划与软件架构设计具有项目成功的关键要素是合理的项目规划,而好的项目规划与软件架构设计具有项目成功的关键要素是合理的项目规划,而好的项目规划与软件架构设计具有 很强的相关性。很强的相关性。很强的相关性。很强的相关性。 7.17.17.17.1 软件架构挖掘软件架构挖掘软件架构挖掘软件架构挖掘 仔细研究软件架构设计的方法,我们可以发现,软件架构设计是一个知识积累的过程,为了 有效的增加知识积累的能力,我们可以利用一个附加实践,那就是所谓架构挖掘。 一一一一、、、、架构挖掘过程架构挖掘过程架构挖掘过程架构挖掘过程 1111,,,,自顶向下和自底向上自顶向下和自底向上自顶向下和自底向上自顶向下和自底向上 自顶向下的设计方法,强调的是把设计视图或者需求文档这样一些抽象的概念,进一步转化 为具体的设计和实现。这事实上是一种预先方法,在实现之前必须产生设计计划。 而在自底向上的设计方法,一个新的设计是从基本的程序或有关部分开始创建的,在递增变 化以设计复用方面,往往更具备生产效率。自底向上方法实际上是一种事后方法,文档往往是根 据已经建立的结构完成的。 一般来说我们推荐自顶向下方法,它体现了一个系统有计划的开发,这样更便于构建有效的 系统。但是,不论愿不愿意,系统当初的设计是不可能一成不变的,这是一个螺旋上升的迭代过 程,在这个过程中,架构师会对应用问题有更深入的理解,不断创建许多新的设计。 自底向上的方法可以认为是这个主体过程的一个补充,事实上大多数信息系统都存在预先设 计,有些设计存在于已经完成的系统中,利用这个信息,架构师就可以在前期建立有效的原型, 对这些信息进行抽取,把抽取的结果应用于软件架构的过程,称之为架构挖掘架构挖掘架构挖掘架构挖掘。 架构挖掘是利用现存设计与实践经验来创建新的架构,通过回顾大量的实现细节,发现、抽 取、提炼设计知识。 2222,,,,架构挖掘过程架构挖掘过程架构挖掘过程架构挖掘过程 架构挖掘开始之前,首先需要识别一组与设计问题相关的代表性技术,这些技术的搜寻可以 用各种方法进行(拜访专家、技术研讨会、网上搜索等),也可以由针对性地进行技术预研。 第一步:对典型技术建模,产生相关的软件接口规范。 第二步:已经挖掘的设计被一般化用于创建一个共同的接口规范。 在这一步,我们不是要得到一个最简约的典型设计,而是应该有一个更加良好的解决方案。 第三步:提炼设计,提炼的驱动力:架构师的评定、非正式走查、回顾过程、新的需求、其 它挖掘研究等。 二二二二、、、、架构架构架构架构挖掘的方法学问题挖掘的方法学问题挖掘的方法学问题挖掘的方法学问题 1111,,,,挖掘的适用性挖掘的适用性挖掘的适用性挖掘的适用性 挖掘的目的事实上并不是具体产品,事实上挖掘的真正目标是架构师的启迪。这对于降低风 险和提高架构的质量是有明显好处的。如果对问题和先前的解决方案能有成熟的理解,架构师就 可以着手准备架构设计了。 挖掘的另一个结果是接口模型,尽管这不是正规的产品,但可以应用于架构的创造性设计的 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 234 - 方法中。挖掘工作对于高风险的或者有着广泛影响的重大设计,是很有意义的,它可以提高设计 的质量和复用性。一般来说,一个典型技术的挖掘研究几天就可以完成,经过几个挖掘研究之后, 就可以满怀信心地开始设计了。 架构师的知识主要来自于多年的实践积累,如果我们不注意把这种积累充分挖掘出来,总是 针对每个新项目重新开始,往往产生一些不成熟的、习惯性的设计,这就增加了设计的风险。 2222,,,,水平与垂直设计元素水平与垂直设计元素水平与垂直设计元素水平与垂直设计元素 在设计模式的讨论中,我们知道建立一个在未来可以适应变化的系统从技术上是可行的,这 种设计被称之为水平设计元素,它的特点是重点考虑软件复用。而针对一个唯一的需求实现硬编 码,被称之为垂直设计元素,它的特点是只考虑具体实现。 架构设计的一个重要目标,就是在水平与垂直设计元素的考虑上寻求平衡,这是一个往往让 人感到迷惑的问题。作为程序员一般比较偏好垂直设计,因为这可以使用一种直接的方式编码, 他们的思维是:既然这样就能解决问题,为什么还要采用其它的方式呢? 但作为构架师,就需要关注具有长期影响的其它设计要点。经验告诉我们,需求的变化是频 繁的,而我们也知道了一些设计方法可以灵活的适应这些变化。这样,我们就可以采用某种能够 合理管理变化的方法,方法是: 1)列出可能的变化源以及它们对设计的影响,这称之为“架构评估”。 2)研究哪些局部变化会会导致全局问题。 3)做出细粒度决策来适应这种变化,这些决策很多来自于直觉。 4)通过实践来平衡对灵活性的要求。 现在我们已经知道,我们可以很容易得把架构设计的很灵活,但合理的架构是基于共识和平 衡设计的。过于灵活的设计存在着一些潜在的不良后果: 1)效率低 高度灵活的设计在一个接口两边都需要额外的运行处理,特别是使用了反射这样的技术尤其 如此,动态装入、动态参数很容易在接口操作上出现两个数量级以上的延迟。使用分布式体系或 者 SOA 架构都可能造成低效率的后果。如果架构强调了质量标准,这种低效率的不可容忍可能会 迫使你放弃灵活性。 2)可理解性差 如果架构太灵活,往往使开发人员难以理解你的架构,结果造成开发的困境。最坏的情况是 开发人员自己做了某些假定,是最后的结果和你的设想大相庭径。所以架构设计中的灵活性应该 在开发人员能够理解的前提下实现,而且需要和开发人员加强沟通。 3)冗余编码 灵活性设计必然导致冗余编码,这样的冗余带来的好处是在变化的过程中不需要修改架构。 但是代价是编码量确实提高了,这就提高了开发成本,这是需要做出某种平衡的。 4)过多的文档化约定 灵活性设计往往需要配置文件,或者是用文档来规定约束,这种应用上的复杂性如果不加以 控制,这就会使软件集成的难度增加。在硬编码和使用约定之间的权衡,需要一个准确的设计平 衡点。 3333,,,,水平设计元素水平设计元素水平设计元素水平设计元素 尽管垂直设计非常受程序人员欢迎而且高效方便,架构师还是应该把眼光落在水平设计元素 上。在需求工程完成以后,架构的设计基本上是属于垂直方法,也就是根据需求来构架体系,接 着,合理的消除垂直设计元素成为一个架构师能力的体现,关键是合理。 需求经过领域分析以后,软件设计就开始了。领域分析可以在一个给定的问题域里面,帮助 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 235 - 架构师确定水平元素和垂直元素。就设计方法而言,由分析人员那里得到的知识和经验尤为重要。 一个好的水平设计通常能满足多个应用要求,不过过分的通用性一般也是很难达到的,这就 需要一个平衡。事实上一般化的问题往往比特定问题更容易解决,这是因为特定问题往往把人的 思维局限于具体细节中,而一般问题可以从具体的细节中解脱出来,但是复用仍然是对一个架构 师水平的考验。 三三三三、、、、职责驱动的开发职责驱动的开发职责驱动的开发职责驱动的开发 在企业应用架构设计过程中,我们要通过特殊的形式的讨论职责驱动的设计和开发。职责驱 动的开发是指根据子系统或者构件所应该具有的功能上的职责,对其进行分析和设计。 在一个系统中,子系统或者构件的职责集相互正交。如果新设计的子系统要承担的职责已经 存在了,就可以把这个职责委派给已经拥有这个职责的子系统、构件或者实例。这种技术以非常 小的委派的代价,最大限度地增加了复用。 这个过程的结果之一,就是为子系统创建软件规范。它以独特的格式区别了接口文档和实现 文档。因为接口要被其它子系统所使用,所以相较于封装在子系统中的类而言,要求更加不容易 变动,这就是一个十分重要的设计原则,接口要保持稳定,高层架构设计的时候,也需要下很大 的工夫规范接口。 在软件开发的时候,如果开发人员不能时常碰头,或者极端的某个子系统是由外包的形式开 发的,设计文档就更应该类似于设计规范,它比一般的描述方式要求更加严密。设计规范应该更 清楚地将子系统、构件间的接口,与详细描述构成系统的子系统内部实现部分加以区分,其目的 是尽可能的减少设计的二义性。 尽管如果类设计中方法、参数及数据结构足够详细,本来是不需要设计规范的。但实际上设 计是在不断改进,利用软件规范明确标定接口,就可以防止设计改动的时候带来问题。 一旦拥有了设计规范,架构时就可以和开发团队交流思想,通过“介绍”迅速沟通,必要的 时候也可以单独指导。在这样的“介绍”中所取得的反馈,也可以用来改进自己的架构,甚至可 以委托开发小组进行小粒度设计和实验,当然这些设计和实验必须符合设计规范,而且是在构架 师指导下进行的。 四四四四、、、、架构的可追踪性架构的可追踪性架构的可追踪性架构的可追踪性 一般来说,垂直设计的可追踪性是比较好的,因为它的实现细节与外部需求是紧密联系在一 起的。但是当一个架构被设计成水平结构的时候,可追踪性就变得不明显了。解决的办法是通过 内部场景显示内部设计是如何支持外部需求的,每个场景都对应于一个重要的外部功能的执行。 典型情况下一个水平设计元素与多个外部场景有关,而不仅仅描述单个需求。 7.2 进行进行进行进行多多多多维度维度维度维度小组的项目小组的项目小组的项目小组的项目规划规划规划规划 敏捷小组一般不会超过 7~10 人,这种规模的小组可以完成很多事情。但某些项目希望用更 大的小组来承担项目的时候,就需要建立更多的比较小的小组,在第九章已经讨论了类似的问题, 给出了一些基本概念,我们也提到了在这种情况下,上层实行结构化管理某种意义上说是合理的。 下面我们针对这种类型的组织敏捷项目规划的有关问题,做更深入的研究。 规划一个大型的、多小组的项目,可能需要下面的方法:  为估计建立共同的基准。  更早给用户描述添加细节。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 236 -  进行前瞻规划。  在计划中加入馈送缓冲区 根据项目中有多少子小组,以及协调的频繁程度和紧张程度不同,项目可能需要上面这些方 法的一部分或者全部,一般还是建议采用上面的顺序进行规划。下面我们详细介绍这些方法。 一一一一、、、、为估计建立共同基准为估计建立共同基准为估计建立共同基准为估计建立共同基准 在项目开始的时候,所有小组应该在一起他们的估计值建立共同的基础,这样一个小组给出 的估计值就会和其它小组同样的工作给出的估计值差不多。每个用户描述只要被一个小组估计, 那无论是哪个小组来估计,给出的值应该是基本一样的。建立基准有两种方法: 第一种方法,如果小组在过去某个项目一起工作过,他们可以从过去的项目中选择一些用户 描述,然后对它进行估计达成一致。他们可以确定 20 来个旧描述,根据现在对这些描述的了解, 就没个描述的新估计达成一致。一旦在这些基准描述上达成了一致,各小组就可以把他们各自的 用户描述与这些基准描述进行比较来估计他们,这是一种共同基准的类比方法。 第二种方法,使小组在一起估计一些新的用户描述。要选择多种新发布计划的用户描述,这 些描述应该覆盖不同的规模,而且选择大多数估计者都与之有关的领域,整个组或者小组代表在 一起就这些描述的估计达成一致,然后就可以与第一种方法一样成为基准了。 即使各个小组相互独立,人员也不会流动,我还是建议使用共同基准,因为这有利于进行项 目有关的交流。 二二二二、、、、尽早给用户描述添加细节尽早给用户描述添加细节尽早给用户描述添加细节尽早给用户描述添加细节 理论上,敏捷开发小组在开始一次迭代的时候,只有模糊定义的需求,他们在迭代结束之前 把这些模糊定义的需求转化为可以工作的意境测试的软件。但是,多小组开发的项目中,在迭代 开始之前就对用户描述进行更多的思考常常是适当的也是必要的做法,这些细节可以让不同的小 组更容易协调他们的工作。 这种对用户描述更详细的思考,一般是把用户描述(user story )转化为用例(user case )。在 建立用例的时候,需要写出简短的用例场景(事件流)。显然,要达到这个目的,更大的组就需 要专职的分析员、架构师、用户交互设计师和其他人员,他们在特定的迭代中花一部分时间来准 备下一次迭代的工作。一般来说,我不推荐让这些人提前准备整整一次迭代的全部开始工作。他 们的主要责任还在当前这次迭代的工作,但也应该包含准备下一次迭代的有关任务。 我发现这些工作最有用的成果,是认定了产品所有者对很可能在下一次迭代中完成的用户描 述的满意条件,产品所有者通过用例场景,了解了对这个描述进行高层次可接受性测试的细节, 从而明确了满意条件。同时,各个小组对协调方的信息了解的更加详尽,有助于协调一致的工作。 这种处理方式虽然有益,但小组不可能、也不需要在一次迭代以前认定所有用户描述的满意 条件,详细的用例场景主要用于对相互协调有关系的那些描述。因为一次迭代到底处理哪些用户 描述,在迭代规划会议之前是不可能精确知道的。不过,产品所有者和开发小组可以合理猜测哪 些描述最可能在下一次迭代中优先处理,那些描述对小组间的协调最有意义。如果这样的猜测都 无法作出,那这样多小组协调的敏捷开发最后陷于混乱,也就是自然而然的事情了。 还要说明,不论是不是多小组开发,在一次迭代开始的时候,首先要做的就是把用户描述转 换为用例场景。这里只不过强调的是有些重要的场景要在迭代规划会议之前进行,这样才有利于 协调。 三三三三、、、、进行进行进行进行前瞻规划前瞻规划前瞻规划前瞻规划 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 237 - 在发布规划和迭代规划中维持一个滚动的前瞻窗口,可以让大多数具有中等复杂度或者中等 频繁度的相互依赖的小组从中受益,下面我们通过一个案例说明这个方法。 案例背景案例背景案例背景案例背景: 假定两个开发小组处理运动员网站 SwimStats ,SwimStats 的一部分显示诸如训练成绩、游泳 池的地址和方位等静态信息。不过 SwimStats 也必须提供数据库驱动的动态信息,包括过去 15 年所有比赛的成绩和所有运动员所在项目上的个人纪录等。 国家和年龄组纪录存放在国家游泳联合会远程设备上的数据库中。访问这个数据库不想小组 当初所希望的那么简单。国家游泳联合会准备在未来 2 年中改换数据库供应商。正是由于这个原 因,产品所有者和开发小组都同意要开发一个 API 来访问这个数据库。这样可以让将来改变数 据库供应商的工作更容易。 处理办法处理办法处理办法处理办法:::: 首先,我们列出了初始的用户描述和对它的估计值,如下表所示。 用户描述用户描述用户描述用户描述 描述点描述点描述点描述点 作为 SwimStats ,我们希望能够容易的改变数据库供应商 30 作为站点的任意访问者,我在被允许访问敏感内容前,需要进行身份验证 20 作为运动员,我想查看训练活动安排在什么时候 10 作为运动员或者家长,我想知道举行联赛的游泳池在什么地方 10 作为任意访问者,我想按照年龄组或者项目查看国家纪录 10 作为任意访问者,我希望能查看任何比赛的结果 10 作为任意访问者,我希望能看到任何运动员的个人纪录 20 估计的速度是每个小组每次迭代 20 点。这里设定了 2 个小组共同完成这项工作,由于有 110 点的工作,这意味着能够在 3 次迭代中交付所有功能。 但是,开发 API 有 30 点,表中最后 3 个描述需要这个 API 支持,这 3 个描述总共 40 点。 必须在 API 完成之后进行。所以,在迭代开始前的规划中,把两个小组的工作安排如下图。 我们曾经建议发布计划只显示最近两次迭代的细节,因为这样做就足以为很多小组遇到的相 互依赖提供支持,当然先是迭代的确切次数,取决于小组之间依赖的频度和显著程度。在完成迭 代以后,就从发布计划中去掉有关它的细节,然后再前瞻性的安排随后的 2~3 次迭代的期望。 由于这总是描绘对新的几次迭代的期望,所以称之为滚动前瞻规划(rolling lookahead plan ),又 称之为向前窥视。 上图还显示规划了一次小组交接,这比迭代期间任意的提供交接更安全。在第 3 次迭代的时 候,第 2 小组知道 API 已经完成,他就可以做出有意义的承诺。 四四四四、、、、在计划中加入馈送缓冲区在计划中加入馈送缓冲区在计划中加入馈送缓冲区在计划中加入馈送缓冲区 但是上面的例子还有一个问题,也就是在第二次迭代的中间必须交付 API。当另一组需要上 午 10 点必须使用您的产品的时候,您不可能 9 点 58 分匆匆地把这件工作做完。例如需要书写交 接文档、向使用方作详细说明、甚至还要听使用方的一些意见与改进。所以,在工作量的安排上, 应该比仅仅开发要多一些,这就需要加上馈送缓冲区。我们需要改变发布规划如下图所示。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 238 - 1,,,,缓冲的对象缓冲的对象缓冲的对象缓冲的对象 这个例子增加馈送缓冲区并不会增加项目时间,因为时间本来就不满。但是在大多数情况下, 增加馈送缓冲区会延长项目时间。这是可以理解的,在多小组并行开发的情况下,协调的成本确 实是要增加的。 要确定什么时候要使用馈送缓冲区,首先必须寻找迭代小组之间的依赖,只在一些关键依赖 上加入馈送缓冲区。还要看接受方的小组有没有能力快速的把高价值的工作交换近来,如果有, 也不一定要加入馈送缓冲区。如果一个小组可以只使用另一个小组的部分交付就可以工作了,也 不需要馈送缓冲区。 2,,,,确定馈送缓冲区的大小确定馈送缓冲区的大小确定馈送缓冲区的大小确定馈送缓冲区的大小 理论上缓冲区的大小要用前面我们说过的均方根方法来指导计算。不幸的是,大多数小组间 的依赖都是由很少的描述或者功能造成的,您通常没有足够的数据来进行这样的计算,所以可以 先简单的用相互依赖的描述规模一定的比例来计算,常用的比例是 50% ,然后再根据小组的判 断来调整。 如果一次馈送缓冲区比一次迭代都长,就要考虑这个缓冲区设置的是不是合理了。这种长缓 冲区通常是计划把一大块功能传递给另一个小组造成的。但如下两个原因告诉我们可能不需要那 么长的缓冲区。 首先,一个小组到另一个小组的交接,几乎毫无疑问的应该被分割开来,让功能逐渐交付。 其次,小组一旦发现有另一个小组在跟随他们空转,就应该找到分割工作的方法,或者在迭 代期间作出其它的调整,而不是耗费掉一个相当大的馈送缓冲区。 很多情况下,让接受小组扮演产品所有者或客户,往往可以使两个小组都找到对他们有用的 增量交付程序。如果一定要用一次 迭代那么长的时间设置馈送缓冲区,就应该质疑并且回顾整 个交付计划,看看有什么办法缩短把可交付功能从一个小组传递到另一个小组的链条。 如果只有一个小组,甚至 3~4 个大约 7 个人的小组,就不一定需要做本节所说的事情。但 是对于大型的、多开发小组的项目,需要在很多个月以前就宣布和承诺最终期限,而且很多大型 项目小组之间具有很强的依赖性,面对这样的状况,多花几个小时来做规划是有意义。这样做可 以让您在一开始就更有信心地、更准确地估计目标完成日期,还可以对那些容易避开的进度延误 提供一些保护。 7.3 改进的软件经济学改进的软件经济学改进的软件经济学改进的软件经济学 对软件开发进行经济上的改进是比较困难的,只对某一方面改进效果也是有限的,只有全面 地改进软件过程的各个方面,才可能获得明显经济上的好处。下面对此提几个建议: 1111,,,,缩小软件规模缩小软件规模缩小软件规模缩小软件规模 不管怎么说,基于投资回报(ROI)最显著的方法,是缩小软件的规模,也就是尽可能的基 于构件的开发。但要注意当构件规模减小的时候,构件之间的通信量就会加大,而且系统集成的 成本就会增加。 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 239 - 2222,,,,复用复用复用复用 复用确实可以提高投资回报率,问题在于开发可复用的构件成本并不低,一个能够在不同项 目中应用的构件,其开发成本不能低估,仅仅开发可复用的构件,并不一定就能降低成本。因此, 开发可复用的构件,必须考虑是不是有广泛的客户基础来获得经济利益。 3333,,,,改进软件过程改进软件过程改进软件过程改进软件过程 软件过程的质量会强烈的影响需要的工作量,也会影响产品的进度。过程的改进至少有 3 个方面: 我们使用一个 N 步的过程并改进每一步的效率。 我们使用一个 N 步的过程,删去一些步骤,以至于现在只有 M 步。 我们使用一个 N 步的过程,并使得要执行的活动和所用的资源尽量保持一致。 很多过程改进都强调的是第一个方面,但本课程强调的是第二和第三方面,这里面大有潜力 可挖,尤其是过程改进的目标是以最小的迭代次数获得完备解,并尽可能消除下游废品和返工。 每一次出现废品和返工都会导致一系列的重做任务,比如在测试的时候发现了设计缺陷,重 做设计将会导致产品延期和费用增加,如何压缩这些任务系列呢?很多过程都是理想情况,但真 要出现这个问题怎么办呢? 我们需要对工程活动进行管理,以使出现废品和返工的时候不会对任何项目相关人员的取胜 条件造成影响,这应该成为多数过程改进的根本前提。 4444,,,,改进团队的有效性改进团队的有效性改进团队的有效性改进团队的有效性 长期以来,人们一直认为人员上的差异是生产力上波动的最大原因。但实际上只建立优秀人 员组成的团队并不现实。人才的配备主要掌握平衡的原则,项目经理要让高度熟练的人员处于重 要的位置上,并注意以下箴言:  只要项目管理良好,使用一般的工程团队也能成功。  管理失当的项目,即使使用专家级的团队,也几乎无法成功。  只要系统构架良好,使用一般的软件团队也能构建。  构架差劲的系统,即使使用专家级的开发团队,也会难于实现。 这样一来,就获得了团队组织的一些基本的原则:  顶尖人才原则:使用更好的和更少的人员,保证团队有合理的规模,人多和少都是不利 的,而一个队伍中的所谓顶尖人才应该严加限制,而对于明显不合格的员工,应该坚决 淘汰。  工作匹配原则:把任务分配给技能和动力都匹配的合适人员。在团队中,有经验的程序 员常常希望被提升到设计师或者经理的位置上,而团队领导似乎也认为这种提升是一种 激励,但两者技能上的需求是不一样的,结果工作的失败往往对程序员和领导双重打击。 这样的例子举不胜举。  职业发展原则:帮助员工自我实现的组织,最终将获得最好的成绩。表现良好的员工往 往总是能在任何环境中自我实现,组织可以帮助也可以阻止员工的自我实现,而组织的 作用最能帮助中等和中等以下的成员,这些帮助主要是通过培训来达到。  团队平衡的原则:选择与其他人互相补充的,协调一致的员工。团队平衡很重要,只要 任何一方失去平衡,团队就可能处于危险之中。 软件开发是一项集体运动,必须培养一种团队的合作的,而不是追求个人成功的氛围,在上 面五项原则中,团队平衡和工作匹配应该是最主要的目标,因为顶尖人才原则和逐步淘汰的原则 必须在团队平衡的原则下实施。 7.5 时代呼唤优秀的软件架构师时代呼唤优秀的软件架构师时代呼唤优秀的软件架构师时代呼唤优秀的软件架构师 ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 240 - 在软件组织中,架构师的作用是举足轻重的,当企业把一个方向的生命线托付给你的时候, 责任也是重大的,因此架构师必须十分谨慎和细致,最后我给你提如下一些建议: 1,,,,架构师的知识结构架构师的知识结构架构师的知识结构架构师的知识结构 1)首先必须是一个好的程序员,技术上要强 2)知识结构:对象的观点,UML,RUP,设计模式 关键不是懂得了原理,而是灵活融合的应用 3)系统的观念:分析能力,把握抽象的能力 4)沟通能力:与客户沟通能力,与项目其它成员的沟通能力 5)知识面要广,把握行业流行趋势,但不要赶时髦 6)灵活机动,不能教条 2,,,,聚焦于人聚焦于人聚焦于人聚焦于人,,,,而不是工艺技术而不是工艺技术而不是工艺技术而不是工艺技术 事实上,软件开发是一个交流游戏,你必须保证人们能彼此有效的沟通(开发人员、项目涉 众)。架构师要以最高效的可能方式与客户和开发人员一起工作和讨论,白板上书写讲解、视频 会议、电子邮件都是有效的交流手段。 3,,,,保持简单保持简单保持简单保持简单 建议“最简单的解决方案就是最好的”,你不应该过度制作软件。在架构设计上,你不应该 描述用户并不真正需要的附加特性一个辅助的原则就是:“模型来自于目的”。 这个原则引发了两个核心的实践。 第一个就是描述模型的文档力求简单明了,切中要害。 第二个就是架构设计避免不必要的复杂性,以减少不必要的开发、测试和维护工作。 你的架构和文档只要足够好,并不需要完美无缺,事实上也做不到,因为建模的原则就是“拥 抱变化”。你的文档应该重点突出,这样可以增加受众理解它的机会,同样这个文档会不断更新, 因此如何管理好文档显得十分重要。 4,,,,迭代和递增的工作迭代和递增的工作迭代和递增的工作迭代和递增的工作 这种迭代和递增的工作,对项目管理和软件产品开发,事实上提出了更高的要求,你必须时 时检验你的项目进展,不要使它偏离了方向。 5,,,,亲自动手亲自动手亲自动手亲自动手 考察一下你遇见过的“架构师”,最棒的那一个一定是需要的时候立刻卷起袖子参加到核心 软件开发中的那个人。架构师首先必须是编程专家,这是没有错的。积极参与开发,和开发人员 一起工作,帮助他们理解架构,并在实践中试用它,会带来很多好处。  你会很快发现你的想法是否可行。  你增加了项目组理解架构的机会。  你从项目组使用的工具和技术,以及业务领域获得了经验,提高了你自己对正在进行的 架构事务的理解。  你获得了具体的反馈,用它来提高架构水平。  你获得客户和主要开发人员的尊重,这很重要。  你可以指导项目组的开发人员建模和小粒度架构。 6,,,,在开口谈论之前先实践在开口谈论之前先实践在开口谈论之前先实践在开口谈论之前先实践 不要作无谓的空谈和争论,你可以写一个满足你的技术主张的小版本,来保证你的方案切实 可行,这个小版本只研究最最关键的技术。这些主张只写够用的代码就行了,来验证你的方案切 实可行。这会减少你的技术风险,因为你让技术决策基于已知的事实,而不是美好的猜想。 7,,,,让架构吸让架构吸让架构吸让架构吸引你的客户引你的客户引你的客户引你的客户 架构师需要很好的与客户沟通,让客户理解你的工作的价值,他如果明白你的架构工作会帮 助他们的任务,那他们就会很乐意的和你一起工作。架构师的这种与客户沟通的技巧极其重要, ◆中科院计算所培训中心 高级系统架构师培训 网址 http://www.tcict.cn - 241 - 因为如果客户认为你在浪费他的时间,那他就会想方设法回避你。你的架构描述能不能吸引客户, 也成了建模是不是能顺利进行的关键。 8、、、、架构师面对时代的考验架构师面对时代的考验架构师面对时代的考验架构师面对时代的考验 年轻人需要成长为合格的架构师,需要扎扎上实实的从基础做起,不断提升自己的能力,并 不是听过几个课程,就能够成为一个合格的架构师的。架构师必须善于学习,一个人最大的投资 莫过于对自己的投资,每周花三个小时时间用于学习是完全必要的。 架构师的知识在必要的时候要发生飞跃,但是,这种知识的飞跃必须是可靠的,是经过深思 熟虑和实验的,同时要反复思索,把自己的思维实践和这种知识的飞跃有机的结合起来。 架构师要更看重企业所要解决的问题。 架构师要学会在保证性能的前提下,寻找更简单的解决方案。 做一个好的架构师并不是一个容易的事情,这需要我们付出极其艰苦的努力。 我这个课程的主题就是“拥抱变化”,需求是在变化的,架构是在变化的,设计模式也是在 变化的,项目管理当然也是变化的。知识经济的时代在呼唤优秀的软件架构师,在这个大变动时 期,给我们每个人提供了巨大的机会,也提出了巨大的挑战。 时代呼唤着优秀的系统架构师,好的架构师的优势在于他的智慧,而智慧的获得,需要实实 在在的努力。
还剩240页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 20 金币 [ 分享pdf获得金币 ] 29 人已下载

下载pdf

pdf贡献者

ruan635

贡献于2010-10-31

下载需要 20 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf