级别: 初级 Ezequiel Cuellar, 软件工程师, Number Six Software
2007 年 8 月 15 日
了解恰当地执行用例实现将如何形成软件系统中良构的面向对象设计。
来自 The Rational Edge。
为什么在如此多的健壮的企业系统解决方案中的代码很难理解、维护及复用?主要原因是当执行用例实现时,开发团队没有按规定的方式来应用最佳实践。
根据 IBM Rational Unified Process®,或 RUP® 所说,“利用对象的协作,用例实现描述了在设计模型中如何实现特定的用例。”用例实现的目标是生成用于实现用例中定义的行为的良构的面向对象的设计。
基本上,使用面向对象的设计意思是适当地将职责分配给那些组成了系统的对象,并且确定出在它们之间传递的消息。这需要职责分配技巧和设计模式的使用技巧。
职责分配的两个关键原则
这两个职责分配原则在完成良好的面向对象设计的方面扮演了主要角色:高内聚和低耦合。内聚是对象职责的力量和焦点的度量。有人说,当对象承担应该由其他对象执行的职责时,它就是低内聚的,换句话说,高内聚需要限制对象的职责。当对象依赖太多其他对象时导致高耦合。因此,低耦合需要限制对象之间的依赖级别。
理论上,这两个职责分配原则似乎很容易理解。在实际中,它们很难理解和掌握,特别是对于那些正在向面向对象应用程序开发过渡的程序设计人员。在某种程度上这就是“对象编程思想”所指的内容,这是面向对象领域中最难学的技巧,而也是最关键的。在执行用例实现时没有应用高内聚和低耦合的原则,表示软件开发项目中有严重风险。
为什么?如今,企业系统解决方案一般采用多层的系统架构,包含用于表示、控制器、应用、领域,和持久性的分别的层。用例实现可能需要在这些层之上的工作。然而,最复杂且关键的应用程序行为 —— 例如,为业务规则、业务逻辑,或业务实体分配职责 —— 落入应用和领域层。如果在这些层中没有用正确且平衡的方式分配逻辑,那么将会产生系统随着越来越多的功能的增加,不能适当的演进的风险。随着时间的过去,系统(或子系统 —— 同样的问题)可能会如此复杂,以至于很难理解、维护,并复用它的代码。这就是为什么对于开发人员来说,在执行用例实现时,向系统的应用和领域层熟练应用高内聚和低耦合的原则是如此重要的(参见图 1)。
图 1:应用高内聚和低耦合的原则在企业系统架构的应用和领域层中是特别至关重要的。
过程不解决此问题
应用程序常常没有嵌入高内聚和低耦合的原则的原因与现实的软件开发过程有关。当一个团队为当前的迭代确定目标之后,第一个步骤常常是分析它们的需求,并将它们编制在用例文档中。当评审用例,并认为它稳定之后,下一个步骤是进行面向对象的分析来支持它们,这涉及领域模型的生成(随着项目的演进,领域模型将快速地过时)。在那之后,最后的步骤是排列用例的优先次序,并分配它们进行实现。
从此开始,用例通常完全委托给开发人员,全部依赖于他们的面向对象设计技能。一般,他们直接根据事件的用例流来编写代码,匆忙地实现对象职责和协作。不幸的是,除了之后执行的系统测试以外,没有对于他们的用例实现的质量度量。如果这些成功了,那么在不验证是否依据高内聚/低耦合原则而设计的情况下将新特性发布为产品。
这是质量控制中严重的缺口。仅仅确保实现遵守了用例需求是不够的,除非团队特殊关注实现的设计,否则结果将是可怕的。
一些软件开发过程建议将可视地建模和设计软件作为避免此问题的最佳实践。然而,一些厂商宣传它们的可视化建模工具的用途,但是没有强调将模型与实现过程中的设计决策和变更保持同步的重要性。当模型过时后,团队常常抛弃,未能利用它们的代码生成能力。对于他们来说,模型只是思考或交流解决方案的非正式的方式。他们没有为整个系统创建精确的模型和设计图,因此不存在用于审查或控制内部构件的质量的文档。
代码审查是用来瞄准此问题的。通常代码审查由系统架构师执行,或者委托给团队中更熟练的开发人员。然而,一般在开发过程中,进行代码审查的时间太晚了(在已经完成用例实现的时候),而且,进行大规模重构工作或完全的再实现是费时且昂贵的 —— 如果审查说明需要进行这样的工作的话。
与其试图在既成事实之后调整实现,倒不如团队用一组实践来推进开发过程中的质量。
用例实现的最佳实践
在本部分中,我将介绍一组已证实的实践,团队可以应用它们将质量建立在用例实现中,并确保它们符合高内聚力和低耦合的原则。简要来说,这些实践是:
- 在团队会议上设计用例实现。
- 利用交互图交流设计思想。
- 应用设计和职责模式。
- 在设计会议上进行代码职责提取。
- 在进行主要的设计变更之前进行清理。
- 核查在设计实现中发生的变更。
也许您在问自己,“我的团队真的需要追求这些最佳实践吗?我们都是有许多面向对象经验的十分熟练的高级开发人员”。
回答是“是的”!即使在这样的团队里,每个成员也不可能都知道开发组织正在使用的每个工具或框架的技巧。团队成员总是应该彼此学习,这样促进技术解决方案中的质量和一致性。总的来说,通过遵循这些最佳实践,团队成员应该能够发现所有风险,然后有效地瞄准它们。
在团队会议上设计用例实现。
用例实现决不应该由一个开发人员单独完成。这会引发出此人会匆忙分配和实现职责,并忽视确保职责的恰当内聚和耦合的风险。在团队会议上(最少包括系统架构师和负责实现的开发人员)进行用例实现要有效得多。其他技术项目干系人可随需要参加(参见图 2),但重点应该仍旧在于面向对象设计。举例来说,业务分析师不需要参加,除非需要他们阐明用例中的一些东西。在团队会议上进行用例设计还将帮助经验较少的开发人员提高他们的设计技能,并且增加他们发现潜在的实现风险的可能性。
图 2:用例实现的团队会议应该包括系统架构师和负责实现用例的开发人员。
在许多团队中,一个人可能扮演多个角色。举例来说,系统架构师还作为开发人员来实现用例实现的一部分是完全可以接受的。然而,即使一个人能够完成小项目中一个用例的设计和实现的每一个部分,生成用例实现也必须是一个团队的工作。
此工作应该分为两个阶段进行:
- 用例实现的团队会议讨论和设计用例实现。
- 后面的团队会议讨论所需的设计变更。进行用例实现的开发人员应该和系统架构师碰面,审查当前的研究结果,并重温前面的设计决策。
利用交互图交流设计思想。
有多少次您曾经在白板上看到用缺少一致语义或符号的非正式的绘画乱写的设计思想?可视化地设计对象是一个很好的实践,但当您试图交流设计决策时,不用定义明确的建模符号来设计会弊大于利。如果设计太复杂的话,绘图会快速松散起来(参见图 3)。定义明确的 UML 交互图比非正式的绘图实际得多。它用有组织的方式清楚简洁地表示思想 —— 并且消除了生成之后不可能正确理解的复杂绘图的风险。
交互图是用于交流在用例实现团队会议中出现的设计思想的非正式文档。它们的目的是帮助向对象分配职责,不是用于十分详细地介绍复杂过程的每个方面的。您是否用可视化的建模工具绘图,或者仅用手绘制都没关系,通过哪种方法,您都可以用它们有效地交流面向对象的设计(参见图 4)。
UML 定义了两种类型的交互图:序列图和协作图。在序列图中,对象交互以一种栅栏的格式显示,并从左到右添加对象。协作图可以垂直和水平地展开,从而适应附加的对象。每个图都有它的利弊,但都相当容易绘制。
Kent Beck 和 Ward Cunningham 提议在团队会议过程中采用 CRC 卡片来进行面向对象设计。这些卡片是交互图的极好选择,唯一的缺点是它们缺少对象之间的消息如何流动的可视化表示。然而,开发团队应该能够在设计会议上补偿这一点。
图 3:没有用定义明确的建模符号绘制的非正式的系统会很快松散起来。
图 4:UML 序列图提供表示系统的简单,条理分明的方法。
应用设计和职责模式。
设计模式是特殊架构和框架环境中的问题的解决方案,它反应了一种完成需要重复的事情的方式。应用模式可以令设计和实现更加一致、可以理解,且高质量。
当设计用例实现时,高内聚和低耦合是要考虑的主要的职责分配模式,但设计模式在面向对象设计中也扮演了重要角色。在团队会议中集中于它们在环境中设计的应用,提供了交换知识并重申设计技巧的极好机会。此外,团队可以创建它们自己的模式,并且加速设计会议。举例来说,如果开发团队不得不为不同的用例多次设计简单的 CRUD(创建、读取、更新,删除)过程,它们可能会发现自己每次都在创建同样的对象职责。当出现这种情况时,团队可以决定创建一个模式,并进行编制。然后,他们可以在交互图中引用该模式,去掉为每个用例重复同样细节的需求。
每个模式都应该有一个简要称呼特别的设计解决方案的名称。这为技术团队提供了丰富且简明的词汇 —— 用于口头和建模交流。举例来说,假设团队都知道称为“Entity Retrieval”的模式。当设计人员说“我们为了获得 Invoice for the Purchase 要在此应用 Entity Retrieval”时,团队中的每个人都将理解设计和编码的含义。设计人员还可以用调用模式的标志对交互图进行注释,去掉了交流在应用模式时所涉及的细粒度关系和行为的需求。
在设计会议上进行代码职责提取。
用例实现会议应该生成主要的输出工件,软件开发过程常常提出由交互和设计类图组成的设计模型。然而,如我们提到的,团队经常把模型作为考虑或交流解决方案的非正式方式,而不是必须实现的一组规范。因此,正式的设计模式也许不是用例实现会议的最好的输出工件。
职责提取是更好的选择,除非在会议地点没法访问项目源代码。理论上,团队应该在用例实现会议上,在对它们制定的设计决策编码的同时,对职责提取进行编码(参见图 5)。然后,开发人员或管理用例的开发人员应该负责完成它们的实现。
此方法令团队对他们将要实际遵循的设计取得一致,而不仅仅是生成可能从不实现的思想。该实践还对设计实现进行了较小程度的监督,没有它,就不能保证会议上讨论的设计会得到实现。
此会议的焦点应该是对那些属于系统应用和领域层的对象的职责提取进行编码,就像我们先前提到的,这是达到恰当的内聚和耦合级别的最关键的地方。是否有必要在其他层中对职责编码主要取决于在用例实现会议中做出的系统架构风格和设计的决策。在提取中还应该使用代码注释,从而详细说明要实现的方法的具体思想或行为。
图 5:职责提取的示例代码
在进行主要的设计变更之前进行清理。
不论您是多么仔细的设计人员,或者不论您重温多少次设计决策,都会存在忽略一些东西,或没有发现隐藏问题的风险。设计决不是百分之百正确的,只有在实现过程中才能证明它们的正确或错误。如果开发人员在实现过程中发现了团队在会议中忽略的问题 —— 一些可能导致主要设计变更或造成严重风险的东西 —— 那么开发人员就有责任通过要求后续的团队会议来引起系统架构师的注意。
在这样的会议中,系统架构师和其他团队成员可以重温设计,并仔细地评估发现。理论上,这样的会议的输出工件或者是用于正确的职责提取的代码,或者是带有必要修正的非正式设计文档。根据设计问题的严重性,系统架构师甚至可能想要召开另一个会议,以在环境中重温整个用例实现。
如果开发人员只发现设计及其实现之间的,可以用较小的调整进行修正的较小的差异,那么他们不应该号召后续的团队会议。这些是可能发生的,只有随着时间的过去,团队在进行用例实现会议时所获得的经验可以最小化,或者甚至消除这些问题。
核查在设计实现中发生的变更。
已知所表示的设计及其实现之间必然存在这些差异,那么实现用例实现的开发人员应该有责任进行较小的调整。然而,一旦完成了用例实现,并且进行了单元测试,系统架构师就必须审查设计实现的准确度。这类似于进行代码审查,但焦点是验证是否恰当地应用了高内聚和低耦合的原则,以及是否根据团队会议中制定的决策来实现设计决策。特别重要的是确保开发人员没有在未首先要求后续的团队会议的情况下引入了主要的设计变更。此架构审查应该是在转入系统测试之前验证代码质量的最后度量。
结束语
在最近几年中,软件工程过程,结合面向对象的程序设计和提高的开发环境,已经给予了软件项目更大的控制等级,因此有更大的成功概率。然而,过程仍旧不够粒度化,以遵循高内聚和低耦合的原则。应用这些良好的面向对象设计的基本原则仍旧是个别开发人员的工作,法规遵循不能由软件工具进行度量或自动化。
本文介绍了能够很好地为许多项目瞄准此问题的最佳实践。然而,主要的意图是揭示可能正在影响许多当前的软件项目的问题,并鼓励读者在这个问题上做些工作。我的希望是更多的团队在执行用例实现时,依据所介绍的最佳实践、开发新的实践,或创建新的工具和技术来正确应用高内聚和低耦合的原则。
参考文献
Craig Larman,Applying UML and Patterns,第二版。Prentice-Hall,2001 年。在线摘录:
http://www.craiglarman.com/book_applying_2nd/Applying_2nd.htm
IBM Rational 统一过程产品信息:
http://www.ibm.com/developerworks/rational/products/rup
Extreme Programming (XP) Web 站点:
http://www.extremeprogramming.org
Kent Beck 和 Ward Cunningham,“A Laboratory for Teaching Object-Oriented Thinking.”在线链接:
http://c2.com/doc/oopsla89/paper.html
参考资料 学习
讨论
- 现在开办了一个特别为 Rational Edge 的文章创办的 新论坛 ,现在您就可以分享您对本文或本期杂志或以前杂志中的其他文章的想法。阅读世界各地您的同行们所说的内容,生成您自己的讨论,或者加入正在进行的讨论。单击 这里 开始。
-
全球 Rational 用户组社区
关于作者  | 
|  | Ezequiel Cuellar 是 Number Six 的软件工程师,拥有八年的企业系统解决方案的设计和实现的行业经验。他参与过许多为 Fortune 500 的客户进行的大型项目的创建,并且是许多开源计划的奠基人。您可以通过 ecuellar@numbersix.com 联系他。 |
对本文的评价
|