OO 设计过程：入门

IBM China
购物 技术支持 免费下载
IBM 主页 产品 咨询 行业 新闻 关于 IBM
dW 中国站点 : 组件技术概览 : 文档库 - 论文
英文原文

OO 设计过程：入门
如何设定优先级

Allen Holub
撰稿编辑，JavaWorld
2000 年 7 月

内容：
OO 不是...
定制过程
缺少工具
设计环境
设计和体系结构
结束语
参考资料
关于作者

欢迎阅读本在线课程的第一部分。在本专栏中，我打算让您实际操作，以亲身体验面向对象 (OO) 的设计和开发过程。与其说本专栏是一个活动，不如说它是一个旅程，因为需要几个月才能完成整个过程。我们将从需求搜集入手，从分析到设计，然后完成设计的 Java 实现。到结束时，您将经历开发 OO 程序的全过程，真正地从开始到结束。我将花很大的篇幅讨论基本理论，但主要重点仍将集中在如何应用该理论的实际示例上。

下个月我们将正式开始，在此之前，我有一些忠告、意见和评论，这些意见将告诉您我对设计主题的个人观点和看法。

OO 并非关于结构
首先，就其核心而言，面向对象与派生、类层次结构、UML、Java 技术等毫无关系。这些是 OO 设计人员用来构成分析、设计和实现的工具，但它们并不是使程序面向对象的主要部分。当然，随着过程的展开，我将使用面向对象的所有这些结构性部件，但如果您将实现结构与面向对象等同起来，那么前几篇专栏文章可能特别难以理解。面向对象的关键概念是建模，所以在动手之前，必须决定建立什么模型。Adele Goldberg（在 Succeeding with Objects 中，请参阅参考资料）叙述了一位犹太教教士在新年依始的宗教集会上讲述的故事：

一位教登上一列火车，由于他经常乘坐这辆车，因此列车长认识他。教士伸手到口袋中掏车票。但没有找到，他开始翻他的行李。列车长阻止了他：“教士，我知道您肯定有车票。现在别急着找。等找到后再向我出示。”但教士仍在找那张车票。当列车长再次见到他时，教士说：“你不明白。我知道你相信我有车票，但 -- 我要去哪里呢？

有太多项目失败就是因为它们没有明确的目标就开始了。OO 过程试图通过首先解决这个问题来应付这种困境；需要几篇专栏文章才能足以细致地做到那一步，以便继续进行分析和设计阶段。即，在可以分析之前，要有东西可以分析。

随意定制
下一步，描述的过程是我个人使用的。我并不打算使这些文章成为权威。我的工作方法类似于 Booch、Rumbaugh 和 Jacobsen 的 RUP（Rational Unified Process，请参阅参考资料），但我并非有意为之。事情就是那样解决的。我的工作方法还类似于 Kent Beck 的所谓 Extreme Programming（XP，请参阅参考资料），但这只是一种巧合。我希望您能够根据自己的需要定制我所用的过程，以使之更可工作。考虑到文学作品十分缺乏任何过程的实践讨论，因此看起来对任何过程的实践讨论（甚至个人过程的讨论）都将十分有益。请不要费时考虑我是“对”还是错，只要记住我所做的只适用我个人，并且为使之更好地为您工作而变更我所做的方式完全没有错。

工具或工具的缺乏
有关 OO 设计还要指出一点。有人曾告诉我说，他无法进行 OO 设计，因为他支付不起：Rose 每客户要 2500 美元，而且显然，没有 Rose 就无法设计。这里所讲的 "Rose" 是 Rational Software 所售的一种 OO CASE（计算机辅助软件工程）工具。（我可能会在以后的专栏中详述 OO-CASE）。撇开 Rose 既不是最划算、也不是功能最强大，更不是现有工具中最易使用的事实不说，我常常只用铅笔和纸、而不用任何高科技工具就能有效工作 - 根本不用计算机。

正如我们要在即将出现的专栏中所见，OO 过程实际上是一种语意分析形式。它与正式的语言学、而不是传统的过程设计技术有共同之处。我发现，计算机常常阻碍这种分析。您在工具上花的时间比实际完成工作所需的时间还要多。更糟的是，很多这些工具（包括 Rose）都有令人讨厌而又苛刻的许可证管理器，它使您无法在没有通过网络与许可证管理器连接的便携式电脑上运行这些工具。当我使用计算机时，我通常使用一个绘图程序 (Visio)，而不是 CASE 工具。（这并不是说，我认为 Visio 是用于该目的的极好工具。只不过我恰巧有一份而已。我使用自己的模板 - 请参阅参考资料 -- 而不是内置 UML 支持。）

我要说明的观点是设计与工具无关。设计是使您明确对复杂主题的自主想法，它是一种确保在开始编码之前尽可能全面考虑问题的方法，并且是与其它程序员就编码进行交流的方法。

由于在这里交流是真正的核心部分，任何对交流没有帮助的工具实际上都毫无价值。如果您单独坐在办公室内，面对基于计算机的 CASE 工具，那么您就不是在交流。

选择 CASE 工具无关紧要。我一般不使用任何工具，即使它们对较大的项目会有所帮助。其缺点是它们都会生成极差的代码，所以我不使用这些工具的代码生成或“往返工程”特性。无论如何，往返工程的观念 --创建设计，生成代码，修改代码，然后将其逆向工程到“设计”，这是一种根本性错误的观念。设计本身应该是单向过程。先设计，然后根据设计实现。修改设计，然后实现修改过的设计。大多数这些工具无论如何创建出的设计都是不正确的，因为它们不能够仅仅通过查看代码就能逆向工程程序的动态行为。

对于我们的目的，相当好的绘图程序和字处理器就已足够。

设计环境和工具
设计始终是一种团体行为。最起码，需要程序员、测试人员、用户（或者完全了解问题领域的人）、写作很棒的人以及 UI 设计人员。您最好有书记方面的人员：能够记录和纂写，以及将手写的草图输入到绘图程序等等。您也可以不需要这些，但设计进度会减慢，因为小组成员不能够集中精力做他们的本职工作。

所有成员必须一起才能有效地工作，而且他们应该始终在一起。设计是专职活动，如果希望按时间表完成，就应该不要被其他事情打扰。（虚拟合作，可能的话使用白板软件或类似的。如果需要有效地沟通，要有视频和音频输入，才能使您看到和听到其他人员。）

可以有的最好设计工具之一是一间专为该用途设计的房间。我的理想工作间是一间令我心情愉快的房间。它有许多窗户和光线。墙上有可以挂白板的木板。（您可以买 4x8 大小，类似于 Home Depot 用的三聚氰胺片，如果您愿意可以将它们安在墙上。）不要会议室 -- 扔掉会议桌和令人不舒适的椅子（我想，这样使会议尽可能短），换上一只舒适的沙发和几只扶椅。您需要装满食物的冰箱和需要时可以移动的小桌子。使用的主要设计工具是：白板笔和板擦，还有可以连到联网机器的 LCD 投影仪，所有这些可以使小组成员共同书写文档。用高分辨率照相机将图像从白板传送到计算机。最理想的是，将设计室和如 Beck 在其 XP 一书中所述风格的工作室组合起来 - 增加几个工作站群集和一些人们独处所需的私人空间。

通常，办公室里没有这样的空间，因此您可能得自己设计。取出螺丝刀，拆开小房间，组合成一个大房间。如果设备人员不喜欢这样，那么就强硬一些。对公司的成功更为重要的是：生产高质量软件，或支持“正确”办公室应该是什么样子的陈旧观念。

设计和体系结构
Microsoft 所犯的更可恶罪行之一是将动词 "to architect" 引入到英语。设计师设计房屋，但他们不“建筑”。我提出这个词语误用的原因在于，这种语言的滥用人为地暗示：房屋设计师和计算机程序设计师从事完全不同的活动。我坚信，无论设计房屋还是设计软件，设计就是设计。例如，Christopher Alexander -- 房屋设计师 -- 设想了设计模式的概念（请参阅参考资料中的 A Pattern Language: Towns, Buildings, Construction），而设计模式是当代软件设计的基础。

如果您对一个承包商说：“希望您为我建造一所房屋，它的造价有多少，您是否能完成？”。那他一定以为您疯了。然而，程序员肯定总在回答同样空泛的问题，而人们也在问：为什么软件很少按时并在预算内交付。此外，没有承包商愿意在没有某种计划那样复杂的情况下开工。然而，承包商自己设计的计划通常不会产生如设计师设计的那样好的房屋。承包商的设计很自然地会简化建造和降低成本，这不一定是您要居住的房屋的最佳选择。类似地，程序员通常不是最好的程序设计人员，因为他们的侧重点与最终用户不同。这并不是说，一名优秀的设计师不知道如何建造他所设计的房屋，- 我确信最好的软件设计师也是最好的程序员 - 而是说，设计和建造是两种不同的活动，不必由同一人来承担。

设计房屋的第一步是与客户协商，并构想如何使用该房屋。设计师感兴趣的是客户的美学，当然，他们还会问很多其它问题。有何爱好？（需要为这些爱好专门准备空间吗？）有宠物吗？要在厨房中准备喂猫的位置吗？）有孩子吗？（他们需要玩耍房间吗？）要几个玩耍房间？（玩耍房间要多大？）所有这些都影响房屋的设计。如果设计师对将在房间中发生的活动不熟悉，他们还必须学习这种活动，以达到令人满意的设计。比如，如果孩子养宠物鱼，那么，设计师还要学习池塘技术 - 不必达到宠物鱼的水准（用设计行话说，“领域专家”），但至少要达到具有相当知识的普通人水平。请注意，重点是将如何使用房屋。建造考虑，虽然总在设计师的考虑之内并影响设计，但不是主要的。

下一步是勾画大量房屋、房间、地面等。这时会有很多更改 -- 改变房间位置和外形、添加和除去固定设备和墙壁。只有当草图正确反映出客户需求时才可以说定制出一组“最终”计划，但即使这些计划也并非真正最终的计划。他们被提交给承包商和工程师，所有这些人都会请求更改，所有这些更改（如果可见，或至少影响成本或进度）都要由客户同意。只有在这时才开始建造。

计划中的细节级别也有关系。大多数设计工作在开始建造房屋时才开始进行。设计师指出墙壁伸展方向，但由承包商构想细节 -- 壁骨放置、门窗的加框等。设计师指出电器开关的位置，而电工则构想如何布线和电气出口的位置。计划指出排水沟和排水管固定装置，而管道工则构想如何布置管道和连接到总水管和下水道。这不是说，不设计这些建筑部分 ，而是说，这部分设计由专门人员建造完成。设计工作常常是非正式的 - 随工作进度现场进行。而且，大部分设计产物，如上面有绘图的纸片或墙壁内壁骨的草图，都被丢弃，因为在建好之后，它们不再有用。当技术人员需要做的事影响其他技术人员或整体建筑设计时，还要重新由设计师修改主计划（在征询客户同意之后）。

如果有简单和复杂的方法都可以解决同一问题，只要不影响结构的整体性和美感，则总是选用简单的方法。但是，有时只有复杂方法可以解决问题。值得注意的是，在建造开始之前所需的细节程度也决定了结构的复杂度。可以在一张餐巾纸上设计一间狗舍。而一间房屋则需要更多细节。事实上，一幢摩天大楼里的每一根管道和电缆管都要设计（尽管有时不设计墙的位置！）。

结束语
以上所述过程基本是与我用来创建软件相同的过程，它也是本专栏将逐一演示的过程。从下个月开始，我将讲解大量细节，以便可以将细节放在相关环境中讨论。实际上，很多这些操作都并行发生，我们将在今后几个月中看到。

  1. 了解问题领域。
  2. 与用户交流，并确定他们的需求和目标。
  3. 开发问题说明书。
  4. 设计用户界面。
  5. 开发用例。
  6. 拟出草案静态模型。
  7. 在细化静态模型时开发动态模型。
  8. 实现。

在逐一进行这些步骤时，我试图描述所发生的整个过程，包括所犯错误和如何修复错误。结束之后，您将对这种设计过程有完整（和现实）的认识。

参考资料

  • 查看 Adele Goldberg 和 Kenneth Rubi 所著的 Succeeding with Objects: Decision Frameworks for Project Management，对于要转向 OO 环境的管理人员来说，这是一本极好的书籍。
  • Ivar Jacobson、Grady Booch 和 James Rumbaugh 在 The Unified Software Development Process 一书中讨论了 RUP (Rational Unified Process)。
  • Kent Beck 在 Extreme Programming Explained: Embrace Change 中讨论了 XP (Extreme Programming)。XP 是有争议的，但我想，许多争论是由于人们没有首先阅读他的书籍而做了 Beck 所说的假设而引起的。如 Grady Booch 在其新的 OO 设计书籍（可在 www.objectmentor.com 中找到 (PDF)）的草稿章节中所述，XP 和 RUP 实际上集成得相当好。ObjectMentor 网站还有大量有关该主题的有用资料。
  • 可从我的网站的 Goodies 一节获得一份 Visio UML 模板。
  • 在 Christopher Alexander、Sara Ishikawa 和 Murray Silverstein 所著的 A Pattern Language: Towns, Buildings, Construction 中阅读更多有关设计和体系结构的内容。

关于作者
Allen Holub 自 1979 年以来一直在计算机界工作。他在许多杂志上发表过文章（Dr. Dobb's Journal、Programmers Journal、Byte、MSJ，以及其它杂志），并且他是在线杂志 JavaWorld 的撰稿编辑。他有八本自己较赞赏的书，其中最新的一本（Taming Java Threads）讲述了 Java 线程的陷阱和缺陷。他一直从事设计和构建面向对象的软件很长时间了。当了 8 年 C++ 程序员之后，Allen 在 1996 年初放弃了 C++ 而转向 Java 编程。他自 1982 年起一直在自己学校和为加利福利亚大学伯克利分校教编程（先是 C，然后是 C++ 和 MFC，现在教 OO 设计和 Java 编程）。Allen 在 Java 技术和面向对象方面提供公共课程和内部培训。他也从事面向对象设计的咨询和商业性 Java 编程。请访问 Allen 的网站 www.holub.com。
您对这篇文章的看法如何？

真棒！ 好文章 一般，尚可 需提高 太差！

意见？
(c) Copyright IBM Corp. 2001, (c) Copyright IBM China 2001, All Right Reserved


OO 设计过程：开始设计软件

dW 中国站点 : 组件技术概览 : 文档库 - 论文
英文原文

OO 设计过程：开始设计软件
一个教学项目

Allen Holub
撰稿编辑，JavaWorld
2000 年 8 月

内容：
OO 需求搜集
问题说明书
细化您的说明书
问题和解决方案
编码前的细节
结束语
参考资料
关于作者

我们在七月份已经谈了这个系列的有关如何区分设计过程的优先次序. 这个月，我将着手设计一个软件。我希望项目不要太琐碎但很简洁，使我们在较合理的时间里能完成它。我需要一个实实在在的程序，而不只是一个纯教学性的练习。在这篇文章中，我采用一段教学软件。在以后的几个月课程中，我将演示这个程序的完整设计和 (Java) 实现，您将能够看到整个过程，从开始直至结束。同时，我（特意）没有编辑过程中的错误，以便使您能够如实地看到所发生的一切。让我们开始吧。

OO 需求搜集
我曾经看到过一份统计，每年有 60% 已编好的程序在完成后几个月内被废弃，这倒不是因为程序没有实现它的任务，而是因为它没有实现任何有用的东西。当然，由此导致的生产力下降大得惊人。此外，生成的大部分代码是非常正式 -- 他们产生了大量的文件，为了完成他们所想的设计。很显然，他们所做的没有起作用（参阅参考资料中 Steve McConnell 的社论， 同样很明显，没有设计而继续也同样不起作用。如果计算高科技创业公司的失败率，我猜想这个百分率现在甚至高于60%。

我坚信失败的主要原因之一是，大多数程序员在软件开发过程中没有一个有关他们将要做什么的清晰的想法。对于一个有用的程序，必须要问那些将要使用该程序的人，该程序所必须要做的。然而，该步骤经常被全部忽略掉。相反，自己只是找一个真正用户的代理（譬如销售人员）来试图虚构他们称之的所谓的需求。事实上程序设计者与真正用户之间需要经常交流，但在这种情况下用户不可能让程序设计者了解他们所需要的。那么，我们的首要任务是解决必须做什么，而不是如何去做。（大多数程序员会立刻将精力集中到如何去做：用什么数据库？象什么样的数据模型？象什么样的 UI? 数据结构怎样？这在目前是多么的不切实际--先抑制一下这种冲动！）

在 OO 说法中，问题定义的第一步称为正规问题说明书，在以后的几个月中构建这些问题说明书是我们的任务。

在系统中，问题说明书经常忽略需求方面的搜集。我更喜欢用问题定义这个术语，因为“需求搜集”通常包含一些关于需求是什么以及您如何去搜集它等方面的不正确的假定（至少从 OO 角度）。在传统的处理过程中，经常根据一些第三方提供的功能来形成功能性需求规范，譬如市场部门。事实上，这种需求是虚假的，您是不能控制的。您会发现在 OO 环境中这种类型的文档 -- 特别是功能列表 -- 几乎是毫无价值的。那些提供算法和指定性能需求的“功能性需求”对于 00 交付是一个有用的附录，而他们不是最中心的。“功能性需求”指定了 UI 行为、工作流等，其原因我们在下几个月讲解。

问题说明书
在任何一个设计中，精确地陈述问题总是第一步的。这里，您的目标是要简洁而精确地说明书您所要解决的问题。再次重申，您的注意力应在做什么和为什么做，而不是如何做。

在阐述问题之前，您必须首先了解有关该问题领域方面的知识。虽然每个设计组一定会有一名该方面的专家（较理想的是最终用户）但设计组的其他成员也必须了解该领域的知识，尽管不一定作为一名专家。当您用该领域的专业词汇进行交谈时，您应该具有该领域方面的足够知识而无须专家停下来去解释基本的概念。

因而，在开始设计项目之前，要有一个研究阶段。如果您正在设计一个会计程序，去参加本地中级院校的 Accounting 101 的课程。再次重申，您不一定非要成为一个专家，因为那是您用户的工作，而您须做的是能与该领域的专家进行如果没有这方面的知识，您甚至不知道去问什么问题或者如何去问他们。

细化您的说明书
问题的说明书应该是精雕细琢的，这是整个设计成败的关键。不要去做那些细支末节的工作，因为他们几乎不能抓住体现问题的微妙之处。如果您不能用清楚而组织良好的语言去说明书它，那么您所理解的问题就不足以使您设计出任何东西。良好的语法也是至关重要的；它提供了良好的结构和精确的表达方法，否则会使所要表达的想法一团糟。在任何一个设计组中，一个优秀的拷贝编辑也是非常重要的。

这里还有一些有关写作过程本身 -- 即需要准确的语言，但是在谈话当中就无需如此 -- 这能够帮助您发现问题的新方面。我发现仅仅在我先写出所有的问题再开始编码的时候，我才不会被一些东西所难倒。如果您在编码之前先写注释的话，编码就会进行很快而且在代码中有较少的错误。

所要讨论的问题必须完全用问题领域方面的词汇来表达。如果您要解决会计方面的问题，问题说明书就应该完全用会计方面的词汇。也就是说，任何一位胜任的会计师都应该理解该问题的说明书，而勿须提出一些问题。关于设计完整的一般性原则是，一位有头脑的外行通过浏览熟悉该领域的且胜任的程序员的文档，能够完全理解所要解决的具体问题和该问题的一般性解决方案。如果这种层次理解不能实现，那么设计是不完整的。

我不能太强调问题说明书不是计算机程序方面的讨论. 问题说明书应该涉及问题本身，而不是该问题基于计算机的解决方案。我们要讨论的是现实世界中您的最终用户所要解决的问题。诸如“计算机”、“菜单”、“对话框”、“因特网”和“互联网”等词汇不应该出现在这里。问题说明书从来不应包含诸如：“系统必须……”或“我们写一段计算机程序……”写计算机程序可能是您的事情，但您的最终用户所关心的问题却很少涉及计算机（虽然他们也许涉及该解决方案）。大多数问题可以在没有计算机的情况下很好地描述和解决，虽然计算机可能解决起来更快些或者更容易些。

虽然您最终必须编写基于计算机解决方案的描述，但到目前为止，我们关注的焦点是文档有关领域方面的部分。记住这里没有计算机方面的行话，任何一位会计师都应该清楚地理解程序员写的会计领域方面的问题说明书。

尽可能地确认问题和解决方案
相反，作为问题自身领域的一部分问题的解决方案理所应当讨论。所有 OO 系统必须进行一些建模。如果已存在的问题有一个好的解决方案，但现实的问题是人们不可能很快地执行已有的解决方案，那么就需要将现有的解决方案建模。即手动过程自动化是全部所必需的，而您的问题说明书应完整地描述那个手动过程。

在问题说明书中，要确认的一件重要事情是用户的目标。用户确切地要完成什么？用户的目标完全影响解决方案的方向。（譬如，您的目标是计划一个会议还是维护一个日历？在第一种情形，您在程序中可能看见类似于传统日历的地方是在输出中。Alan Cooper，在他的 About Face这本书中，称这种工作方式为“指导目标的设计”（请参阅参考资料。）

您还必须说明所希望的结果。解决问题的最终成果是什么？最终成果表达了什么信息？（注意在此阶段我们感兴趣的是该信息的内容，而不是物理布局或者是内容表达的方式。）

尽可能地忽略旧系统的存在。用户的目标很少是改进旧系统，相反，用户是要完成工作，并且因为旧系统不能工作就由您来处理。让我们从头开始。

在问题说明书问题中，不要试图太正式。只要描述您想要做的事情就行了，就好象您在和一位伙伴谈话一样。（我经常被问及“我怎么说?" 我的回答总是，“就象那样 -- 您说的那样。”

下一步，和真正的用户和该领域的专家进行几次会谈，来恰到好处地定义问题。然后，用该领域的词汇来说明问题，让您的用户看一下问题说明书，以确保您是正确的。这里我认为可以跳过会谈过程，因为我自己就是该方面的专家（父母），下面我就直接开始了。结果证明采取的这种捷径是错误的，我将在下个月的专栏中讨论这个问题。

所以，让我们开始吧

The Bank of Allen
教孩子如何管理钱财（也是一个很有趣的工作）的最好办法之一就是为他们建立一个银行帐户。然而，说起来，真正的银行不会支付非常多的利息来满足小孩子的兴趣。（毕竟，一般年利率为 3.5%，20美元存一年所获得的利息最多为 $0.72）。我从 NationalPublic Radio 的 "Marketplace," 节目中得到启示，决定开个 Bank of Allen（或 BofA）,它的月利率是 5%（每个月的确是5% -- 每年为60%）但每日计算。根据 Bank of Allen 的这个利率 20 美元在一年之后可以赚得 $15.91。尽管有如此高的利率，Bank of Allen 仍然象真正的银行一样运作。孩子们有他们自己的帐户，在这个帐户上，除了利率,他们可以进行任何操作。他们可以在任何时候查看（或打印）存折。他们可以随意的存取。

为了提高孩子们的兴趣，存折必须让孩子们看，以显示他们除了一般帐项（存、取和余额）以外赚得了多少利息。

孩子必须对假设分析游戏感兴趣：如果我整整两个月不取一分钱，那么会有多少钱呢？这一点显示了您不花钱而可以赚钱。

这里还有几个安全性的问题。只有银行（即，父母）可以改变利率或建立新帐户（和初始余额）。象真正的银行一样，孩子们不能够通过篡改他们的存折来存钱或取钱，他们必须让银行来做这件事情；银行处理交易和更新存折。即，孩子把钱交给银行，银行把这笔钱放入其帐户。银行从该帐户提取钱款并将其分与孩子。（实际上也就是，这意味着只有父母可以存钱或取钱，但这是在孩子们提出取钱的请求或认为银行正在使用这笔钱而使他们取不到自己的钱。）

目标（按重要性排列）：

  • 教孩子们存钱
  • 显示利息是怎样形成的

"Bank of Allen" 是 Allen I. Holub 的一个商标。本程序及相关设计，是 (c) 2000, Allen I. Holub. 版权所有，保留所有权利。

我不认为这个问题的说明书完整的，它只是一个开始。一般来说，如果一些适当的问题还没有表述出来，那问题的说明书是不完整的。比较典型的是大量的细节问题。对于一个小程序 -- 大概需要 6 个人花 8 个月的时间去完成 -- 在所有的细节都解决后，它的问题说明书很容易达到 80 页左右。所以，很显然到目前为止我所做的是一种非常简略的方法。

编码前的细节
练习的要点是在编码开始之前，尽可能多的考虑许多细节。您不要试图把细节问题都留到真正开始编码的时候。不要异想天开您可以事先解决所有的问题。除非您在一个非常安静的夜里工作。事先解决所有的细节问题几乎是不可能的，在任何情况下，当系统转化成成果并开始使用时，最终用户会发现一些他们原先没有考虑过的一些问题。这种事后发现很正常，任何设计方法都不能应付后来发生的变化，从而使设计方法在大多数的编程环境下显得很脆弱。事实上，随后，当设计和执行时，问题的定义会发生变化。这就是为什么在设计队伍中要有一个最终用户的原因：以确保您不会因为改善它们而破坏一些事情。

虽然如此，最初的问题定义应尽可能的详尽。将您所想的都写在纸上。不要遗漏任何细节，即便是最简单的。教导成员通过他们的 OO 设计是我赖以生计之一，但往往他们在已经开始设计时，才邀请我。通常，我让我的客户给我一份他们的设计文件，这样我就可以为我们的第一次会谈做准备，而且是多多益善。我被告知我必须去并且发言以使我充分地理解问题是什么。那些答复回给我一些提醒。如果问题没有被详尽的写下来而一个外行人可以通过阅读而理解问题，那么我就知道客户对问题的理解可能不足以开始设计。尽管我相信“分析失败”的存在，但我从来没有看见过；相反，我曾看见过与之相反的：程序员过早地进入编码状态，而没有进行充分地分析。

回过头来看一下初始的问题说明书，您会注意到我还没有在任何地方提到计算机。我现在正在描述问题，而不是计算机程序。这里，问题是养育，而不是银行。因此，说明书是写给父母读的，不是银行家。

同时也要注意使用相对简单的句子。避免使用被动语态以及深奥晦涩的学术性文体，这对于用户来说简直是灾难。简单句（主语/谓语/直接宾语）确定了行为的主体（主语），执行的动作（谓语）和请求动作的信息接受者（直接宾语）。而被动语态只涉及了这三部分中的后两部分。无论什么时候用“我”、“您”等有意义时，就那么做。

结束语
这个月就到这里。我们已经开了个头 -- 确定了基本问题。下个月，我将继续这个讲座，细化问题说明书，使它能有效地使用。

参考资料

  • 请阅读 IEEE Software 2000年3月/4月期刊中的有关“文书工作进展中的困惑”的社论。这篇社论"Cargo Cult Software Engineering"，由 Steve McConnell 写。

  • 了解更多有关 UI 设计，请参阅About Face: The Essentials of User Interface Design由 Alan Cooper 写（ IDG 出版社）。与此巧合的是这本有关 UI 设计的书作者也是“Visual Basic 之父”--该工具是造成许多最差的用户界面的原因从而引起计算机界的混乱的作者。Cooper 有许多观点，其中一些我赞成，还有一些我不赞成。（特别是我认为作为 UI 的程序不应有智能方面的瑕疵而使程序挂起）。尽管如此，Cooper 仍然有许多好的见解，使人感兴趣。

    我发现大多数有关 UI 设计的书籍是由程序员（他们把程序员的便利置于用户需求之上）或者美工（他们把艺术、效果和创意置于用户需求之上）。无论哪一种情况，用户都被抛弃了。所以，让人喜欢读的有关 UI 书是由有见识的程序员著作的，他不但坚持把用户放在第一位，而且有良好的设计意识。

    Cooper Design 网站，从 Cooper Design "Manifesto"：事实上，今天大多数软件都有类似于这样的界面：
    错误屏幕

关于作者
Allen Holub 自1979年一直在计算机领域工作。他在许多杂志上发表过文章（ Dr. Dobb's 月刊, 程序员月刊, Byte, MSJ，以及其它杂志），并且他是在线杂志JavaWorld的撰稿编辑。他有八本自己较赞赏的书，其中最新的一本（Taming Java Threads）讲述了 Java 线程的陷阱和缺陷。他一直从事设计和构建面向对象的软件很长时间了。在当了 8 年 C++ 程序员之后，Allen 在 1996 年初放弃了 C++ 转向 Java 编程。他自从1982年一直在自己和为加利福利亚大学伯克利分校教编程（先是 C，然后是 C++ 和 MFC，现在教 OO 设计和 Java 编程）。Allen 提供 Java 技术和面向对象方面公共课程和专门培训。他还进行面向对象设计咨询和商业性 Java 编程。请访问 Allen 的网站 www.holub.com。
您对这篇文章的看法如何？

真棒！ 好文章 一般，尚可 需提高 太差！


细化问题定义

dW 中国站点 : 组件技术概览 : 文档库 - 论文
英文原文

OO 设计过程：细化问题定义
关键是直接付款

Allen Holub
撰稿编辑，JavaWorld
2000 年 9 月

内容：
验证和细化
不要忽略明显的错误
倾听
参考资料
关于作者

这个月我将继续细化上个月开始的关于 Bank of Allen 的教育软件的问题说明书。

当开始问题定义时，我通常是做下一步，建立一个词汇表。一般来说，对于熟悉该领域的人会马上明白术语的含义，因而不需要去定义它，但对于具有模糊含义的术语，或者在目前的问题中用在有限的范围内，这就需要专门来定义它。

在问题说明书中如 gotcha 是不妥当的。一个词不能指两个不同的事物，同时两个不同的词不能指同一件事物。要做到这一点，首先要建立词汇表。不需要定义该问题领域的术语。譬如，如果是会计方面的问题，您勿须定义信用或者利率。然而，目前的问题是关于养育，所以象信用之类的术语需要精确定义。

词汇表对我们的主要目标也有帮助，它能确定关键问题层次的抽象。避免词汇表中的词和概念经常出现在问题说明书的正文中。譬如，存折引入了术语交易。（交易的概念是重要的，虽然我没有立即认识到这一点。）

在词汇表中加入实现细节部分也是很有帮助的，它提供了记录这些细节的地方，就其本身而言，尽管它不是问题说明书的都具有的。那些通常归入功能性需求规范的东西常被放入实现细节。

以下是我的第一遍词汇表和实现细节。

词汇表
余额 | 当前在帐户中的货币金额。
银行 | 父母
贷方 | 由银行存入您的帐户，而不是您自己。
存款 | 将钱放入银行；增加您的余额。
利息 | 作为您一直把钱放入银行的报酬而给您的信用。利息是每天计算的，通过把信用按当前帐户余额的固定比例放入该帐户（“本金”）。如果利率是 10%/月，余额乘以 (.10/30) 就得到每天的利息。
存折 | 发生在某个帐户上的交易的作业记录和摘要信息。孩子们必须从银行得到存折。
取款 | 从银行取钱；降低您的余额。

实现细节
就其本身而言，这部分不是问题说明书的一部分。它只不过是我想起的清单，我把它与问题说明书放在一起。

  • 当程序第一次运行时，父母可以为他们自己设置口令。从那以后，以父母亲登录必须要知道口令。父母亲在任何时候可以更改口令。
  • 更改利息率可以立即生效，但根本不影响以前的余额。（存折不一定（但可以）反映新的利率，该利率用于利率更改之日的存款，甚至是那天以前的。）
  • 小孩必须向银行申请存折。
  • 每次在给孩子们之前，存折要更新。
  • 父母必须要有备份或银行的措施。这可以是简单文件拷贝或更加详细描述的说明。

验证和细化问题说明书
如果满意自己准备的合理的问题说明书，那么下一步做设计审查。我感激我的妻子 DJ，她是程序员并且是该方面的专家--母亲。她发现了各种各样小的语法性问题（我必须承认，你刚才所读的最初的陈述是我修订的），而且她发现了一个我没有意识到的严重缺陷。“你不能象那样计算利息”她指出。如果你仅仅根据一个月的天数来计算每月的利息，但是每天计算利息过于复杂，到月底你会得到一个很大的数目这要比你到月底再结算一次利息多的多。你在此所需要的是名义的每月低于5%的利率，它将向你提供每月5%的有效利率。好吧。回到正文。

在当前问题中，出现了两个领域：养育，当然，从较小范围的角度讲的融资。我原以为我对费用了解的很多，但是很显然我错了。所以，我停下了设计，拣起了书本直到我完全了解了这方面的知识再继续设计下去。

这个问题本该出现得更早一些，如果我没有省略掉设计所必须的第二个步骤：在你对问题所属领域非常熟悉之后（第一阶段）和在开始建模之前（第三阶段），你必须与客户进行交谈以发现他们所需要的。我认为我不需要这样做，因为我已经是这方面的专家了-作为一个父亲。我错了。如果我与这方面的专家会谈过，一些并没有象我一样进入到设计的实质的人（例如我的妻子），在第一部分的利息问题上，我就可能做对。

不要忽略明显的错误
这儿有很多其他的例子，但最重要的是我称之为“浴室效应。”这一词干来源于几年以前我写给“ Dr. Dobb's Journal”一篇（未发表）文章开头的部分。“假设我们建造房子象我们做软件一样。” （我们都居住在如Winchester Mystery House一样的破房子里）。总之，到了文章的结尾，最后购房者第一次来看房子（在房子已经完全造好之后）。购房者带着迷惑的表情四处看了一圈，问“浴室在哪里？”

“浴室？”分包商说，“在说明当中没有浴室。现在把他们加上去会花很多钱；我们得浇注水泥，敲开墙。我们为什么不在房屋的后院盖一个呢？”

"这是什么意思‘它不在说明当中是什么意思’？”购房者结结巴巴的说，“谁会傻到设计房子而没有浴室？”

但是在软件设计中却是经常发生的问题。用户倒是不经常提及他需要的东西，而这些东西对问题来说都是很基本的。不是用户“对我隐瞒”正如许多程序员所说。这是因为设计者对问题所涉及的领域不够了解而提不出正确的问题。

不要仅仅点头 -- 倾听
当进行会谈的时候，倾听！不要打断。记住会谈中出现的每一件事情（那种会速记的人获益非浅），准确的记录下此领域专家的每一句话都是非常重要的。我曾经参加过许多会议，用户们说了几个小时，没有谁记录。随后，当用户离开后，没有谁能精确地记住讨论了些什么。在一部分程序员当中有一个不好的倾向，对用户所说的不加理会。都知道程序员们将用户对于问题是什么的描述扭曲为程序员认为问题该象什么的描述。他们将重新描述一些事情，将那些组成的问题涉及的领域翻译成更熟悉的语句，否则，他们所听到的就会产生扭曲和变化。经常，记住的是程序员们对问题的错误理解，而不是此领域的专家对问题的实际描述。

最后，记住大多数的用户都有过与那种自以为是的计算机人员相处很长的历史，那些人认为对于任何问题，他们比用户懂得更多。 （由于认不清方向，城里人对农夫说，“你不是很聪明，是吗？”农夫回答道，“也许，但我没有迷路。”）问题是许多用户过了一会儿就放弃了。如果程序员不认真倾听，为什么在交谈中就会有障碍？你将不得不打破这一系列的障碍。

一般而言，对于问题说明书的主要问题根本在于细节的级别是否属于它。通常，在问题实际上所涉及的领域当中，问题说明书的本身不会是科技术语。你得为了这个目标去参考教科书或字典。但在这，答案是“是的”因为固定利率和有效利率的区别在于他们不是实际问题领域的一部分（这是养育）。我们领域的专家是父母或会计师们。我们并不是发展一个运用于银行或建立一个试图运用于真正银行的模块的系统。这仅仅是为孩子们写的软件，不是关键的银行系统，所以我们不能采取我们部分专家关于如何计算利息的建议。利息的计算是问题定义的一个重要的部分，所以事实上它属于问题说明书。

问题说明书重点放在手边的问题是十分重要的。我们并不想在一段孩子的软件中解决“银行”的通病。在另一方面，我们也不想遗漏任何重要的细节。如果一般的读者（假设他们都是此领域的专家）需要一些信息以了解问题的陈述，然后就属于他们。如果这些资料是这个领域当中重要的一部分，我们不会加上去。在现在的情况下，如果我真正在写一个银行系统，我当然也就不会为定义“有效利率”犯愁了。因为我可以向这方面领域的专家请教这些知识。（由于同样的原因，设计者必须对问题所涉及的领域和此领域的基本术语有足够的了解。你不能设计一个会计应用程序除非你懂会计的一些知识-至少是一个聪明的外行。）

所以我找出一本傻瓜金融书，查出名义利息，并修改利息的定义为:

利息 作为您一直在银行存钱的报酬而给您的信用。利息被加入每天的余额中。

Bank of Allen 的运作如真正的银行一样，利息按照月份来计算。尽管如此，每天挣得的利息必须登记在存折上。这里的问题是根据每天计算的利息与每月计算的利息会产生不同的结果。

譬如，如果您开始在银行有 $100，利率是 10%，按照每 10 天计算的复利，在 10 天后，您帐户上将有 $110。

而另一方面，如果根据每天的复利计算，也就是将 10% 的利率平摊到每一天（1%/天）来计算，得到的结果会大于按每 10 天计算的结果。
第零天 | 本金 | $100.00
 | 利息 | $1.00
第一天 | 本金+利息 | $101.00
 | 利息 | $1.01
第二天 | 本金+利息 | $102.01
 | 利息 | $1.02
第三天 | 本金+利息 | $103.03
 | 利息 | $1.03
第四天 | 本金+利息 | $104.06
 | 利息 | $1.04
第五天 | 本金+利息 | $105.10
 | 利息 | $1.05
第六天 | 本金+利息 | $106.15
 | 利息 | $1.06
第七天 | 本金+利息 | $107.21
 | 利息 | $1.07
第八天 | 本金+利息 | $108.28
 | 利息 | $1.08
第九天 | 本金+利息 | $109.36
 | 利息 | $1.09
第十天 | 本金+利息 | $110.45

需要用两种利率解决这个问题：一种是孩子们知道的，另一种是银行内部用的。

有效利率
实际的利率，包含按复利计算的结果。假定月名义利率为5%，当按每日复利计算时，有效利率为5.12%。
名义利率
规定的利率，除去按复利计算的结果。

什么是有效利率，按每日复利计算，它是如何得到5%名义月利率？
N = 以小数表示的名义利率 (.05)
P = 周期的天数（30天）
E = 以小数表示的有效利率

公式：
E = (1 + N/P)P -1 = .0512

利率 5%，按每日复利计算，与按每月复利 5.12% 计算，30 天后帐户上的余额数目是相同的。

当按每日复利计算时，名义利率为多少，可以得到有效利率为5%/月？
N = 以小数表示的名义利率
P = 周期的天数（30天）
E = 以小数表示的有效利率 (.05)

公式：
N = P * (E + 1)1/P -1 = .0488

利率 4.88% 按每日复利计算，与按每月复利 5% 计算，30天后帐户上的余额数目是相同的。

在一个周期内，如果只计一次复利，那么有效利率与名义利率是相同的。即，如果一个月计算一次利息，那么月有效利率与月名义利率是相同的。

简单地讲，银行公布的是有效利率：“您的月利率为 10%。这意味着如果您在银行存 $10 一个月，银行付给您 $1 的利息。”

由于利息是按每日复利计算的，每天计算利息必须要用名义利率（这样到了月底，由有效利率技计算的利息才能与之相一致）。譬如，假定一个月有 30 天，公布（有效）月利率为 5%，计算使用的（名义）日利率为 4.88%。如果您的帐户上初始有 $10，运用以下算法，您帐户上的将有 $10.50：
  double balance = 10.00;
  int days_in_month = 30;
  double
effective_rate = ->05;
  double nominal_rate = days_in_month *

( Math.pow(effective_rate+1,

1.0/days_in_month)
                          -1);
  for
( int i = 0; i </ days_in_month; ++i )
      balance =
balance + balance *

(nominal_rate/days_in_month));

现在看上去更合理，下面我们该做还没有完成的事情。这正是下个月专栏的主题。

参考资料

  • 参阅上个月的 “OO 设计过程”专栏，开始设计软件."
  • 访问Winchester Mystery House 参阅任意设计的结果。

关于作者
Allen Holub 自1979年一直在计算机领域工作。他在许多杂志上发表过文章（ Dr. Dobb's 月刊, 程序员月刊, Byte, MSJ，以及其它杂志），并且他是在线杂志JavaWorld的撰稿编辑。他有八本自己较赞赏的书，其中最新的一本（Taming Java Threads）讲述了 Java 线程的陷阱和缺陷。他一直从事设计和构建面向对象的软件很长时间了。当了 8 年 C++ 程序员之后，Allen 在 1996 年初放弃了 C++ 转向 Java 编程。他自从1982年一直在自己和为加利福利亚大学伯克利分校教编程（先是 C，然后是 C++ 和 MFC，现在教 OO 设计和 Java 编程）。Allen 提供 Java 技术和面向对象方面公共课程和专门培训。他还进行面向对象设计咨询和商业性 Java 编程。请访问 Allen 的网站 www.holub.com。

"Bank of Allen" 是 Allen I. Holub 的一个商标。本程序及相关设计，是 (c) 2000, Allen I. Holub. 版权所有，保留所有权利。

您对这篇文章的看法如何？

真棒！ 好文章 一般，尚可 需提高 太差！


OO 设计过程：验证分析

dW 中国站点 : 组件技术概览 : 文档库 - 论文

英文原文

OO 设计过程：验证分析
原型与模型的差异以及二者的重要性

Allen Holub
首席技术官，NetReliance
2000 年 12 月

内容：
修改
模拟解决方案
提交模型
无用功能
用户、市场营销和销售
这就是模型
参考资料
关于作者

既然我们已经定义并细化了问题陈述，现在要为我们的教育软件构建一个模型。

本文是 OO 设计过程系列中的第四篇文章。前三篇是：

  • 入门
  • 开始设计软件
  • 细化问题定义

这个月，我将通过创建一个模型来从问题陈述阶段转向解决方案阶段，并将该模型部署到用户社区（我 7 岁大的儿子）。

修改
在纸上定义好问题之后，下一步是确保问题定义是正确的。可以使用几种方法来这样做（模型、UI 原型以及其它方法），我将在本月和下个月分别讲解。请记住，正如您阅读这些文章所感觉的那样，杂志上的文章一般都是连续的，但实际上，这些活动中的很多活动都并行发生。例如，我在创建 UI 的同时还在细化模型；我将使用通过用例分析发现的信息来改进 UI，并且，在进行 UI 方面工作的同时，还会找出用例分析中的缺陷。同样，当开始进行模型方面的工作时，我还会不可避免地发现初始问题陈述中的缺陷，并回过头来修正它们。设计过程不是一种有序的、循序渐进的过程，您不能在进行到下一步之前完成上一步的所有工作。而且，还要经常重新修改那些您认为已经完成了的工作，并用在目前正在进行的工作中发现的信息来更新它。

经常性的反复修改正是设计过程的一部分，并且必须这样做。对于设计文档来说，变成“过时的”实在是很容易，因为它不是象这样来更新的。对于在一年之后才能看到代码的程序维护人员来说，设计文档是必需的。虽然我认为，只要仔细编写代码（仔细选择变量和方法名，具有良好结构等等），代码本身也可以提供足够的文档信息，但是我不同意 Kent Beck 和那些“极端编程”人员的看法：不应该费力去使设计文档与实际代码保持同步，因为繁忙的程序员没时间这样做。

既然设计过程如此不稳定，我喜欢尽可能多地将它记录下来。Stravinsky 在创作 Rite of Spring 时将整篇乐谱放在公寓的墙上，以便可以从总体上观看作品。对于软件设计，这也是个好主意。

例如，我不喜欢使用 OO CASE 工具，因为它将设计文档隐藏在计算机内部，我看不到它们，我更喜欢用大量的白板，并在上面留下足够大的空间，以便随时使用。50 码长的白色简易纸卷也是不错的选择。只有在设计过程变得相对稳定时，我才会考虑将设计移至 CASE 工具中进行，并且，我不会使用任何不允许轻易打印出整个设计的 CASE 工具，因为，那样我就无法将设计样图粘回到墙上了。

模拟解决方案
“修改”过程中的第一步是确保问题是值得解决的。我通常用模型来回答这个问题。

“原型”和“模型”之间有一个重要区别。以航空业为例，波音 777“原型”是一架功能齐全的飞机。除了在设计过程完成之后会有新机型取代它之外，它就是波音公司出售的飞机。实际上，原型是一个被部分构建的程序，而不是在测试之后即被丢弃的废品。与波音 777 一样，您使用原型程序作为设计思路的试验台。如果设计思路有效，则在原型中保留该思路。也就是说，原型是最终程序的部分构建版本（通常是最终程序的一部分）。必须仔细设计和组织原型；这是您设计的成品程序。这种类型的开发－逐步细化原型，直到生产出成品为止－通常称为“递增式细化”。

但在目前，我不需要原型。我需要的是模型。我想看看，为孩子设计的银行的总体思路是否有意义；我还不想构建实际的应用。模型是用管道粘胶、口香糖、纸夹和油漆等制成的试验品，用于测试那些目前还不想实现的概念。波音公司可能也模拟了 777 坐舱，以了解是否可以从驾驶员座椅触及所有的开关。他们可能用胶合板和其它现有飞机的座椅进行试验。相反，原型坐舱的大小与实际坐舱相同，有完整的电子线路，其中有真正的设备、座椅和开关。可能会将它挂在计算机飞行模拟器、而不是一架飞机上，但是，如果愿意的话，也可以将它放在飞机中。

原型的另一个特点是，它为在设计期间出现的重要问题提供答案。例如，通常回答“是否有足够的带宽来做 X？”这样的问题的唯一答案就是模拟做 "X" 那部分程序的原型（或者至少是与网络相关的程序部分）。如果不创建实际代码（也就是说，如果以人工方式模拟问题），实际上就回答不了这个问题。实际版本与模拟版本的行为可能不同。

因此，我在 Excel 中为“阿伦银行”实现了一个模型。在模型中，银行只是一个简单的电子表格，每一行记录一天的交易。可以通过在电子表格的适当单元内输入存款额来存款，并可以在另一个单元中看到当前余额和累计利息。对于实际的程序来说，这可能是一个糟糕的 UI，但是，这当然只是一个模型而已。

将模型提交给实际用户
然后，我使用这个模型在我 7 岁的儿子菲利普身上尝试这个概念。试验获得了惊人的成功。首先，菲利普自己想出了复利的概念。

我说：“那么，在这个月末，你就可以取出最先存入的美元，还有银行为保存你的钱而付给你的五分硬币。”

他说：“那么，下个月，我得到的钱要超过五分硬币，因为我现在在银行中有更多的钱，是吗？”

很明显，他喜欢这种不工作就可以赚钱的主意。证据就是，在几个月使用 Excel 模型的试验期内，菲利普没有取出一分钱。太棒了。

这个模型确实反映出有两处需要改进。事实上，菲利普经常询问他的帐户里还有多少钱。模型过程的一个具体效果就是如下修改“问题陈述”，以反映新的需求：

重要的一点是：孩子们应该能看到每天的利息累计，每月发布一次利息信息是不够的。因此，银行每天都要计算复利，并且每天发布复利信息。存折要显示每天的发布信息，即使那天没有其它交易也要这样做。这样，即使没有存款，孩子们也可以看到利息是如何累计的。存折中还突出显示存款期间获得的总利息。

第二个问题是在模型使用了一段时间之后才出现的。菲利普最终取出一笔钱去买一张他看中的 Pokemon 卡，但是他还缺大概一美元。“就像银行一样”，我答应借给他这些钱。也就是说，我将按他存款利息的两倍来收取他的贷款利息。这种额外的损失足以使他放弃取款（因此，我教他如何省钱的主要目的确实达到了），但是，我确实认为，将来有贷款的能力将很方便。因此，需要再次向问题陈述中添加内容：

孩子应该可以从银行借钱，但是这个过程也应该模拟实际的银行。贷款利率应该足够高（例如，是存款利率的两倍），以表明借钱是昂贵的，并且银行应该要求，每隔固定、相对短的时间（外面的银行是几个月一偿还）就偿还。孩子可以用存款来自动偿还。还是那样，因为练习的目的是要教孩子如何管钱，所以，银行必须可以为孩子显示贷款结算表，以使他们清楚地知道贷款要花费多少。另外，存折应该突出显示自动扣款，以显示减少的本金和所付的利息。如果未及时还款，父母将使银行对孩子施以罚款。

银行用“简单”利息的方法计算贷款利息－在每个还款期间，用当前未还清的本金乘以当前利率，从而计算出应付的利息。还款之后所剩的任何钱都可以用来减少本金。孩子应该可以在任何时候，通过在贷款帐户中进行额外付款（或比平时多付款）来减少本金。

计算还款的标准公式为：
rate /= 1200;  // Adjust rate to per-month
tmp = pow( 1.0 + rate, (double) number_of_periods );
return( (tmp * present_value * rate) / (tmp - 1.0) );

其中，rate 是年利率（即，如果年利率是 11.5%，rate 就是 11.5）。

无用功能
我之所以没有添加第二个功能是因为菲利普实际上没有贷款，因此，我无法确定是否需要实现贷款功能。实现那些用不到的特性是没有意义的。事实上，那是很多现有程序的一大问题：它们有大量可能需要的特性，但实际上，没有人使用这些特性。这种“无用功能”除了在每一方面都使程序更昂贵之外，不做任何事：花更多钱去构建，花更多时间去构建，并且，因为公司必须收回构造成本，它还会向用户收取更高的费用。

金玉良言是：

程序应该只实现那些最终用户实际使用的功能，不要多也不要少。

然而，总体设计应该足够灵活，以便以后可以轻易添加新特性。

至于目前这个程序，我决定先不添加贷款功能，以便尽快将程序推向市场。然而，我将尽快提出一个以后可以轻易添加该功能的设计方案。

用户、市场营销和销售
关于模型要指出的最后一点是：Jacob Nielse 曾写到（抱歉，我记不起其出处），为两名用户演示原型的效果是为一名用户演示的效果的两倍，但是为三名用户演示的效果只是为一名用户演示的效果的 2.5 倍。为三名以上的用户演示是浪费时间。

使一名最终用户实际成为设计小组的成员是必需的，这样，不仅能帮助进行产品定义，还可以在设计和实现的整个过程中不断提出反馈意见。有两名用户就更好了，但是，第二名用户不必在现场，只需检查所设计的原型和模型即可。事实上，如果第二名用户不十分熟悉整个设计过程可能更好，这样，他／她提出的意见才更新奇和更公正。

如果没有真正的最终用户，如果愿意，还可以使用市场营销（不是销售）部门中的人员（用户代表）。区分市场营销和销售是很重要的。一个好的市场营销部门的主要目的是调研。市场营销是试图想出现实世界中做实际工作的人们真正需要什么样的程序的过程。他们用调查、面谈等方法来实现这点。“商业发展”和市场营销的概念密不可分。实际上，正式的问题陈述是市场营销档案，但必须由市场营销和技术小组协作指定，以确保可以及时完成程序。传统的特性与推向市场的时间之间的权衡是市场营销的决定。

另一方面，销售部门的工作是销售现有产品。销售部门绝对没有规划商业运作的权利，并且永远不应该被允许直接与技术方联系。销售人员应该向市场营销人员建议新特性，然后，市场营销人员验证该特性对于用户社区是否确实有价值。如果这些特性确实有价值，市场营销人员将随后与技术方协作，以将该特性添加到设计中，并最终添加到程序中。顺便提一句，不管添加特性有多困难，程序员都必须添加由市场营销人员指定的特性。市场营销人员需要提出产品占据市场所需的最小特性集。如果没有那些特性集，产品就不会成功。不允许程序员仅仅因为添加一个特性不方便就怠工，并妨碍公司的成功。

当设计方向由销售人员、而不是市场营销人员决定时，就完全出错了－很多人都经历过这种错误。某个客户打电话来请求一个新特性，销售人员随后将之变成“我的天哪，我们昨天就应该有这个特性”的请求。然后，一些不幸的程序员将会被强制停止有用的工作，并着手添加这个新的特性。三天以后，同样的事情再度发生，然后，添加第一个特性的要求被取消，取而代之的是一些新的“现在就要做出来”的特性。最终结果是什么也完不成。如果奇迹般地生产出一个有功能的程序，这个程序可能不会做任何有用的事。仅仅因为一名客户要求某一特性就添加该特性或修改设计是完全错误的；那样做的时候，您正冒着将您的软件公司变成只有一名客户的客户软件作坊的风险。真正的问题是：这个特性会对更广泛的社区有用吗？这就是一个市场营销问题。

这就是模型
关于模型，已经讲了很多。下个月，我们将从用例分析和 UI 设计开始。

参考资料

  • Extreme Programming Explained: Embrace Change（阅读：Addison Wesley，2000；ISBN 0-201-61641-6）中描述了 Kent Beck 的“极端编程”。
  • 要想看到模型与原型的真正区别，请查看有关如何建造波音 777 的 PBS 文档：21st Century Jet: The Building of the 777。
  • Usability Engineering（San Francisco：Morgan Kaufmann Publishers，1993；ISBN 0-12-518406-9）中描述了 Jacob Nielsen 有关原型可用性的思想。

关于作者
Allen Holub 是 NetReliance 的首席技术官，该公司位于旧金山，它为在网络上执行可信任商业交易而构建安全的全局基础设计。

Allen Holub 自 1979 年一直在计算机领域工作。他在许多杂志上发表过文章（Dr. Dobb's 月刊、程序员月刊、Byte、MSJ 以及其它杂志）。他为在线杂志 JavaWorld 编写了 "Java Toolbox" 专栏，还为 IBM developerWorks 组件专栏 编写了“OO 设计过程”专栏。他还主持了 ITworld Programming Theory & Practice 讨论组。

Allen 有八本自己较赞赏的书，其中最新的一本讲述了 Java 线程的陷阱和缺陷（Taming Java Threads）。他从事设计和构建面向对象软件很长时间了（用 C++ 和 Java）。他自从 1982 年一直在为加利福利亚大学伯克利分校教 OO 设计和 Java。请访问 Allen 的网站 http://www.holub.com 来与他联系。

您对这篇文章的看法如何？

真棒！(5) 好文章 (4) 一般，尚可 (3) 需提高 (2) 太差！(1)


OO 设计过程：用例简介

IBM China Skip to main content
搜索帮助
IBM 主页 | 产品与服务 | 支持与下载 | 个性化服务

IBM : developerWorks 中国网站 : 组件技术 : 所有的文章 		developerWorks 中国网站
OO 设计过程：用例简介 英文原文
内容：
用例简介
表示用例
结束语
参考资料
关于作者
设计程序的动态（运行时）行为

Allen Holub
首席技术官，NetReliance
2001 年 1 月

在前几篇文章中，我们已经细化了问题说明，并且建立了教育软件的模型。在本文中，我们将研究用例分析。

本文继续探讨我的 OO 设计过程系列。前四部分是：

  • 入门
  • 开始设计软件
  • 细化问题定义
  • 验证分析

这个月，我将分析程序的动态（运行时）行为。为了进行分析，我将引入用例的概念，并且将讨论如何构建其中一个用例。下个月，我将把这个结构放到“艾伦银行”的框架中去。

用例简介
“问题说明”现在看似很好。我可能已经忘了一些关键性的东西，但傻坐着一点用也没有，不要指望这些丢失的东西会从我的脑袋里完完整整地蹦出来，就象雅典娜从宙斯的脑袋里出来一样。通过继续前进并以另一种方法研究同一个问题，这些丢失的东西会逐渐变得清晰。然后，下一步就是再一次研究问题，但这次是从动态（例如，运行时）行为的角度来进行研究。在某种意义上，问题说明是问题的静态定义 -- 它描述了用户必须解决的问题，但仅此而已。最后，我将以静态模型的形式来使这种系统的观点成为正式。

问题的动态定义关注的并不是必须做什么，而是如何去做。即，与其研究问题本身，还不如研究用户为解决问题而必须做的事。我们用来在动态行为上构建思想的主要工具就是用例分析。还可以以动态模型的形式来表示动态行为，但在那样做之前，我们必须确定动态行为是什么。我们将使用用例分析来做这些。

首先，定义：

用例是单个任务，能产生有用的结果，由系统最终用户执行。

“最终用户”和“结果”的定义可以多种多样 -- 例如，最终用户可能是自动化子系统，而结果的种类也可以随着用例的不同而有着根本上的不同。有时结果是实际产品，如报表，而有时结果是公司目标，如雇佣一个雇员。但结果始终有一个可以感知（由用户）的值。

可以认为用例是符合用户意图的。如果用户进入系统是为了做某些事，那么他要做“什么事”呢？例如，登录就不是完整的用例，因为没有人会仅仅想要登录而进入系统。您登录是为了实现某些其它更大一点的目标，要执行某些实用的任务，而用例正是那个更大任务的定义。

表示用例
正如软件中的常见情况，一个经过整理的半正式用例表示有助于您建立用例。正式的表示通常涉及表 1 中列出的几个部分。（我待会儿就会解释每个部分包含的内容。）最好将表 1 看作是一个检查表，而不是一个往里填补空白的表单。并不是所有部分都与每个定义有关，在实际工作文档中可以组合（或忽略）任何部分。

而且，虽然必须提供表 1 中的所有类别，但却无需按该表中指定的顺序提供它们。通常，最好是从方案开始，将它们开发成正式的工作流定义，然后填入其它细节。表 1 中的顺序实际上是为了方便用例的实施者，而不是创建人员。

我们设计人员在创建用例的过程中有几个主要目标：

  1. 完善对问题逐步深入的理解。
  2. 与最终用户交流，以确保我们真正在解决他们的问题。
  3. 动态地组织设计（和最终程序），以反映用户头脑中真正的商业模型。换句话说，确保程序真正能做一些对实际用户有用的事。
  4. 对实际开发过程提供指导说明和结构框架。（在几个月后我们开始讨论实现时会详细说明这一点。）

表 1. 正式用例表示的组成部分

名称
描述
期望结果
用户目标
参加者／角色
依赖性
前置条件
方案
工作流
后置条件
商业规则
需求
实现注意事项

用例定义是协作过程。这些组成部分中有许多是由商业方（通常是市场营销）组织指定的，而其它部分由技术方组织填写或扩充。用户界面设计也是用例分析的一个重要组成部分。UI 模型（不是原型）提供了许多关于是否已正确捕捉到用例有用的反馈信息。下个月我再讨论基于用例的 UI 设计。

现在，让我们展开表 1，详细地说明文档的各个部分。

名称
所有用例都应该有名称。Constantine 建议使用动名词，后跟一个直接宾语（例如：“取款 (withdrawing funds)”或“检查存折 (examining the passbook)”）。这种约定促使用例名称简明地标识所执行的操作以及受该操作影响的对象（或子系统）。我认为此建议就现状而言是好的，但如果您最终希望不遗漏重要信息，那么它对于恪守公式却没有特别好处。例如，“客户从活期帐户中取款 (customer withdrawing funds from checking account)”与“银行经理从客户的活期帐户中取款 (bank manager withdrawing funds from customer`s checking account)”就不是同一个用例。简单的“取款 (withdrawing funds)”这个名称就不能充分说明这一点。

名称应始终以用户为中心，而不是以系统为中心。例如，“存款”（以用户为中心）与“接受存款”（以系统为中心）相反。从用户的角度来命名，而不是以系统的角度命名。

不论是哪种格式，名称是至关重要的 -- 必须明确地标识用例，以便能够有效地讨论它。我通常还对每个用例指定某些易于管理的身份，这样就可以方便地从其它文档引用它。通常，我更喜欢使用非常一般的描述，再加上有层次结构的编号方案（例如，"Withdrawal: 1.1"）。

描述
描述用例实现什么。例如，当“取款”或“检查存折”时，用户要做什么。应详细说明，但不要描述用户如何使用计算机程序。

例如，银行客户在取款时会填写一张取款单，并向银行出纳员提供这张取款单。然后出纳员将此取款单交给银行主管人以获得批准。银行主管人检查帐户余额并正式批准，等等。请注意，在此论述中我从未提到过计算机程序、菜单、对话框等。与在这个层次上，这些实现细节都是无关的。（当然，虽然在开始编码之前仍需要明确的实现细节。但我们还没有开始呢。）

我对提到“系统”的分析级文档感到特别头疼。它也许是您在创建某种“系统”时遇到的问题，但它肯定不是您用户的问题，即不是在这里显得很重要的用户的问题。

期望结果
根据定义，用例应该具有有用的结果。必须执行某种有意义的工作。在此描述结果。结果也许是一个报表（在此情况下，应该包含一个报表外观的示例），一个事件或条件（现在雇员将得到医疗津贴），或者类似的东西，但必须有一个有用的结果。

用户目标
用户使用用例的真正目标是什么？请注意，目标与用例描述并不相同。如果“期望结果”部分描述了用户希望实现什么，那么“目标”部分就描述了用户为什么要得到这个结果。

知道用户目标可以从根本上影响用例的发展方向。借鉴一下 Alan Cooper 的例子，假设我们负责创建会议日程安排程序。让我们感兴趣的用例是“安排会议”。您的脑海中会立即浮现出日历和约会本的图像。但那种方式类似于 Microsoft Outlook -- 一个臃肿但难用的程序，它实际上不能为任何人做什么有用的事（艾伦，现在告诉我们你到底在想什么）。

那么，如何避免使我们的系统象 Outlook 那样呢？要考虑目标。每个人对于会议的期望是什么？我保证我们都有同一个目标：根本不要去。除此之外，我们的目标就是尽快离开，并使会议尽可能有结果。实现此目标的唯一方法是制订一个议程。因此，“安排会议”用例的第一步就是子用例“创建议程”。（马上就将详细讨论子用例。）而真正确定日期和时间就变得次要了。

参加者／角色
用例中的参加者不是自然用户，他们是可能与系统相关的自然用户的角色。例如，我们正在建立一个考勤卡授权系统，那么会立即想要两种角色：雇员（填写考勤卡的人）和经理（核准考勤卡的人）。事实上，同一个自然人在某一时刻扮演两个角色并不冲突，因为有两个逻辑参加者：雇员和经理。

UML 将这些角色定义成“参与者”。但是，我经过慎重考虑，决定不使用该术语。我认为，实际用户是扮演与系统相关的角色的参与者。参与者通常在系统外部，与系统无关。真正重要的是参与者扮演的角色。（并不是我一个人认为“参与者”并不是好的术语，见参考资料。）

关于参与者在系统外部的规则有一个特例。设想关于访问控制的问题：假设您正在鉴定一个以给定角色在程序中进行操作的参与者。因此，参与者出现在程序中，将参与者映射成角色才能使程序工作。

CRC 卡
对于每个角色，我们需要建立两段关键信息：
  1. 参与者扮演角色时的责任。例如，银行出纳员接受客户的存款和取款请求，接受银行主管人的取款批准。
  2. 参与者的协作者 -- 需要与其进行交流的角色。例如，银行出纳员既与客户协作，也和银行主管人协作，但主管人却从不与客户直接协作。

这些类别是 CRC（表示“类／责任／协作者”）卡的两个组成部分，它们用于某些 OO 设计方法，如 Kent Beck 的极端编程。（CRC 卡由 Beck 和 Ward Cunningham 开发，用于教学辅助；见参考资料。）

由于此信息与参加者紧密联系，而不是明确的用例（即，某个扮演特定角色的参与者也许要参与几个用例），因此有时将 CRC 卡当作根据个别用例引用的单独文档进行维护就显得很实用。由于我们将进行 UML 动态建模，因此维护责任列表将会显得非常重要。

依赖性
依赖性关系有时并不存在 -- 但当一个程序相当复杂，且有多个用例时，那种情况就不多见了。通常依赖关系不会立即显现，但会随着用例模型和相关用户界面的进化而逐渐显露。

典型的依赖关系包括以下一个或多个关系：

  1. 子集／组合
    通常在开始分析顶级用例并发现可以通过执行几个较小但独立的任务来实现复杂任务时，会出现子集用例（我称作 subcode）。这些独立任务中的每一个都是主用例的一个子集。无论使用“分解成子集”还是“组合”，实际上这只是开始时的一种方式。如果您从较小的用例入手，然后意识到可以将它们组合成较大的用例，那么就使用“组合”。如果从较大的用例入手，然后要分解它，那么请使用“分解成子集”。例如，虽然标识自己（登录）通常不是本意想做的用例（您不会仅仅为了要标识自己而进入系统，然后关机、回家），但它可能是其它用例的子用例。而且，在所有需要标识的用例中，标识过程也许是相同的。应当创建“标识自己”子用例并将它指定成类似于独立用例，然后通过在主用例中进行引用来合并它。
  2. 使用／被使用（包括）
    这一关系非常类似于子用例关系；不要浪费许多时间来担心“使用”和“子用例”关系中哪个更好，因为在使用用例文档时这两种关系的处理方式是相同的。“子集”和“使用”之间的主要区别是当用例既是子用例也是独立用例时应用“使用”关系。（它定义了一个产生有用结果的独立操作。）子集型关系有时用于区别由另一个顶级用例使用的子用例，而“使用”关系也许描述了一个在所有场合使用的子用例。
  3. 前／后
    这一关系建立了用例之间的工作流。例如，“注册客户”必须在“指定订单”或“浏览商品目录”之前。
  4. 需求
    前／后关系表示顺序，但不是依赖性。即，“购买购物车中的商品”用例必须要使用“注册用户”，但“注册用户”只是在“浏览商品目录”之前 -- 它并不是一个必要条件。
  5. 扩展／专属
    如果用例 B 扩展了用例 A（即，添加子任务、操作等），那么 B 就是 A 的一种特例。（通常，需要额外任务是为了满足某些普通用例中不会出现的特殊需求。）例如，“标识经理”可能属于“标识雇员”的一种特例，因为经理必须被鉴定拥有比普通雇员更高的安全性标准。（程序员将这种专属关系称作派生，他会说 B“源自”A 来表示 B 是 A 的特例。）
  6. 类似
    通常，您会注意到工作流中的两个用例相互之间很相似，尽管它们之间还有一些小差异。类似关系表示您应该密切关注相似的用例，尝试查找共同点，它们可以被分解成“子集”用例或等价物。
  7. 等价
    从用户的角度看，两个用例可能不同，但它们的实现方式却相同（在“艾伦银行”示例中，等价用例之间的逻辑区别）。基本代码是否相同与用户并无关系 -- 存款和取款就是不同的逻辑操作。在任何事件中，等价用例在是设计过程中都有分路。

我发现使用 UML 静态模型图表示这些依赖关系非常有用。（换句话说，UML 的正式用例标记法几乎毫无价值。）Constantine 和 Lockwood 已经提议用图表表示用例关系的表示法，但我更喜欢使用标准 UML，当暂时没有与现成表示法可以使用时，标准 UML 会使用固定形式。以后我会给出一个具体示例（在“艾伦银行”项目中）。

前置条件
当运行用例时，关于系统的状态，您会做出什么假设？例如，用户在取款之前必须在银行有一个帐户。因此，必须先执行“客户开设帐户”用例，然后才能执行“客户取款”用例。

在可以成功完成此用例之前，必须存在什么条件？条件可以是内部的，也可以是外部的。例如：帐户余额必须大于取款额。

方案
方案是关于某人使用用例的简短叙述性描述。应从旁观者的角度去描述：作为观察事件发生的旁观者描述发生了什么。

既然是描述，我尝试尽量使方案抽象（讨论如何使用银行，而不是模拟银行的计算机程序）。“Philip 需要取款来购买食品。他从衣柜最上层抽屉里的一大堆脏袜子下面翻出存折，发现存款余额足够满足他的需要，然后前往银行……”

某些程序员认为方案毫无价值，因而不使用方案，但我却发现它们非常有用。最重要的用途是理清我的思路。通常，当完成方案时，我会发现未曾想到过的用例，或者我会发现未曾出现过的工作流问题。例如，其实在取款之前就会发生一些问题。（要是他不能找到存折，该怎么办？）

设想一个可能有几个有关方案的用例。在最近由我主持的 OO 设计专题学术讨论会上，我们选择 Vanderbilt 大学课程登记系统作为样本项目。其中只有一个高级用例，即：“课程登记”。可是，在这个用例中，我们想到了几种方案：

  1. 我坐下，报名参加我要学的所有课程，加入这些班级，我很满意。这种方案 -- 一切畅通无阻 -- 称作（至少我这么叫）“一路顺风”方案。
  2. 我报名学习要学的课程，但有一个班已经满了，因此我被放入了候补名单。以后，这个班有了一个空名额，我就被自动登记，并会通知我参加该班的学习。
  3. 与第 (2) 种方案相同，但我一定要学习此课程才能毕业 -- 我必须加入这个班。
  4. 其它方案

在分析这些方案时，我会判定第二和第三种方案实际上包含了一个与“一路顺风”方案截然不同的独立用例。

请注意，某些方案是失败方案，但不应该将这组方案看作只包含了一个“一路顺风”方案和许多失败方案。在课程登记示例中，一个真正的失败方案是您必须学习某个课程才能毕业，但却不能加入这个班。

实际上，在大多数已正确完成的用例中没有失败的方式！起先，这种惊人的论述并没有如预料中那样令人震惊。大多数计算机程序采用错误消息作为掩盖程序员偷懒的方式。实际上，大多数“错误”可能由程序本身就能处理。然而由于程序员懒得寻找处理错误的方式，因此大多数情况下会打印错误消息，这样他们就将这项工作推给了用户。以“班级已满”为例。懒惰的设计人员会说“哦，您要参加的班级已经满了。太可惜了。我无能为力”，并且会将这种情况归结为失败的方案：而出色的设计人员则会寻求解决方案，如我们的候补名单策略。作为设计人员就应该解决用户的问题，而不是把问题再还给用户。Alan Cooper 甚至说精心编写的程序根本不应该打印错误消息。我不敢肯定这个目标是否能够实现，但我敢打赌您可以消除大多数程序中 90% 的错误消息，最终会得到一个更便于使用的系统。

另外，对于程序员，请注意在用例中根本不要出现严重的致命错误 -- 这种错误会导致抛出异常。这些条件表示程序的失败方式 -- 它们与实现相关，但与问题领域无关，因此它们不属于用例分析。请记住，我们正在从用户的角度定义要解决的问题，我们不是在设计计算机程序（还没到时候）。设计程序要等到我们完全理解了问题以后再进行。异常抛出错误条件实际上是实现需求（见以下部分），应该在“需求”或“实现注意事项”部分中列出。

不要将上述的讨论作为借口而不考虑失败条件。您的目标是考虑所有可能的失败条件，然后确保每次出现失败条件时，它都会得到妥善处理。创建一个列出所有失败方式的工作文档不会有什么坏处。然后当复查用例以确保已经考虑了所有失败方式时，可以使用该列表作为检验表。

最后一个注意事项，销售人员在总结陈述时会发现方案确实有用。能卖出您正在构建的产品总是好事。

工作流
通常，用例描述足够描述一个简单用例中的工作流（“执行 A，然后执行 B，然后执行 C”）。有时工作流非常复杂，因此不应在“描述”部分中完整地描述它，而有时需要图表（如 UML 工作流图表，我会留在最后讨论）来明确定义工作流。在此填写此类信息。

后置条件
用例常常会更改系统状态（例如，现在帐户余额下降了），而不是生成实际产品。另外，需要在某些用例之后执行其它用例以便完成整个任务。此类信息都应填写在“后置条件”部分中。即，可以在用例完成之后检查后置条件，以便确定用例是成功还是失败。后置条件示例：新的余额就是老的余额扣除取款金额。

商业规则
商业规则是商家建立的策略，它可能会实现用例的结果。例如，“您每周取款数额可能不超过 20 美圆。”用例描述中最好不要包含这些规则，但应该以某种方式指定这些规则，以便使文档足够正确而能够使用。我发现最好在一个单独的文档中指定这些规则，然后在“商业规则”部分中引用这些个别的适当规则。

需求
需求通常是客户指定的关于系统实现的行为约束。（“必须支持每分钟 10,000 次交易。”）再次声明，最好在一个独立文档中指定并引用需求。

请注意，某些称作“需求”的东西其实并不真是“需求”。UI 设计通常是作为“需求”指定的，但真正的需求通常不是程序的外观如何，而是它做什么。如果实际用户不在意程序是否使用肾形嵌套紫红色按钮，那么肯定没有对使用肾形嵌套紫红色按钮的“需求”，不论您多喜欢这种按钮。（换句话说，如果用户在意，那么这就是需求，不论有多小。就是这样。）

我曾经有一个客户，他常说“用户需要[某些功能]。”我的回答是“哪些用户？告诉我他们的名字，这样我可以给他们打电话，和他们进行讨论。”由于实际上不存在这种用户，因此很容易忽视这些需求。这并不是说设计人员有时会不考虑有用的功能，但除非有一个真正的用户关心您的创新，否则它们就不切实际。要迫使您的用户们接受某些糟糕的创新很容易，但这些创新什么也干不了，只会在用户使用程序时给他们添麻烦。应拒绝这种诱惑。

了解需求是否有效的一个好的测试是拒绝所有在设计过程的自然产品之前指定的所谓需求（UI 观感、程序结构等）。这种“需求”只是错误的下意识设计。从需求文档中除去此垃圾。如果您有训练有素的商业开发队伍，那么从一开始在此文档中就不会有这些“需求”。

最后请注意，OO 设计人员不可能从“功能”列表着手进行工作。功能列表可能会是很长但又紊乱的未经细心考虑的构思集合，这些构思是某些客户未经大脑考虑而向销售人员建议的。然后销售人员会把此建议转换成“哦，天啊，我们现在必须设计这个功能！！！”，然后我们就这样做了。他们最多不过是建议了研究方向，但他们自己也不知道是否正确。您感兴趣的是为什么会提出这些功能。这种特定功能如何使用户的工作变得更简单？用户想要实现什么？您的其他客户是否想要实现相同的事？是否有更好的方法可以实现目标？最重要的是用户要执行的任务是否是我们以前没有遇到过的用例？

实现注意事项
尽管我们的目标是使用例文档尽可能位于问题领域中，但在完成方案和工作流时想到实现细节也是很自然的。当它出现在您的脑海中时，应该捕捉此信息，那么这一刻片段可以让您草草记下实现注意事项。这些注意事项并不具体。它们并不是实现规范。实际上，它们是将会影响实现以及与当前用例有关的细节。它们将指引实现级设计，但不会控制此设计。

结束语
因此，这就是用例的建立过程。下个月，我将为“艾伦银行”项目开发一些用例，从而向您演示如何应用此理论。以后的文章还将演示如何使用 UI 设计的用例，以及如何根据用例构建正式的动态模型。

参考资料

  • Ivar Jacobson，Object-Oriented Software Engineering : A Use Case Driven Approach (Reading: Addison-Wesley, 1994; ISBN: 0201544350)。此书是最早谈到用例的书之一。虽然 Jacobson 是此领域中的先驱，但我和他在诸如用例颗粒度等事物的看法上还是有不同意见。可我还是列出了这本书，因为它是很基础的，但对我来说有写抽象。
  • Larry Constantine 和 Lucy Lockwood，Software for Use (Reading: Addison-Wesley, 1999; ISBN: 0201924781)。此书描述了基于用例的 UI 设计方法，称作以用法为中心的设计。这种方法是最好的正式 UI 设计方法之一，此书的前四分之一收集了很好的用例讨论。
  • Larry Constantine 和 Lucy Lockwood，Structure and Style in Use Cases for User Interface。此书概述并扩充了 Constantine 和 Lockwood 的著作中讨论的设计方法。它是关于该主题的一个很好的摘要介绍。
  • Russell C. Bjork，An Example of Object-Oriented Design: An ATM Simulation。这些都是 Bjork 博士在 Gordon College 教书时出色的课堂笔记。虽然我对某些资料不太满意，但 Bjork 确实介绍了一套完整的源于 OO 设计过程的设计方法（包括用例）。
  • Kent Beck 和 Ward Cunningham，A Laboratory For Teaching Object-Oriented Thinking。（摘自 OOPSLA'89 Conference Proceedings October 1-6, 1989, New Orleans, Louisiana，和 SIGPLAN Notices Volume 24, Number 10, October 1989 专刊。）此书描述了在教室环境中 CRC 卡的最初用途。

关于作者
Allen Holub 是 NetReliance 的首席技术官，该公司位于旧金山，它为在网络上执行可信任商业交易而构建安全的全局基础设计。

他自 1979 年以来一直在计算机领域工作，曾在杂志（Dr. Dobb 月刊、程序员月刊、Byte、MSJ 以及其它杂志）上发表过大量文章。它为在线杂志 JavaWorld 撰写过 "Java Toolbox" 专栏，还为 IBM developerWorks 组件专区撰写过“OO 设计过程”专栏。他还主持 ITworld Programming Theory & Practice 讨论组。

Allen 有八本书广受好评，其中最新的一本讲述了 Java 线程的陷阱和缺陷 (Taming Java Threads)。他从事设计和构建面向对象软件很长时间了（用 C++ 和 Java）。他自从 1982 年一直在加利福利亚大学伯克利分校讲授 OO 设计和 Java。可以访问 Allen 的网站 http://www.holub.com 与他取得联系。

到页首
您对这篇文章的看法如何？
真棒！ 好文章 一般，尚可 需提高 太差！


OO 设计过程：用例规划

IBM : developerWorks 中国网站 : 组件技术 : 所有的文章 		developerWorks 中国网站
OO 设计过程：用例计划 英文原文
内容：
创建用例
参考资料
关于作者
对本文的评价
相关内容：
用例，简介
应用的用例，第 1 部分
设计程序的动态（运行时）行为

Allen Holub (allen@holub.com)
首席技术官，NetReliance
2001 年 3 月

在上一篇专栏文章中，我介绍了正式的用例演示的概念。本专栏文章的后几个部分将讨论一些用例。它们是一个复杂的主题并涵盖很多资料，因此我们要花一些时间彻底地考虑这些问题。本月我们关注的是：如何确定该用哪些用例，以及在计划用例时应该考虑的其它事项。

创建用例
让我们再来看看上个月对用例的定义：

用例是由系统的最终用户执行的具有一些有用结果的单一任务。

看着前几个月的 Allen 银行问题陈述清单，几个用例浮现在我的脑海中：

  • 小孩将钱存入帐户
  • 小孩从帐户提取钱
  • 小孩或家长查看存折
  • 家长开户（opening account）
  • 家长更改帐户
  • 家长关闭帐户（closing account）
  • 银行贷款给小孩（将在版本 2 中完成）

在本阶段，此列表只是一种猜想。随着过程的深入，肯定将会发现其它用例。此外，有些用例可能会过大，以至于必须要分解成小块。（通常，在进行到实现级别（implementation-level）设计时,用例的实现不应超过一个月。事实上，对于粗略估计而言，每个用例一个人月是一个不错的经验法则。有些方法 — 如 Kent Beck 的 XP — 需要的是能在两周内实现的较小用例。）请注意我们没有列出低层的原子操作 — 如计算利息，因为它们不是独立的用例。但是这些用例几乎都会成为子例。

现在既然我已经写了一些东西，那么就可以开始设计了。看着前面的列表，我有几个想法。首先，我不能肯定“存款”和“取款”是否为能分割开的用例。替代方法可能是类似于“完成交易”的用例，其中“交易”是存款或取款操作。具有相同工作流的两个用例当然是等价的，但我还不知道我的问题是否属于这种情况。目前，我将把两个用例都保留下来，因为在用户看来，它们显得不同，并且它们的外观结果显然也不同。（存款增加余额而取款则减少余额。“完成交易”用例修改余额。）

当着眼于“查看存折”时，我根本不能确定它是否为一个用例。它没有特别的结果而且存折很可能是在其它用例的过程中被显示。经过考虑，我把这个用例改成“做假设分析（what-if）游戏”，它的确有一个结果：了解财务计划。

接下来，我考虑开户、关闭和更改帐户。同样的，尽管在用户看来结果肯定明显不同，但我可以设想“开户”和“关闭帐户”用例的工作流将是相同的。UI 当然将是相同的 — 只是在开户时某些字段可能会是空白。这些细微差别可以在工作流中用简单的条件分支（“如果帐户是新的，则 ...”）处理。

我决定把“关闭帐户”作为一个真正的（尽管是琐碎的）用例，因为它的 UI 将与开启／更改用例的 UI 不同。我不会象一般的银行那样当余额恰好为 0.00 美元时就自动关闭帐户，因为很容易想象小孩会将他们的帐户余额变为零，而在这种情况下我不想重做设置。

最后，我决定抛开“贷款”用例。它是版本 2 的一个不错的特性，但现在不需要。

请注意在作这些决定时，我正在设想实现的细节（UI）。毕竟我在设计一个计算机程序，所以需要事先考虑它的实现。但是，我还是试着同时尽可能长时间地将思想集中到问题领域。我正在设想着实现以帮助理解域级（domain-level）问题，但我不是在设计实现，我也不是专门做这个的。UI 就是一种帮助我理清关于域级问题的辅助设计工具。这种方法和 Alan Cooper 推荐的 VB 风格的方法有很大不同，后者是 UI 设计驱动程序设计过程。这里，程序将从概念模型中产生，而 UI 仅仅只是开发那个概念模型的便利工具。

象“备份”、“保存”这样的事情不是用例，因为它们不是发生在问题领域。然而这也告诉我们一些有关程序应该如何工作的事情。退出任何程序以前，不一定要显式地“保存”，本用例尤其如此。如果存款，那就是存款。需要一个象保存这样的额外操作才能意识到存款事务是不自然的。我保证十岁左右的小孩在退出前不会考虑什么保存。要是您考虑它，那么我可以告诉您，要求“保存”操作和使“放弃我的所有工作”成为程序缺省行为是同一回事 — 那并不是特别好的缺省行为。“备份”也是一样。要么象更改帐户状态的副作用那样，程序应该自动备份；要么程序必须维护一个审计跟踪以便撤销更改。“任何不出现在域级的操作都应该出现在 UI 中”的情况是很少见的。可能出现的例外也许包括“帮助”，但如果 UI 十分直观就不需要帮助了。您可能会争论说程序应当通过留意您的行为来觉察到您需要帮助，并在那时提供帮助给您。大多数配置选项从来不被用户更改，所以用这种意义不大的东西使 UI 混乱是不明智的。（当然也有例外，象出现在问题领域的配置问题，如货币表示符号，当然它也可以通过检测当前语言环境来自动完成。）

暂定的用例列表如下所示。

  • 小孩将钱存入帐户
  • 小孩从帐户提取钱
  • 做假设分析游戏
  • 家长开户或更改帐户
  • 家长关闭帐户

在后面的两篇专栏文章中，我将通过深入详细地说明“存款”用例来继续这个练习。我将为您展示用例规范本身和有关正在做什么的详尽注释。

参考资料

  • 请参阅我编写的关于 OO 设计过程系列文章的前五个部分：
    • 入门
    • 开始设计软件
    • 细化问题定义
    • 验证分析
    • 用例，简介
    • 应用的用例，第 1 部分
  • Kent Beck 的 XP 方法希望用例小到能在两周内实现。请在 Extreme Planning wiki 阅读相关内容。
  • Alan Cooper 建议 UI 设计驱动程序设计过程。请在 uidesign.net 的 interview 中了解更多 Alan Cooper 的思想。
  • 工作流程图（连同 UML 的剩下部分一起）在 Martin Fowler 和 Kendall Scott 合著的 UML Distilled, 2nd Ed 一书中有详尽的描述。

关于作者
Allen Holub 是 NetReliance 的 CTO，这是一家位于美国旧金山的公司，它们为可信的网上商业交易的管理建立安全的全球基础设施。（是的，我们在招聘。）他从 1979 起就一直在计算机领域工作，并出版过多篇文章。Allen 已经写了八本书，包括他最新的 Taming Java Threads，它涉及 Java 线程的陷阱和缺陷。

他自己也不记得从事 OO 软件设计和构建的时间有多长了（使用 C++ 和 Java）。他从 1982 年起在美国加州大学伯克莱分校函授学院讲授 OO 设计和 Java。可以通过 allen@holub.com 或 allen.holub@net-reliance.com 与 Allen 联系。Allen 还是作家，艺术家和可以做仪器导航飞行（instrument-rated）的商业飞行员。他拥有加州大学伯克莱分校中世纪史和计算机科学的学士学位。

到页首
您对这篇文章的看法如何？
真棒！ 好文章 一般，尚可 需提高 太差！


OO 设计过程：应用的用例，第 1 部分

IBM : developerWorks 中国网站 : 组件技术 : 所有的文章 		developerWorks 中国网站
OO 设计过程：应用的用例，第 1 部分 英文原文
内容：
样本用例
参考资料
关于作者
对本文的评价
相关内容：
用例，简介
用例规划
应用的用例，第2部分
详细说明用例

Allen Holub (allen@holub.com)
首席技术官，NetReliance
2001 年 4 月

在本月的文章中，我继续上个月关于用例规划的文章，开始填写第一个（存款）用例的用例模板。我不只填写了模板，而且提供了有关工作时思考过程的详尽注释。

有关如何构建用例文档的几条初步注释已准备好。考虑将如何使用模板，我已经排列了模板的各节。不过创建模板的次序很少会与使用模板的次序相同。我已经按模板次序介绍了用例，但当我真正把东西拼装在一起时，我不会固守一种形式。我从名称和描述开始，然后进入并着手进行少许方案工作，接着进入并添加实现的注解，再回过头并进行一定的方案工作。然后，我填写期望的结果部分，添加后置条件，就这样一轮一轮的进行下去。

此外，在现实世界情况中，很少有一个人可以独自开发整个用例。理想的情况是，与商业相关的组件（用例名称、描述、方案、用户目标可能还有期望的结果）将主要由市场营销方开发，而工程方起咨询角色。技术项（依赖性、前置条件和后置条件、正式工作流分析、实现需求和注解）将主要来自工程方，而市场营销组织起咨询角色。商务规则与需求通常共同创建。如果现实的客户 — 产品的实际用户 — 也参与到过程中来就更好了。

您将要并行地设计几个构件。在开发用例时发现问题的迄今未知方面是很自然的，在遗忘之前应该回头把它们添加到问题陈述中。将包含各阶段设计的各种文档保存在一起对于大型设计而言是真正的问题，但它很关键。一旦发现思考问题时的缺陷，就必须立即更改所有相应的文档。通常在我工作的同时，我会将问题陈述、所有相关用例描述、UML 图和代码都放在我面前。当发现采取的方法不对时，能够回滚到以前的设计是很重要的，可是我不知道有任何自动化文档管理软件包能够做到真正“无痛苦”的回滚，要知道设计是由很多类型的文档 — 图画、格式化文本、ASCII 源代码等等 — 组成，而它们可能都会受到更改的影响。（如果谁知道能胜任此任务的文档管理系统，请给我发电子邮件。）

现在我们来看第一个用例。

样本用例

名称 | 1.0. 小孩将资金存入帐户。
描述 | 小孩（客户）将钱存入他／她的帐户。存款生效前必须经过银行主管人／家长的认可。

这在上下文中是第一次出现认可（authorization）这一概念，并且它是编写用例如何能找出问题陈述中的缺陷的一个实例。在我第一次处理这个用例时，我写道“家长和这个用例没有关系”但我后来的想法是“可是怎样防止小孩存入任意数目的钱呢？” 认可的概念解决了这个问题，但也引入了几个有趣的方案，我将在下面讨论。

原始的问题陈述的确涉及到安全性问题，但用的方式并不奏效。这里是原始问题陈述的相应部分：

原始问题陈述
有几个安全性问题。象真正的银行一样，小孩们不能通过自己修改存折进行存款或取款；他们必须要求银行做这件事。银行处理这个交易然后更新存折。也就是说，小孩把钱交给银行，银行再把钱放入帐户。银行从帐户取出钱然后交给小孩。（实际上，这意味着只有家长能够存款或取款，但家长应该按要求处理取款请求，否则小孩会以为银行是一个不让他们拿到自己钱的骗子。）

回顾一下，银行从帐户取出钱并交付那种事行不通，因为银行根本不做那件事（出纳员做，银行主管人做，银行不做）。因此，我现在返回并修改问题陈述，使家长对某种交易的认可的概念与在此用例中发现的更一致。

这里是修正的部分：

修正的问题陈述

有几个安全性问题。只有银行主管人（即家长）能够更改利率，设立新帐户（和初始余额）或者关闭帐户。尽管对于绝大部分交易，小孩都能完全独立地使用银行，有些交易（存款和贷款）需要家长／银行主管人的批准才能完成。对于小孩而言这样的交易是可见的并且在存折上显示出来，但未经批准的存款不能用于取款。取款不需要家长批准。取款要求会导致一张支票被打印出来。这些支票可以象现实世界支票一样使用。如果支票遗失，则发出停止支付的命令（需要收费）。支票可以交给银行（家长）来换取现金。

我是在写作时想到支票的问题 — 设计过程如何运作的另一个示例。当从不同角度考虑问题时，会发现遗漏了问题的某些部分。

当然，在对问题陈述作了更改后，现在需要得到领域专家批准才能检入新的版本。（既然我自己就是领域专家，那就简单了。）

期望的结果 | 帐户余额变得更大
用户目标 | 小孩：尽可能地使帐户余额变大并且注视财富累积。 家长：确保小孩没有利用未经授权的任意存款使余额膨胀。

请注意系统的不同用户有不同的目标，而且这些目标经常是相互排斥的。小孩希望帐户余额任意地大，而家长则想确保只有真正的资金 — 小孩们真正可支配的资金 — 被存入。这种目标冲突并不少见，所以当前练习的要点之一就是识别（并解决）这些冲突。

参与者／角色
我将使用上个月讨论的 CRC-card 格式来标识角色。同样，这个列表是我在进行方案工作时逐渐开发出来的；您在这里所见的是该列表的最终形式。

请注意有些参与者是自动化的（出纳员）而其他则是真人。还要注意有些参与者是系统里的被动对象（存折）。这就是用于程序开发的用例与用于 UI 开发的用例可能不同的地方。UI 样式的用例通常只会关心系统的实际用户所扮演的角色。而在 OO 系统里，即使被认为是被动的对象也有定义于其上的职责和操作。（例如，存折可能打印自身内容。）

类 职责 合作者
Kid（银行客户）
  • 填写存款单。
  • 持有存折。
 Teller（处理小孩的请求）
Parent（银行主管人）
  • 认可存款
 Teller（请求存款）
Teller
  • 收取存款单。
  • 从银行主管人处获得许可进行存款。
  • 更新存折。
 Parent（授权交易），
Kid（提出请求）
Account ???? ????
Passbook
  • 跟踪交易历史
 Kid（查看它），
Teller（更新它）
Deposit slip
  • 标识存款金额
 Kid（填写它），
Parent（认可它），
Teller（将它传给存折），
Passbook（记录它）

请记住，我这里介绍的组织更多地是为了方便用例的使用者而不是用例的创建者。

令人惊讶的是，我想不出 Account 类的对象能够在此系统中做什么。当前余额由存折保存，并且存折还保存着该帐户交易的记录。于是我怀疑帐户和存折是一回事；Account 所做的就是将一些标识信息分配给交易记录。但如果小孩／客户持有他们的存折，则无需又在帐户中存储标识信息；只需从小孩那里取得存折然后找出他们拥有的帐户即可。

这个问题实际强调了数据库样式的实体关系（ER）世界观和面向对象（OO）世界观之间的不同。将一种世界观映射为另一种世界观称为对象—关系映射，而这种映射很少是直接了当的，因为需要协调两种冲突的组织性策略。一个好的对象模型很少会生成一个好的数据库模式（反之亦然）。在当前示例中，一个 ER 世界观可能会将 Customer、Account 和 Passbook 都看作有效实体。Customer 包含客户特定的标识信息并且标识客户拥有的各种帐户，Passbook 包含交易历史，Account 则包含帐户特定信息，如当前余额。由于我刚才讨论过的原因，对象模型在那种方式下则不奏效。既然没有事情需要 Account 去做，那么它就不是合法的类。

在 OO 系统中，类如果没有事情可做就不应该存在；必须有职责（操作）。事实上，类完全根据它所能做的事情来定义 — 它用来执行那些任务的数据是应该对外部世界隐藏的不相关的实现细节。帐户可能封装数据，但我想不出任何它将执行的操作，因此 Account 就不是一个合适的类。（有关以这种方式考虑 OO 系统的更多信息，请参阅参考资料。）

另一方面，对象建模者与实体建模者的世界观有从根本上的不同。对象建模者几乎只对操作有兴趣 — 对象做什么。对象实现这些操作所必须保存的状态数据在设计过程的早期通常是不相关的。对象建模者关心静态属性仅当它可以用来区分一类对象与另一类对象。（例如，雇员有薪水，而普通人没有，所以薪水属性可以当作 Employee 的区分特征使用。）但是，数据库实体建模者却只关注静态属性 — 操作对他们而言是非实质的东西。其净结果是那种对于实体建模者是被诅咒者的组织（没有任何状态数据的类、类之间一对一和多对多的关系等等。）在 OO 系统中到处出现。

最终的文化冲突问题围绕着静态数据的组织。例如，在 OO 系统中，委员会的成员将会有效地包含在 Committee 类中。即，成员将是 Committee 类的静态属性。在关系型系统中，更常见的是 Committee 标识作为成员的属性（以便可以在委员会名称上连接或选择）。

但我的做法与前面的原则是相违背的。尽管前面已经说过，我还是将“Account”暂时保留在列表中以备帐户上的某些操作出现。但是我怀疑 Account 最终会消失。

现在已经完成了半个模板 — 在某种程度上，这是不太有意义的一半，因为该模板的这些部分对于组织我的思想更有用，而不是实际上详细说明用例的进展情况。下次我将提供用例定义的更有意义的那些部分：方案及活动图。随着用例进展，这些部分实际上试图制定工作流。

参考资料

  • 本文继续了我编写的关于 OO 设计过程的系列文章。前六个部分是：
    • 入门
    • 开始设计软件
    • 细化问题定义
    • 验证分析
    • 用例，简介
    • 用例规划
  • 工作流程图（连同 UML 的剩下部分一起）在 Martin Fowler 和 Kendall Scott 合著的 UML Distilled, 2nd Ed 一书中有很好的描述。

关于作者
Allen Holub 是 NetReliance 的 CTO，这是一家位于美国旧金山的公司，它们为进行网上可信的商业交易构建安全的全球基础设施。（是的，我们在招聘。）他从 1979 起就一直在计算机领域工作，并出版过多篇文章。Allen 已经写了八本书，其中最新的涉及 Java 线程的陷阱和缺陷（Taming Java Threads [Berkeley: Apress, 2000; ISBN 1893115100]）。

他自己也不记得从事 OO 软件设计和构建（使用 C++ 和 Java）的时间有多长了。他从 1982 年起在美国加州大学伯克莱分校函授学院讲授 OO 设计和 Java。可以通过 allen@holub.com 或 allen.holub@net-reliance.com 与 Allen 联系。

Allen 还是作家、艺术家和可以做仪器导航飞行（instrument-rated）的商业飞行员。他拥有加州大学伯克莱分校中世纪史和计算机科学的学士学位。

到页首
您对这篇文章的看法如何？
真棒！ 好文章 一般，尚可 需提高 太差！


OO 设计过程：应用的用例，第 2 部分

IBM : developerWorks 中国网站 : 组件技术 : 所有的文章 		developerWorks 中国网站
OO 设计过程：应用的用例，第 2 部分 英文原文
内容：
填写“存款”用例的用例模板
方案
参考资料
关于作者
对本文的评价
相关内容：
应用的用例，第 1 部分
用例规划
完成用例

Allen Holub (allen@holub.com)
首席技术官，NetReliance
2001 年 5 月

本文是有关我编写的 OO 设计过程的系列文章的继续。前七个部分涵盖了规划阶段，从初始设计到问题陈述的细化以及开始使用用例。在下个月转向用户界面以前，我将在本月结束用例的讨论。

我在本月这篇文章中接着上个月继续填写“存款”用例的这个用例模板的剩余部分。和上个月一样，我不仅填写了模板，还提供了工作时思考过程的详尽注释。这个模板在本月的部分在某种程度上是用例描述真正的实质部分，因为它实际上描述了随着用例进展的工作流。当用户通过用例操作时，这个工作流就是程序在运行时所必须做的。

在我的上一篇专栏文章中，讲述了大约一半的用例模板内容。我将继续该话题。

填写“存款”用例的用例模板

依赖性
类似于：2.0。小孩从帐户取钱。（等同？）前一用例：4.0。家长开户 包含：6.1。家长批准一笔存款 后一用例：6.0。家长批准较早的存款。

这个列表是暂定的。例如，用例 2.0 的“类似于”关系实际上可能是“等同”关系。

先决条件
帐户必须存在。
输入
小孩挣得了一些钱。

方案

  1. （“一路顺风”）
    Philip 干完了当天的家务后决定将挣得的 2.25 美元存到 Allen 银行。（如果他每天做家务的话，我现在可能就破产了。）他进入银行，向出纳员要一张存款单，填完存款单后把存款单和他的存折一起交给出纳员。出纳员在接受存款单之前要求某种身份验证，然后将存款单交给银行主管人批准。银行主管人（家长）在存款单签名批准存款，然后把它交还给出纳员，出纳员再更新 Philip 的存折以显示此次交易。然后出纳员将存折还给 Philip，他便可以愉快地估算自己正在增长的存款余额了。

    请注意在这个方案描述中，我小心谨慎地使用了问题陈述（在本系列文章中的前面的部分中讨论过）的词汇（例如，“存折”、“出纳员”等等）。只要我在这里引入了新的概念 — 那种情况确实会发生 — 我就必须回过头修改问题陈述来引入新的词汇。设计者的一个重要的（也是困难的）目标就是使所有设计文档能相互保持“协调”。如果一个文档中的更改影响到另一个文档，那么这两个文档都必须立即更改。例如，我有意不说“出纳员制作一个反映储蓄交易的日志项”，因为“日志”不属于这个领域的词汇而“存折”属于其中。对于设计而言，把它称做“日志”或“存折”没有区别。

    还要注意我没有使用“系统”一词。“系统”不做任何事情；所有的工作都由扮演着“精心设计的”角色的参与者完成。某些参与者（小孩）是真人。另一些参与者（出纳员）是完全自动化的。还有一些参与者（银行主管人）在有些情况下是自动化的，在有些情况下是真人（银行主管人的某些职责由家长而不是计算机执行）。重要的是“角色”。至于扮演“角色”的参与者是不是自动化的并不是问题的实质。

  2. 在这个方案里，钱实际上没有加到帐户里，更准确地说，这笔存款是以“未批准”的状态输入存折的。任何家长可以在随后的时间里批准该交易。换句话说，存款在批准以前处于“扣留”状态。出纳员将存折还给小孩，并且在存折上会反映出这笔存款，但是在家长在存款单上签字以前这笔钱不能从帐户取出。（这个方案与“一路顺风”不同之处在于没有家长来批准存款。）

  3. 在这个方案里，家长为小孩存钱（不同于由小孩开始然后家长批准的交易）。

在这一时刻，和其它不太冒失的词相比，我觉得“小孩”这个词相当不妥。我之所以还用小孩这个词 — 而不是用孩子、客户或其它等价的术语 — 是因为在整个设计文档集中保持命名的内部一致性是很重要的。但我还是希望我选择术语时能更谨慎一点。

第二个方案实际上引入了一个新的用例，这是我以前没有意识到的：“家长批准优先于存款”。这确实是一个独立的用例，即使批准在当前用例中也是一个操作（意味着一个子用例：家长批准一笔存款）。

工作流
这部分是一个关键，所以我将使用与到目前为止我一直使用的略有不同的格式并且显示工作流是如何发展的。首先，我将“一路顺风”作为一个简单列表建模。

“一路顺风”方案：

  1. 小孩从出纳员领取存款单并填写。
  2. 小孩将存款单和存折一起提交给出纳员。
  3. 出纳员验证小孩的身份是否有效。
  4. 出纳员将存款单交给银行主管人以获得批准。
  5. 银行主管人将存款单返回给出纳员，标记为批准。
  6. 出纳员在小孩的存折里写一条记录以显示这笔存款。
  7. 出纳员将存折交还给小孩。

尽管对于简单的工作流线性列表非常有效，但当工作流复杂的时候它就不能满足要求了。

Constantine 和 Lockwood 建议，表示非线性操作时使用象“以任何次序：”后面跟一个列表。还可以用“同时地”后面跟一个表示并发的列表。

可能我是那种喜欢形象化的人吧，我发现这些仅用文字表示用例会使人糊涂。当用例很复杂时我更喜欢 UML 工作流程图。“一路顺风”的 UML 如图 1 所示。

图 1. “一路顺风”工作流程图

符号差不多是自解释的。每个框表示一个活动，箭头表示工作流。从实心圆开始并以空心圆结束。这些列，被称为“泳道（swim lane）”，标识负责执行活动的对象（或子系统）的类。

在看图时，我注意到有些地方可以有并行性。即，某些活动（验证小孩的身份和批准存款）可以并行完成。（您也许会发现，倘若在进行到下一步之前这些活动都被执行了的话，则它们执行的顺序并不重要。）

图 2 显示了为反映并行性而对图 1 所做的修改。表示一组并行行为开始的粗水平线（在表示这组行为的流程图的顶部）称为“分叉”（fork）。底部的线（表示到达这点时，这些并行行为在继续后一步可能的工作之前必须要同步）称为“汇合”（join）。

图 2. 经过修改的“一路顺风”以反映并行性

现在我回过头考虑另外两个方案，并试着把它们合并到“一路顺风”图中。（修改后的图见图 3。）通常，如果不可能合并这些方案或合并后生成的图显得非常笨拙，那么这些附加的方案可能应成为独立的用例，而不是当前用例的方案。当然，目前这几个方案合并得很好。

在我意识到倘若我允许家长扮做小孩的角色那么工作流就和“一路顺风”一模一样以后，解决家长为小孩存款这一方案就不费吹灰之力了。就是说家长的密码可以用来登录小孩的帐户。我将这一结果的注释放在活动图（图 3）和商业规则部分（在下面）。

第二个方案（家长不在场）稍稍有些困难，因为我必须引入分支（branch）（在图 3 中以有一个输入箭头和多个输出箭头的菱形表示）。控制沿哪个输出路径而行的条件（称为“监护”（guard））就是在线上的文字标签。合并（merge）（也由一个菱形表示，但有多个输入箭头和一个输出箭头）标记条件行为的终止。每个分支应该有对应的合并。

可以如下表达在“分叉”与“汇合”之间的所有复杂的情况，如下：验证小孩的身份并在家长在场的情况下标记存款为批准，或者当家长不在场时标记为未批准（这两个过程不分先后次序）。当两个活动（验证和标记）都完成后就更新存折。

请注意，最初的方案描述说明出纳员扣留未批准的存款。当使用这个图时，我们可以明显地看到将未批准的存款扣留在存折中比由出纳员扣留方便得多，于是我就那样做了。我回过头修改了方案描述以反映这种更改。

图 3. 将附加方案添加到“一路顺风”

后置条件
新帐户余额 = 旧帐户余额 + 存款额

请注意这个用例包含了两个不必同时发生的不同操作（存款请求和存款批准）。然而，这的确是一个具有单个结果的单个用例。帐户余额总会改变，即使存款活动与批准活动之间相隔数日。

输出
更新的存折。

这一部分将是显示输出形式的地方（例如，以报表的形式）。我将这一部分推迟到随后文章中开始解决 UI 的时候再讲述。

商业规则（与领域相关）

  1. 存款生效前必须由家长批准。
  2. 利息自存款当日计算，即使几天之后才有批准。
  3. 未批准存款能无限地处于未批准状态，等待家长签名。它们不会“过期”。
  4. 家长的密码将验证小孩。即家长可以作为小孩登录。

需求（与实现相关）
  1. 存款必须自动“保存”。这里没有“File/Save”菜单。显式地“撤销”没有必要也不需要。可以用取款来撤销存款。
  2. 当小孩看存折时，必须显示“扣留”的存款。以灰色显示扣留的存款。

如果我说的是“以某种方式突出显示”而不是“以灰色显示”，那我将冒险写下无意义的规范。到结束时，规范必须明确。既然最后都要做出决定，那么不妨现在就开始。如果做了错误的决定那么总是可以回过头去做更改（当然是在规范正式批准以前）。

对我来说做错误的决定总比不做决定要好。我看到过的许多所谓的规范都太不直接明了，以至于可以从这个规范生成几百个不同的程序，而且所有的程序都是符合这个规范的。这种不明确性比无效更糟，因为它促使您做一些不正确的工作。

在当前用例中，我想这个问题显得太琐碎，以至不必作决定。只要对这一项做了突出显示，至于如何做就并不重要了。因而这个决定可以推迟到正式的 UI 设计过程。如果突出显示的概念模糊不清，那么实现设计的各个程序员将按各自对突出显示这一概念的理解实现，导致 UI 不一致，其结果是更难使用。

实现注解
  1. 目前还没有登录的概念。看着这些方案，Philip 不大会一次做几项交易。事实上，更有可能是他只做一项交易，但是忘了注销。（这是我在家里观察到的行为 — Philip 经常在早上玩游戏机，而我会发现游戏机在 12 小时之后还开着，也就是在他上床睡觉之后。）考虑到这种行为，接受存款单的同时还要求某种验证似乎更安全些。这也是真正的银行的本来的运作方式。
  2. 与登录类似的是在被允许进入银行之前向警卫表明身份。（那也许是一个合理的方法，只不过我没有选它。）请注意在这个用例中没有禁止出纳员记住 Philip 是谁。也就是说，客户在这个用例的作用域内以某种方式被标以“可信的”，这样不需要重复验证过程就可以执行随后的交易。“可信的”属性可以通过注销来取消，或者干脆在给定时间到了以后，取消这个属性。（例如，如果在银行您两分钟后仍无任何活动的话，将变成不可信的。）
  3. （在我们开始真正的对象建模之前，您可能会不明白这个注解，所以如果现在不理解，不必担心。）在大多数情况下，小孩是外部对象。也就是说，系统里没有程序性的对象表示小孩；更准确的说，小孩是系统的物理用户。另一方面，既然系统里某个对象必须处理验证，那么小孩身份的概念几乎肯定会被建模。

  4. “可用资金”与“当前余额”之间有所不同。（后者不反映未批准交易。）既然小孩认为存款就是存款，不管是否批准，存折应该把二者的值都显示出来。不管怎样，告诉小孩“扣留”的工作原理是没有坏处的。

    我还没确定怎样在 UI 表示扣留才最好。解决方案之一是在存款单上向查看存折的小孩显示扣留，但在存款单获得家长批准之后存款才会在存折上正确地显示。另一个可能的方案是存款象获得批准的存款一样显示在存折上，但带有表示存款正被扣留的某种可视化标记（灰色文字？）。

    后者更好一些，但这意味着“家长批准当前存款”的用例将可能与“家长批准暂挂的存款”的用例有明显的不同。

    请注意，如果出纳员为家长批准而扣留存款单，小孩将不得不请出纳员看存折，以便于出纳员可以显示扣留的存款单和存折。

  5. 解决家长批准的最好方法可能就是 UI 上的存款单本身。或许有一个“批准的”框，在选中后会弹出一个小的对话框用来输入签名。另一个方法是在存款单上有一个“批准签名”栏，家长可以在其中输入“同意”这类短语。（请注意交易已批准的指示 — 例如，签名的副本 — 应该出现在经过批准的存款单上）。请不要在“经批准的签名”栏中显示 x（或至少做到：一旦签名被验证就替换掉这些 x）；在通过验证的阶段泄露字符的数目实在没有必要。
  6. 这里有一些访问控制问题。扮演小孩角色的参与者能指定存款，但不能批准存款。（批准会使帐户余额和存折被更新。） 扮演家长角色的参与者既能指定存款也能批准存款。系统必须能够区分这两种角色的参与者 — 密码验证就足够了。

  7. 扣留资金的利息会被累加，即使还不能动用这些钱。处理利息可能的最好办法是把存折看作交易的列表，按日期排序。当存款单被批准后，将把它按已排的顺序插入到交易列表中。利息将从最近插入的交易的时刻开始重新计算。若用这种方法，则未批准的交易实际上没有生利息。

    然而您也许想根据天数来显示利息的增长，那么象真正的银行存折或银行帐单那样只列出交易就显得不够。您实际需要每天有一行项目。但是会有很多行。一个合理的折衷办法是在每天计息的基础上显示前 30 天的信息，而存折剩余部分则象标准的银行帐单一样按常规显示公布的利息（不是每天），可能每星期公布一次。

这就是完整的用例介绍。为了不使您感到厌烦，我不在随后的文章里介绍用例的剩余部分，我将把设置帐单的整个过程放到我的网站上（请参阅参考资料）以便您在空余时间浏览。下个月我将继续这个设计过程：研究 UI 设计（它是必不可少的，不仅因为我们将必须构建它，还因为 UI 能用于验证这些用例和提供有用的通信工具）。

参考资料

  • 本文是有关我编写的 OO 设计过程系列文章的继续。前七个部分是：

    • 入门
    • 开始设计软件
    • 细化问题定义
    • 验证分析
    • 用例，简介
    • 用例规划
    • 应用的用例，第 1 部分
  • 可以在 www.holub.com 访问 Allen 的网站。
  • Constantine 和 Lockwood 所建议的表现非线性操作的方法与本文中的不同。
  • 在 Allen Holub 的什么是对象？（JavaWorld：July，1999）中讨论了由对象的操作定义对象的概念。这个系列的后续文章（在 http://www.holub.com/aiharticles.html 上有所有文章的索引）讨论的是如何将这种原理应用到现实世界的系统。
  • 在 Martin Fowler 和 Kendall Scott 合著的 UML Distilled, 2nd Ed 一书中很好地描述了工作流程图（连同剩下的 UML）。

关于作者
Allen Holub 提供 OO 设计和方法以及 Java 编程领域的咨询服务和正式的培训与指导。他从 1979 起就一直在计算机领域工作，最近担任 NetReliance，Inc. 的首席技术官（还在寻找另一个 CTO 的职位）。他出版过多篇文章。Allen 已经写了八本书，其中最新的涉及 Java 线程的陷阱和缺陷（Taming Java Threads [Berkeley: Apress, 2000; ISBN 1893115100]）。Allen 还是作曲家，艺术家和可以做仪器导航飞行（instrument-rated）的商业飞行员。他拥有加州大学伯克莱分校中世纪史和计算机科学的学士学位。

到页首
您对这篇文章的看法如何？
真棒！ 好文章 一般，尚可 需提高 太差！