面向对象的设计过程
这是来自于IBM的文档。。主要是谈面向对象设计方面的知识。。但我觉得跟UI设计也有很多关联....
=======================
OO 设计过程:入门
如何设定优先级
Allen Holub
撰稿编辑,JavaWorld
2000 年 7 月
<!-- END title and author lines --><!-- Begin Table of Contents-->内容: 欢迎阅读本在线课程的第一部分。在本专栏中,我打算让您实际操作,以亲身体验面向对象 (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. 实现。
在逐一进行这些步骤时,我试图描述所发生的整个过程,包括所犯错误和如何修复错误。结束之后,您将对这种设计过程有完整(和现实)的认识。
----------------------------------------------------------------------------------------------------------------------
OO 设计过程:开始设计软件
一个教学项目
Allen Holub
撰稿编辑,JavaWorld
2000 年 8 月 <!-- END title and author lines--><!-- Begin Table of Contents -->
内容: OO 需求搜集 问题说明书 细化您的说明书 问题和解决方案 编码前的细节 结束语 参考资料 关于作者
<!-- End Table of Contents-->
我们在七月份已经谈了这个系列的有关如何区分设计过程的优先次序. 这个月,我将着手设计一个软件。我希望项目不要太琐碎但很简洁,使我们在较合理的时间里能完成它。我需要一个实实在在的程序,而不只是一个纯教学性的练习。在这篇文章中,我采用一段教学软件。在以后的几个月课程中,我将演示这个程序的完整设计和 (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美元存一年所获得的利息最多为 {logcontent}.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。
<!-- End paper-->
----------------------------------------------------------------------------------------------------------------------
OO 设计过程:细化问题定义
关键是直接付款
Allen Holub
撰稿编辑,JavaWorld
2000 年 9 月
<!-- END title and author lines --><!-- Begin Table of Contents-->内容这个月我将继续细化上个月开始的关于 Bank of Allen 的教育软件的问题说明书。
当开始问题定义时,我通常是做下一步,建立一个词汇表。一般来说,对于熟悉该领域的人会马上明白术语的含义,因而不需要去定义它,但对于具有模糊含义的术语,或者在目前的问题中用在有限的范围内,这就需要专门来定义它。
在问题说明书中如 gotcha 是不妥当的。一个词不能指两个不同的事物,同时两个不同的词不能指同一件事物。要做到这一点,首先要建立词汇表。不需要定义该问题领域的术语。譬如,如果是会计方面的问题,您勿须定义 信用或者利率。然而,目前的问题是关于养育,所以象信用之类的术语需要精确定义。
词汇表对我们的主要目标也有帮助,它能确定关键问题层次的抽象。避免词汇表中的词和概念经常出现在问题说明书的正文中。譬如, 存折引入了术语交易。(交易的概念是重要的,虽然我没有立即认识到这一点。)
在词汇表中加入实现细节部分也是很有帮助的,它提供了记录这些细节的地方,就其本身而言,尽管它不是问题说明书的都具有的。那些通常归入功能性需求规范的东西常被放入实现细节。
以下是我的第一遍词汇表和实现细节。
<!-- Sidebar -->
词汇表
余额 当前在帐户中的货币金额。 银行 父母 贷方 由银行存入您的帐户,而不是您自己。 存款 将钱放入银行;增加您的余额。 利息 作为您一直把钱放入银行的报酬而给您的信用。利息是每天计算的,通过把信用按当前帐户余额的固定比例放入该帐户(“本金”)。如果利率是 10%/月,余额乘以 (.10/30) 就得到每天的利息。 存折 发生在某个帐户上的交易的作业记录和摘要信息。孩子们必须从银行得到存折。 取款 从银行取钱;降低您的余额。 实现细节
就其本身而言,这部分不是问题说明书的一部分。它只不过是我想起的清单,我把它与问题说明书放在一起。
• 当程序第一次运行时,父母可以为他们自己设置口令。从那以后,以父母亲登录必须要知道口令。父母亲在任何时候可以更改口令。
• 更改利息率可以立即生效,但根本不影响以前的余额。(存折不一定(但可以)反映新的利率,该利率用于利率更改之日的存款,甚至是那天以前的。)
• 小孩必须向银行申请存折。
• 每次在给孩子们之前,存折要更新。
• 父母必须要有备份或银行的措施。这可以是简单文件拷贝或更加详细描述的说明。
<!-- End sidebar -->
验证和细化问题说明书
如果满意自己准备的合理的问题说明书,那么下一步做设计审查。我感激我的妻子 DJ,她是程序员并且是该方面的专家--母亲。她发现了各种各样小的语法性问题(我必须承认,你刚才所读的最初的陈述是我修订的),而且她发现了一个我没有意识到的严重缺陷。“你不能象那样计算利息”她指出。如果你仅仅根据一个月的天数来计算每月的利息,但是每天计算利息过于复杂,到月底你会得到一个很大的数目这要比你到月底再结算一次利息多的多。你在此所需要的是 名义的每月低于5%的利率,它将向你提供每月5%的有效利率。好吧。回到正文。
在当前问题中,出现了两个领域:养育,当然,从较小范围的角度讲的融资。我原以为我对费用了解的很多,但是很显然我错了。所以,我停下了设计,拣起了书本直到我完全了解了这方面的知识再继续设计下去。
这个问题本该出现得更早一些,如果我没有省略掉设计所必须的第二个步骤:在你对问题所属领域非常熟悉之后(第一阶段)和在开始建模之前(第三阶段),你必须与客户进行交谈以发现他们所需要的。我认为我不需要这样做,因为我已经是这方面的专家了-作为一个父亲。我错了。如果我与这方面的专家会谈过,一些并没有象我一样进入到设计的实质的人(例如我的妻子),在第一部分的利息问题上,我就可能做对。
不要忽略明显的错误
这儿有很多其他的例子,但最重要的是我称之为“浴室效应。”这一词干来源于几年以前我写给“ Dr. Dobb's Journal”一篇(未发表)文章开头的部分。“假设我们建造房子象我们做软件一样。” (我们都居住在如Winchester Mystery House一样的破房子里)。总之,到了文章的结尾,最后购房者第一次来看房子(在房子已经完全造好之后)。购房者带着迷惑的表情四处看了一圈,问“浴室在哪里?”
“浴室?”分包商说,“在说明当中没有浴室。现在把他们加上去会花很多钱;我们得浇注水泥,敲开墙。我们为什么不在房屋的后院盖一个呢?”
"这是什么意思‘它不在说明当中是什么意思’?”购房者结结巴巴的说,“谁会傻到设计房子而没有浴室?”
但是在软件设计中却是经常发生的问题。用户倒是不经常提及他需要的东西,而这些东西对问题来说都是很基本的。不是用户“对我隐瞒”正如许多程序员所说。这是因为设计者对问题所涉及的领域不够了解而提不出正确的问题。
不要仅仅点头 -- 倾听
当进行会谈的时候,倾听!不要打断。记住会谈中出现的每一件事情(那种会速记的人获益非浅),准确的记录下此领域专家的每一句话都是非常重要的。我曾经参加过许多会议,用户们说了几个小时,没有谁记录。随后,当用户离开后,没有谁能精确地记住讨论了些什么。在一部分程序员当中有一个不好的倾向,对用户所说的不加理会。都知道程序员们将用户对于问题 是什么的描述扭曲为程序员认为问题该象什么的描述。他们将重新描述一些事情,将那些组成的问题涉及的领域翻译成更熟悉的语句,否则,他们所听到的就会产生扭曲和变化。经常,记住的是程序员们对问题的错误理解,而不是此领域的专家对问题的实际描述。
最后,记住大多数的用户都有过与那种自以为是的计算机人员相处很长的历史,那些人认为对于任何问题,他们比用户懂得更多。(由于认不清方向,城里人对农夫说,“你不是很聪明,是吗?”农夫回答道,“也许,但 我没有迷路。”)问题是许多用户过了一会儿就放弃了。如果程序员不认真倾听,为什么在交谈中就会有障碍?你将不得不打破这一系列的障碍。
一般而言,对于问题说明书的主要问题根本在于细节的级别是否属于它。通常,在问题实际上所涉及的领域当中,问题说明书的本身不会是科技术语。你得为了这个目标去参考教科书或字典。但在这,答案是“是的”因为固定利率和有效利率的区别在于他们不是实际问题领域的一部分(这是养育)。我们领域的专家是父母或会计师们。我们并不是发展一个运用于银行或建立一个试图运用于真正银行的模块的系统。这仅仅是为孩子们写的软件,不是关键的银行系统,所以我们不能采取我们部分专家关于如何计算利息的建议。利息的计算是问题定义的一个重要的部分,所以事实上它属于问题说明书。
问题说明书重点放在手边的问题是十分重要的。我们并不想在一段孩子的软件中解决“银行”的通病。在另一方面,我们也不想遗漏任何重要的细节。如果一般的读者(假设他们都是此领域的专家)需要一些信息以了解问题的陈述,然后就属于他们。如果这些资料是这个领域当中重要的一部分,我们不会加上去。在现在的情况下,如果我真正在写一个银行系统,我当然也就不会为定义“有效利率”犯愁了。因为我可以向这方面领域的专家请教这些知识。(由于同样的原因,设计者必须对问题所涉及的领域和此领域的基本术语有足够的了解。你不能设计一个会计应用程序除非你懂会计的一些知识-至少是一个聪明的外行。)
所以我找出一本傻瓜金融书,查出名义利息,并修改利息的定义为:
<!-- Sidebar -->
利息 作为您一直在银行存钱的报酬而给您的信用。利息被加入每天的余额中。 Bank of Allen 的运作如真正的银行一样,利息按照月份来计算。尽管如此,每天挣得的利息必须登记在存折上。这里的问题是根据每天计算的利息与每月计算的利息会产生不同的结果。
譬如,如果您开始在银行有 $100,利率是 10%,按照每 10 天计算的复利,在 10 天后,您帐户上将有 $110。
而另一方面,如果根据每天的复利计算,也就是将 10% 的利率平摊到每一天(1%/天)来计算,得到的结果会大于按每 10 天计算的结果。
第零天本金 $100.00 利息 .00 第一天本金+利息 $101.00 利息 .01 第二天本金+利息 $102.01 利息 .02 第三天本金+利息 $103.03 利息 .03 第四天本金+利息 $104.06 利息 .04 第五天本金+利息 $105.10 利息 .05 第六天本金+利息 $106.15 利息 .06 第七天本金+利息 $107.21 利息 .07 第八天本金+利息 $108.28 利息 .08 第九天本金+利息 $109.36 利息 .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 一个月,银行付给您 的利息。”
由于利息是按每日复利计算的,每天计算利息必须要用名义利率(这样到了月底,由有效利率技计算的利息才能与之相一致)。譬如,假定一个月有 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));
<!-- End sidebar -->现在看上去更合理,下面我们该做还没有完成的事情。这正是下个月专栏的主题。
----------------------------------------------------------------------------------------------------------------------
OO 设计过程:验证分析
原型与模型的差异以及二者的重要性
Allen Holub
首席技术官,NetReliance
2000 年 12 月
<!-- END title and author lines --><!-- Begin Table of Contents -->内容: <!-- End Table of Contents -->
既然我们已经定义并细化了问题陈述,现在要为我们的教育软件构建一个模型。
本文是 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 设计开始。
这是来自于IBM的文档。。主要是谈面向对象设计方面的知识。。但我觉得跟UI设计也有很多关联....
=======================
OO 设计过程:入门
如何设定优先级
Allen Holub
撰稿编辑,JavaWorld
2000 年 7 月
<!-- END title and author lines --><!-- Begin Table of Contents-->内容: 欢迎阅读本在线课程的第一部分。在本专栏中,我打算让您实际操作,以亲身体验面向对象 (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. 实现。
在逐一进行这些步骤时,我试图描述所发生的整个过程,包括所犯错误和如何修复错误。结束之后,您将对这种设计过程有完整(和现实)的认识。
----------------------------------------------------------------------------------------------------------------------
OO 设计过程:开始设计软件
一个教学项目
Allen Holub
撰稿编辑,JavaWorld
2000 年 8 月 <!-- END title and author lines--><!-- Begin Table of Contents -->
内容: OO 需求搜集 问题说明书 细化您的说明书 问题和解决方案 编码前的细节 结束语 参考资料 关于作者
<!-- End Table of Contents-->
我们在七月份已经谈了这个系列的有关如何区分设计过程的优先次序. 这个月,我将着手设计一个软件。我希望项目不要太琐碎但很简洁,使我们在较合理的时间里能完成它。我需要一个实实在在的程序,而不只是一个纯教学性的练习。在这篇文章中,我采用一段教学软件。在以后的几个月课程中,我将演示这个程序的完整设计和 (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美元存一年所获得的利息最多为 {logcontent}.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。
<!-- End paper-->
----------------------------------------------------------------------------------------------------------------------
OO 设计过程:细化问题定义
关键是直接付款
Allen Holub
撰稿编辑,JavaWorld
2000 年 9 月
<!-- END title and author lines --><!-- Begin Table of Contents-->内容这个月我将继续细化上个月开始的关于 Bank of Allen 的教育软件的问题说明书。
当开始问题定义时,我通常是做下一步,建立一个词汇表。一般来说,对于熟悉该领域的人会马上明白术语的含义,因而不需要去定义它,但对于具有模糊含义的术语,或者在目前的问题中用在有限的范围内,这就需要专门来定义它。
在问题说明书中如 gotcha 是不妥当的。一个词不能指两个不同的事物,同时两个不同的词不能指同一件事物。要做到这一点,首先要建立词汇表。不需要定义该问题领域的术语。譬如,如果是会计方面的问题,您勿须定义 信用或者利率。然而,目前的问题是关于养育,所以象信用之类的术语需要精确定义。
词汇表对我们的主要目标也有帮助,它能确定关键问题层次的抽象。避免词汇表中的词和概念经常出现在问题说明书的正文中。譬如, 存折引入了术语交易。(交易的概念是重要的,虽然我没有立即认识到这一点。)
在词汇表中加入实现细节部分也是很有帮助的,它提供了记录这些细节的地方,就其本身而言,尽管它不是问题说明书的都具有的。那些通常归入功能性需求规范的东西常被放入实现细节。
以下是我的第一遍词汇表和实现细节。
<!-- Sidebar -->
词汇表
余额 当前在帐户中的货币金额。 银行 父母 贷方 由银行存入您的帐户,而不是您自己。 存款 将钱放入银行;增加您的余额。 利息 作为您一直把钱放入银行的报酬而给您的信用。利息是每天计算的,通过把信用按当前帐户余额的固定比例放入该帐户(“本金”)。如果利率是 10%/月,余额乘以 (.10/30) 就得到每天的利息。 存折 发生在某个帐户上的交易的作业记录和摘要信息。孩子们必须从银行得到存折。 取款 从银行取钱;降低您的余额。 实现细节
就其本身而言,这部分不是问题说明书的一部分。它只不过是我想起的清单,我把它与问题说明书放在一起。
• 当程序第一次运行时,父母可以为他们自己设置口令。从那以后,以父母亲登录必须要知道口令。父母亲在任何时候可以更改口令。
• 更改利息率可以立即生效,但根本不影响以前的余额。(存折不一定(但可以)反映新的利率,该利率用于利率更改之日的存款,甚至是那天以前的。)
• 小孩必须向银行申请存折。
• 每次在给孩子们之前,存折要更新。
• 父母必须要有备份或银行的措施。这可以是简单文件拷贝或更加详细描述的说明。
<!-- End sidebar -->
验证和细化问题说明书
如果满意自己准备的合理的问题说明书,那么下一步做设计审查。我感激我的妻子 DJ,她是程序员并且是该方面的专家--母亲。她发现了各种各样小的语法性问题(我必须承认,你刚才所读的最初的陈述是我修订的),而且她发现了一个我没有意识到的严重缺陷。“你不能象那样计算利息”她指出。如果你仅仅根据一个月的天数来计算每月的利息,但是每天计算利息过于复杂,到月底你会得到一个很大的数目这要比你到月底再结算一次利息多的多。你在此所需要的是 名义的每月低于5%的利率,它将向你提供每月5%的有效利率。好吧。回到正文。
在当前问题中,出现了两个领域:养育,当然,从较小范围的角度讲的融资。我原以为我对费用了解的很多,但是很显然我错了。所以,我停下了设计,拣起了书本直到我完全了解了这方面的知识再继续设计下去。
这个问题本该出现得更早一些,如果我没有省略掉设计所必须的第二个步骤:在你对问题所属领域非常熟悉之后(第一阶段)和在开始建模之前(第三阶段),你必须与客户进行交谈以发现他们所需要的。我认为我不需要这样做,因为我已经是这方面的专家了-作为一个父亲。我错了。如果我与这方面的专家会谈过,一些并没有象我一样进入到设计的实质的人(例如我的妻子),在第一部分的利息问题上,我就可能做对。
不要忽略明显的错误
这儿有很多其他的例子,但最重要的是我称之为“浴室效应。”这一词干来源于几年以前我写给“ Dr. Dobb's Journal”一篇(未发表)文章开头的部分。“假设我们建造房子象我们做软件一样。” (我们都居住在如Winchester Mystery House一样的破房子里)。总之,到了文章的结尾,最后购房者第一次来看房子(在房子已经完全造好之后)。购房者带着迷惑的表情四处看了一圈,问“浴室在哪里?”
“浴室?”分包商说,“在说明当中没有浴室。现在把他们加上去会花很多钱;我们得浇注水泥,敲开墙。我们为什么不在房屋的后院盖一个呢?”
"这是什么意思‘它不在说明当中是什么意思’?”购房者结结巴巴的说,“谁会傻到设计房子而没有浴室?”
但是在软件设计中却是经常发生的问题。用户倒是不经常提及他需要的东西,而这些东西对问题来说都是很基本的。不是用户“对我隐瞒”正如许多程序员所说。这是因为设计者对问题所涉及的领域不够了解而提不出正确的问题。
不要仅仅点头 -- 倾听
当进行会谈的时候,倾听!不要打断。记住会谈中出现的每一件事情(那种会速记的人获益非浅),准确的记录下此领域专家的每一句话都是非常重要的。我曾经参加过许多会议,用户们说了几个小时,没有谁记录。随后,当用户离开后,没有谁能精确地记住讨论了些什么。在一部分程序员当中有一个不好的倾向,对用户所说的不加理会。都知道程序员们将用户对于问题 是什么的描述扭曲为程序员认为问题该象什么的描述。他们将重新描述一些事情,将那些组成的问题涉及的领域翻译成更熟悉的语句,否则,他们所听到的就会产生扭曲和变化。经常,记住的是程序员们对问题的错误理解,而不是此领域的专家对问题的实际描述。
最后,记住大多数的用户都有过与那种自以为是的计算机人员相处很长的历史,那些人认为对于任何问题,他们比用户懂得更多。(由于认不清方向,城里人对农夫说,“你不是很聪明,是吗?”农夫回答道,“也许,但 我没有迷路。”)问题是许多用户过了一会儿就放弃了。如果程序员不认真倾听,为什么在交谈中就会有障碍?你将不得不打破这一系列的障碍。
一般而言,对于问题说明书的主要问题根本在于细节的级别是否属于它。通常,在问题实际上所涉及的领域当中,问题说明书的本身不会是科技术语。你得为了这个目标去参考教科书或字典。但在这,答案是“是的”因为固定利率和有效利率的区别在于他们不是实际问题领域的一部分(这是养育)。我们领域的专家是父母或会计师们。我们并不是发展一个运用于银行或建立一个试图运用于真正银行的模块的系统。这仅仅是为孩子们写的软件,不是关键的银行系统,所以我们不能采取我们部分专家关于如何计算利息的建议。利息的计算是问题定义的一个重要的部分,所以事实上它属于问题说明书。
问题说明书重点放在手边的问题是十分重要的。我们并不想在一段孩子的软件中解决“银行”的通病。在另一方面,我们也不想遗漏任何重要的细节。如果一般的读者(假设他们都是此领域的专家)需要一些信息以了解问题的陈述,然后就属于他们。如果这些资料是这个领域当中重要的一部分,我们不会加上去。在现在的情况下,如果我真正在写一个银行系统,我当然也就不会为定义“有效利率”犯愁了。因为我可以向这方面领域的专家请教这些知识。(由于同样的原因,设计者必须对问题所涉及的领域和此领域的基本术语有足够的了解。你不能设计一个会计应用程序除非你懂会计的一些知识-至少是一个聪明的外行。)
所以我找出一本傻瓜金融书,查出名义利息,并修改利息的定义为:
<!-- Sidebar -->
利息 作为您一直在银行存钱的报酬而给您的信用。利息被加入每天的余额中。 Bank of Allen 的运作如真正的银行一样,利息按照月份来计算。尽管如此,每天挣得的利息必须登记在存折上。这里的问题是根据每天计算的利息与每月计算的利息会产生不同的结果。
譬如,如果您开始在银行有 $100,利率是 10%,按照每 10 天计算的复利,在 10 天后,您帐户上将有 $110。
而另一方面,如果根据每天的复利计算,也就是将 10% 的利率平摊到每一天(1%/天)来计算,得到的结果会大于按每 10 天计算的结果。
第零天本金 $100.00 利息 .00 第一天本金+利息 $101.00 利息 .01 第二天本金+利息 $102.01 利息 .02 第三天本金+利息 $103.03 利息 .03 第四天本金+利息 $104.06 利息 .04 第五天本金+利息 $105.10 利息 .05 第六天本金+利息 $106.15 利息 .06 第七天本金+利息 $107.21 利息 .07 第八天本金+利息 $108.28 利息 .08 第九天本金+利息 $109.36 利息 .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 一个月,银行付给您 的利息。”
由于利息是按每日复利计算的,每天计算利息必须要用名义利率(这样到了月底,由有效利率技计算的利息才能与之相一致)。譬如,假定一个月有 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));
<!-- End sidebar -->现在看上去更合理,下面我们该做还没有完成的事情。这正是下个月专栏的主题。
----------------------------------------------------------------------------------------------------------------------
OO 设计过程:验证分析
原型与模型的差异以及二者的重要性
Allen Holub
首席技术官,NetReliance
2000 年 12 月
<!-- END title and author lines --><!-- Begin Table of Contents -->内容: <!-- End Table of Contents -->
既然我们已经定义并细化了问题陈述,现在要为我们的教育软件构建一个模型。
本文是 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 设计开始。
回复Comments
作者:
{commentrecontent}