使用用例 (Use Cases) -- 尽管这个名称令人感到纳闷 -- 已经变成对象导向方法中最受欢迎的技术之 一 。 Ivar Jacobson 提出这项技术并获取广泛的运用因而引人入胜,也在信息技术及系统发展中取得核心的地位。 由于近来每一位作者于对象导向设计方面都会谈及使用用例 (use cases) 并引发讨论。 同时,它们也在统一模塑语言 (UML) 中扮演一个重要的角色。
使用用例的价值来自以下几点原因。 首先,它们协助我们去发现需求; 使用用例允许你通过将焦点集中在用户需要去执行的工作上,以捕获用户的需求。 使用用例能够帮助我们有系统的阐述系统,并且帮助我们验证这个使用用例的确可以被构建在此系统中。 它们也协助控制反复式开发 (iterative development) :在每一个发展阶段重复发生时,你可以进一步的建立使用用例需求的子集。
尽管使用用例被广泛的使用,然而仍有许多问题点无法被充分的理解。 如其基本的定义 -- 系统运行与用户互动过程中,所产生一连串的活动 (actions) – 看起来够简单了吧。 但是你若立即开始尝试去捕捉系统的使用用例时,你将跑进死胡同。 我曾经看过一些项目在使用用例里纠缠不清,促使发展过程进退维谷。 所以这里我就列举一些我所看过误用使用用例的案子,并且让我们思考如何去避免这些错误。
滥用功能分解 (Abuse by Decomposition)
我见过数 个项目 因为将太多的努力浪费在架构使用用例上而陷入困境,仿效 Jacobson 的作法,在 UML 规格里于使用用例之间采用以下两个关系: uses 及 extends [朱子1] ( 译 注 [ 一 ] ) 。 这可能是有用的,然而我将它们视为是一种困扰 - 特别是使用这类关系。 在上述的用例里,分析者采取相当粗糙的使用用例 (use case) 并且将它拆解成数个子使用用例 (sub-use cases) 。 每一个子使用用例再进一步的被拆解,通常直到你将它们分解到 单元式的使用 用例,类似原子的程度为止。
这是一种功能分解 (functional decomposition) ,一种与对象导向开发相对立的设计风格。 它导致了以下的几种问题:首先是使用用例的结构直截了当地反映到代码时,造成系统的设计看起来就像这些经拆解的使用用例 (use cases) 一样。 一般的症状是去找出行为富丽堂皇的控件对象 (controller objects) 来操作沉默寡言的数据对象 (data objects) ,这种作法较之一个经封装的数据结构而言,是相当琐碎的工作。
这样的设计方式也丧失了找到更多有益的对象。 系统重复的行为可能跨越不同的控制项,并且数据结构的知识 (knowledge) 也遍布这些控件。 基本的问题在于功能的分解促使你思考一种行为,会依循其更高阶 (higher-level) 行为的脉络。 采取这样的方式,会造成你难以在另一个环境 (context) 中运用相同的系统行为,就算这两种环境大部分是雷同的。 若你考虑到要让对象拥有的行为可以运作在不同的环境里,就不能采用功能分解的方法。
当然,并不需要像使用用例这般在意系统外部的结构 [朱子2] ( 译 注 [ 二 ] ) ,你只要牢记系统内部的结构就可以避免这个问题。 无论如何,对于毫无对象导向开发经验的开发者而言,这是个困难的概念,所以这些人也就极有可能 采 功能分解的方式去建立系统。
就算你不会注意到功能分解如何影响你内部的设计,大量地构筑使用用例 (a set of use cases) 终将会陷入这种困扰,因为人们最后还是会花费大量的时间在它们身上。 把每一个使用用例往下拆解成单元的步骤里,对拆解成单元的使用用例进行参数化作业,使它们与更高层 (higher-level) 使用用例的功能保持一致,然而这些作业所占用的时间最好能用在其他事物上。 在开发阶段初期是不需要去捕获使用用例的每 个 细节,你会想要在开发初期留意到具有高风险的部分,而大多数的细节会留待反复式开发 (iterative development) 最后的阶段才进行,那即是反复式开发的整个重点。
所以我会打消那些对对象 (objects) 不熟悉的情况下行使 use ( 使用 ) 关系的主意,同时把它当作是在设计及使用用例之间的一项警讯。 「 extends 」 ( 延伸 ) 关系似乎比较不会造成困扰,但是我依然不会有使用它去做出一件大事的举动。 我曾经看过即使没有用到这些关系也能很 有成效地运用 使用用例的项目,所以它们基本上并不具任何意义。
滥用抽象化 (Abuse by Abstraction)
论及对象就不离抽象化 (abstraction) 。 就算是简单的抽象化也会发现其效力无穷,它让复杂的问题易于处理,这就是所谓良好的设计。 所以作为一位设计者 (designers) ,我们运用抽象化,促进抽象化,歌咏抽象化。
但是抽象化与使用用例摆在一起运用可能会导致困扰。 我记得在一个项目里与一位用户交谈,这位用户坦白的说,虽然刚开始他能够明了使用用例为何,然而他现在却感到迷失在一大堆抽象的使用用例中。 「当开发者对我解说它们 ( 使用用例 ) 时我认为我可以理解,但是到最后我却不能够记得这些说明内容。」使用用例主要的目的之一是与系统的用户 – - 客户 -- 作沟通,抽象化的使用用例已经超越可被理解的程度,所以对任何人都毫无帮助。 由于内部的结构不需要与外部的结构相同,所以在使用用例中缺乏抽象化并不会造成危害。 我不曾发现在使用用例里欠缺抽象化就会导致在其内部缺少抽象化,相反的,使用较少抽象的使用用例 (abstract use cases) 对我们反而有所帮助,因为一个抽象内部的结构会对映到一个具体的使用用例 (concrete use case) ,以协助我们去了解抽象化。
抽象化的 (abstracting) 使用用例也有可能导致更庞大的使用用例,换言之,在一个反复式开发过程中,会造成规划工作益加困难。 你可能要花费更多的时间去推导出 (arguing) 抽象化 – 而这些时间你最好是用在其他地方。
所以我的建议是去违反抽象化的原则,而尽量让使用用例具体化 ( 译 注 [ 三 ] ) 。 有关上面所述及的,重点在于不要做过多的抽象并让你的用户得以领会,使用具体的使用用例进行解说和确认你效用无穷的抽象化作业。
滥用图型用户界面 (Abuse by GUI)
尽管图型绘制工具问世不久,大多数的人们正使用这些图型绘制工具来协助自己决定使用用例,理论上是动人的。 GUI 对用户而言可以更具体化; 它协助我们清除容易忽略 (easy-to-forget) 细节的障碍; 它让用户可以明了系统看起来应该像什么; GUI 使我们容易去塑造原型; 它们合理的展示并解说系统的特征及功能。
由于上述的种种理由,我过去曾认为 GUI 原型是一种良好的需求 (requirements) 工具,但是仍存在一些根本上的问题。 当你向用户展示 GUI 原型时,就算那只是形同在戏剧后 台 留下少量的线路,但它看起来就像是近乎完成品。 当然,我们知道那是潜在于后 台 里面极为复杂练习的一部分,但这是非常难以让客户了解。 GUI 导致不正确的进度指示且难以掌握 – 它也许可以正确的将一个按钮摆放在图形界面 (UI) 上,既使如此可能也需花费几星期的努力。 有一条鸿沟介于实际上的努力与感知上的努力 之间, 使得它难以去成功的跨越,那也就是说将它控制在某个范围里是重要的。
尽管事实上 GUI 工具似乎是如此容易使用,而且总是有许多细微的调整功能使得它看起来正确无误。 这些美好的调整功能在某些方面也限制了人们的预期,造成当一个简单的设计构想 浮 现时却很难处理这些改变。
所以我现在告诉人们,如果无法完全写好代码之前不要展示任何 GUI ,使用笔 和纸所绘制 出来的 GUI 原型仍不失为是好的制作方式,这种方法可以让你避免不正确的进度指示。
滥用否定可供选择的项目 (Abuse by Denying Choice)
以一个高消费阶层的 (high-end) [朱子3] 文字处理器为例 ( 译 注 [ 四 ] ) ,想象一下有关这个文字处理器的使用用例 (use-cases) 。 你大概会包含「 变更样式 」、「 应用样式 」及「 从另外一个文件 里 汇入样式 」的功能? 问题在于这些使用用例并非直接点出一个用户的需求。 用户实际上所需要的东西应该像是「 确保文件内前后格式的一致性 」,或者「 制作一份文件类似另外一份 」。 我将前者的描述视为 系统 的使用用例,而后者则视为 用户 的使用用例 ( 译 注 [ 五 ] ) 。
这个问题在于直接采取系统使用用例的举动,将否定你提供其他系统的使用用例以处理类似问题的机会。 无论如何,你不能够单单运用用户的使用用例,因为它们无法充分地符合反复式发展程序中调度 (iterative scheduling process) 所需。
对此问题我承认无法提供一个完整的答案,我将注意力放在系统的使用用例,最主要地在于他们对反复式规划及系统测试这些方面相当有用。 无论如何,伴随每 个 系统使用用例的背后,我会考虑到是否另有其他用户的使用用例存在,我对此保留一段注解,并且尝试去提供系统使用用例的替代方案。
使用用例是相当有价值的技术之一,且适合我们使用。 我长期运用它们在我的开发工作上,而且但愿我过去一开始就使用这项技术。 但是需要谨记,你使用它们有何目的,并且小心提防这些陷阱。 除此之外,期盼某些人写一本有关于「怎麽制作一个好的使用用例」的好书,至少有一位积极进取的开发者 (active developer) 确实地想要去阅读它。
|