第6章 需求 之 系统用例规约
那许多简单情节,那许多复杂表情。
《岁月》;词:沈庆,曲:沈庆,唱:沈庆;1995
用例图表达了用例的目标,但是对于完整的需求来说,这是远远不够的。用例的背后封装了不同级别的相关需求,我们需要通过书写用例规约把这些需求表达出来。用例规约就是以用例为核心来组织需求内容的需求规约,也就是说,有了用例规约,不需要另外再写“需求规约”。用例规约的各项内容用类图展示如图6-1所示。
图6-1 用例规约的内容
目前UML并未包含用例规约的表示法。过去常见的做法是用Word等文本处理器来书写用例规约,不过扁平文本形式难以高效建立、修改和维护用例各项内容之间的关系。现在已经有专门用于编写用例规约的工具,例如Case Complete、Visual Use Case等,而且越来越多的UML建模工具也开始提供编写用例规约的功能,使得需求人员能够以“立体”的方式来书写和保存用例规约,并以文本、图形、表格等各种视图查看或输出,如图6-2。
图6-2 制作用例规约的工具
图6-1的各项内容中,执行者和用例在用例图已经存在,照搬到用例规约中就可以。剩下的内容,用例图上是没有的,需要另行添加。我们逐个看看其他各项内容的要点。
6.1.1 前置条件和后置条件
用例通过前置条件(precondition)、后置条件(postcondition)以契约的形式表达需求。用例相当于系统的一个承诺:在满足前置条件时开始,按照里面的路径步骤走,系统就能到达后置条件。
前置条件:用例开始前,系统需要满足的约束。
后置条件:用例成功结束后,系统需要满足的约束。
在本书中,后置条件不再像有的方法一样分为最小后置和成功后置。用例怎样才算成功,只写出最像的那个答案。这样就避免掉入“从实现角度看这样可以那样也可以”的陷阱。
前置条件、后置条件必须是系统能检测的。
图6-3中“录入保单”用例的前置条件是错误的。业务代表是否已经把保单交给内勤,系统无法检测,不能作为前置条件;同样,“收银”用例的后置条件也是不对的。顾客是否已经带着货物离开商店,系统也无法检测,不能作为后置条件。
图6-3 系统必须能检测前置、后置条件
前置条件必须是用例开始前系统能检测到的。
如图6-4所示,储户开始取现金的交互前,系统不知道储户是谁,要取多少钱,所以“储户账户里有足够的金额”这个条件是无法检测的。
图6-4 前置条件必须是用例开始前系统能检测到的
前置后置条件是状态,不是动作。
例如,“经理→批假”的前置条件不能写“员工提交请假单”,因为是一个动作不是状态,应改为“存在待审批的请假单”。特别要注意的是,写成“员工已经提交请假单”也很可能是不对的,因为状态和导致达到某个状态的行为不是一一对应的,请假单未必是员工自己提交,也可以组长负责帮本组人员请假,也可能是从另外的系统批量导入。
如果分不清楚状态和行为的区别,建模就会遇到很大的麻烦。后面的建模工作中,还会不断讨论状态和行为的问题。
前置后置条件要用核心域词汇描述。
“系统正常运行”、“网络连接正常”等放之四海而皆准的约束,和所研究系统没有特定关系,不需要在前置条件中写出来,否则会得到一堆没有任何增值作用的废话。
后置条件也不能简单地把用例的名字加上“成功”二字变成“××成功”。例如,用例“顾客→下单”的后置条件写“顾客已经成功下单”,这不是废话吗。
“已登录”不应作为前置条件。
一些用例规约会有这样的前置条件:××已经登录。下面花一些篇幅来讨论这样做是否合适。
以购物网站为研究对象,登录不是用例。这一点在第5章已经阐述过了。如何处理登录?在过去的不少书和文章里可以看到如图6-5的做法:
图6-5 画法一:把其他用例作为登录的扩展
会员登录以后可以下单,也可以查看以往订单,还可以退货……所以图6-5把下单、查看以往订单画成登录的扩展。这是错的。并不是先做A然后做B,B就成了A的扩展。例如,张三先吃饭,然后可能看电视,也可能上厕所,也可能散步。如果把看电视、上厕所、散步画成吃饭的扩展,意思就成了“张三可能会以上厕所的方式吃饭”或 “上厕所是张三达到吃饭目标的一条路径”。
第二种做法如图6-6,把登录变成被其他用例包含(Include)的被包含用例(Included Use Case)。这样做是正确的。登录用例本来不存在,后来在写用例规约的时候,发现下单、查看以往订单等用例里都有以下步骤集合:
1. 会员提交身份信息
2. 系统验证身份信息
3. 系统保存会员登录信息
4. 系统反馈会员定制界面
为了节省书写用例规约的工作量,考虑把这些形成一个小目标的步骤集合(不是单个步骤)分离出来,作为一个被包含用例单独编写规约。这个用例只被其他用例包含,不由主执行者指向。被包含用例的这个特点和类的私有操作很相似。
图6-6 画法二:把登录作为被包含用例
按照图6-6的做法,下单用例规约的步骤集合里,应该有表示包含登录用例的步骤集合:会员【登录】(“登录”二字加了粗括号表示这是一个被包含用例,它的步骤和约束在另外的地方描述。不喜欢括号可以用加下划线等其他方法以示区分)。有些人觉得这样做的话,好些用例里会出现会员【登录】,看起来有些碍眼,就想能不能把它提到前置条件里,那就得到了第三种:把登录作为一个用例,会员已经登录作为其他用例的前置条件,如图6-7。这样用例的步骤看起来更清爽,但是严格来说这也是不对的,登录不是购物网站的用例。
图6-7 画法三:其他用例以“已登录”作为前置条件
可能有的人会觉得第三种画法更好,理由是从最终实现上看,会员登录以后可以下单,可以查看以往订单,不用再重新登录,看起来是不是第三种更合理?其实还是第二种更合理。第5章说过,如果在做需求时考虑到复用,可能已经陷入了设计的思维。能够在多个用例中复用登录的状态,这是设计人员的本事,他甚至还可以做到10个用例的界面都从一个模板生成,但不能因此就把10个用例合并成一个。
认为第三种更好的另一个理由也和“复用”有关:当几个执行者在使用某些用例时都会有登录的步骤集合,把登录单独分离出来,可以抽象出一个用户执行者,指向登录,如图6-8。
图6-8 错误:想通过泛化执行者复用用例
这个看起来正确,实际上也是不对的。不同的涉众利益会带来不同的需求。这样做,潜意识里就有着一种追求“需求复用”的思想,会诱导需求人员对不同用例之间的微妙差别视而不见,这对于做需求来说是危险的。
会员登录需要验证码,货管员登录时不需要。系统反馈给会员的是未完成的订单,反馈给货管员的则是最近货品的动态。会员登录时可能要求反馈速度很快,而且允许百万会员同时在线,货管员则没有那么严格。这些需求差异不能视而不见。
更合理的做法如图6-9,分成几个不同的被包含用例。
图6-9 分成不同的被包含用例,较正确
被包含用例(以及扩展用例)严格来说不能算用例,应该有更好的名字,例如“交互片段”,否则名称中带的”用例“二字容易误导开发人员从实现的角度定义用例,而不是从对外提供价值的角度。
6.1.2 涉众利益
前置条件是起点,后置条件是终点,中间的路该怎么走?这就要由涉众利益决定了。只有目标,没有考虑到涉众利益,不足以得到正确的需求。
假设我需要1000元现金。为了达到这个目的,首先我会拉开家里的抽屉,如果里面有超过1000元的现金,我就从中拿1000元;如果抽屉里没有现金或者现金不够,我就拿上银行卡,到楼下ATM去取。问题来了:同样的目标,为什么家里的抽屉拉开就可以达到,而楼下的ATM却要插卡输密码?
图6-10 目标一样,抽屉和ATM的交互却大不相同
背后的原因是涉众利益不同。涉众利益即针对某件事情,某类人担心什么和希望什么。家里的抽屉只涉及到我和家人的利益,如果我和家人和睦相处,拉开抽屉就可以拿;反之,如果我和家人的利益冲突得非常厉害,那么可能需要买一种长得很像ATM的抽屉才符合我家的需要。
对于银行ATM来说,就不是这样了。储户在ATM取现金时,涉及的涉众利益如下:
储户—希望方便;担心权益受损。
银行负责人—希望安全;希望节约运营成本。
正是在这些涉众利益的交锋之下,目前我们日常生活中所看到的ATM的用例片断如下:
基本路径
1. 储户提交账户号码
2. 系统验证账户号码合法
3. 系统提示输入密码
4. 储户输入密码
5. 系统验证密码合法、正确
6. 系统提示输入取现金额
7. 储户输入取现金额
8. 系统验证取现金额合法
9. 系统记录取现信息,更新账户余额
……
业务规则
5. 密码为6位数字
8. 取现金额应为100元的倍数;取现金额应少于账户余额;单次取现金额不超过3000元;当日取现金额不超过20000元
设计约束
1. 通过磁条卡或芯片卡提交账户号码
我们来看看涉众利益的交锋如何影响了需求。步骤1有设计约束通过磁条卡或芯片卡提交账户号码,这是为了照顾储户“方便”的涉众利益。
既然为了储户方便,还验密码干什么?银行不是口口声声说储户是上帝吗?为什么不在储户提交账户号码后,把钱箱弹出来让储户随便取?这是为了照顾银行“安全”的利益。
既然设密码是为了安全,密码长度怎么只要6位呢?何不设10位或更长的密码?这又是“安全”和“方便”交锋后的妥协。可以想像,如果有一天,“ATM黑客”魔高一丈,6位密码很容易攻破,这条规则可能就会变成密码为8位数字。
系统记录取现信息,更新账户余额。这是储户看不见摸不着的,为什么要写出来?因为要是系统不做这件事,银行就吃亏了。
业务规则取现金额应为100元的倍数……如果能有1元2元的ATM,储户会非常高兴(没有零钱坐公交车?到ATM那里取去!),但银行不高兴――成本太高了。
业务规则单次取现金额不超过3000元;当日取现金额不超过20000元。这主要是考虑银行节约运营成本的利益。当然,银行的解释会是为了保护储户的利益,防止被冒领等等。
认识到需求由涉众利益的冲突和平衡来决定,我们的需求过程就会充满“人”的味道,变得乐趣横生。扩展开来看,我们为什么在现在的公司工作,为什么选择现在的配偶,甚至午餐吃什么,都是权衡了各种涉众利益的结果。
为了寻找用例的涉众,可以用“醉酒法”思考。假设台上的演员“喝醉”(“喝醉”加了引号是因为在台上的未必是人)了在台上表演,谁看到这个场面会担心自己的直接利益受到侵害?担心的人就是这个用例的涉众。一般来自以下这些地方:
涉众来源一:人类执行者
用例的执行者如果是人类,当然是用例的涉众。执行者如果不是人类,就不是涉众,因为没有利益主张。如图6-11,仓管员是人类,所以是涉众,而ERP系统不是人类,不是涉众。
图6-11 非人类执行者不是涉众
涉众来源二:上游
执行者要使用系统做某个用例,可能会需要一些资源,这些资源的提供者很可能是该用例的涉众。如图6-12,保单由业务代表提供给内勤。如果内勤喝醉了酒乱录,信息错得一塌糊涂,业务代表的利益就被损害了。考虑上游之后,录入保单用例的涉众如图6-12所示。
图6-12 录入保单用例考虑上游之后
涉众来源三:下游
执行者使用系统做某个用例,产生的后果会影响到其他人。受影响的这些人也是涉众。还是图6-12录入保单的例子,如果系统做得不好,没有检测内勤录保单时是否填了必填项就放了过去,后面负责审核的经理工作量增加了。考虑上游和下游之后,录入保单用例的涉众如图6-13所示。
图6-13 录入保单用例考虑上游、下游之后
还有,在图6-11中,ERP系统虽然不是接收到货通知用例的涉众,但这个用例产生的结果会影响到ERP系统背后的人。例如仓储管理系统不停向ERP系统发垃圾数据包,导致ERP系统瘫痪,ERP系统维护人员的工作量就增加了。所以ERP系统维护人员也是下游的涉众。考虑下游之后,接收到货通知用例的涉众如图6-14所示。
图6-14 接收到货通知用例考虑下游之后
涉众来源四:信息的主人
用例会用到一些信息,这些信息可能会涉及到某些人。虽然这些人也许并不知道这个系统的存在,但他们是用例的涉众。如图6-12中录入保单的用例,保单的信息涉及到了被保人、投保人和受益人,如果信息出错或泄漏,这些人就会遭殃,所以他们是涉众。这类涉众因为“远在天边”,在寻找涉众时比较容易被忽略,要特别注意。考虑上游、下游和信息的主人之后,录入保单用例的涉众如图6-15所示。
图6-15 录入保单用例考虑上游、下游和信息的主人之后
说到这里,也许您已经看出来,业务建模对于识别涉众是非常有帮助的。如果我们在需求之前有业务建模工作流,会更了解一件事情的前因后果,大多数涉众都能够从业务序列图中看出来。例如,从图6-16的业务序列图中,很容易找到“内勤→录入保单”的涉众。
图6-16 业务建模可以帮助寻找涉众
最后回答一个经常被问到的问题:系统的研发团队成员是该系统的涉众吗?不是。根据第2章所说的“投币法”,系统是投币得来的,考虑需求时根本没有“研发团队”。这里的意思并不是说工作性质为软件开发的人就不能当涉众,当他充当投币者的角色时是可以的。例如,以建模工具EA为所研究系统时,使用EA来建模的软件研发团队成员就是EA的涉众,但EA的研发团队成员不是涉众。
寻找涉众利益
寻找涉众利益时,要“亲兄弟,明算账”,把不同涉众各自关注的利益体现出来,而不是写成一模一样。家里两夫妻对同一件事情都还有不同的立场,更不用说一个组织里面形形色色的涉众了。
司机开车进厂装化肥,工作人员通过地磅系统操纵地磅给车称重。针对这件事情,不同的涉众可谓是“各怀鬼胎”:
化肥公司老板―担心公司内部人员贪污;
地磅操作员―希望操作简便;担心承担责任;担心系统坏掉影响工作量;
仓管人员―担心称不准导致无谓的返工装包;
买主―担心进去时称得轻了,出来时称得重了,导致给少了化肥;
司机―担心等侯时间太长导致每天拉货次数减少;
即使有些利益有时不方便白纸黑字写出来共享,但至少建模人员要心知肚明,不能一团和气了事。
涉众利益要通过仔细观察和揣摩涉众的痛苦得到。如果没有细心调研,往往写出来的涉众利益很苍白。例如,前面提到的“储户→取现金”用例,涉众利益写“储户担心取不到现金”,这就是废话了。
再看下面这个例子,一个输液系统,涉众利益写:
护士—担心出错。
正确的废话,谁不担心出错,但为什么还是出错?仔细调研过之后写出来就生动多了:
护士—担心自己的药理学知识记错,对药物名称相近的药物计算错剂量,导致给药错误。
善于积累涉众利益
需求是不断变化的,新系统肯定在功能或性能上和旧系统有所不同,否则还做什么新系统?但是,背后的涉众利益要稳定得多。我们来看之前ATM例子中出现的涉众利益:
储户——希望方便;担心权益受损
银行负责人——希望安全;希望节约运营成本
这些涉众利益不止适用于ATM,也适用于清朝的钱庄柜台、现在的银行柜台、网上银行和手机银行。
图6-17 系统的具体形态变了,涉众利益不变
很多需求中的两难,都是因为信息不足导致的。如果我们能善于积累涉众利益,把目标组织内部各种人的小算盘搞得一清二楚,对方稍微说句话,我们就已经知道他心里的小九九,而且还知道他的要求对谁有利,对谁有害,从而可以自如应对。
6.1.3 基本路径
观众已经一排排坐好,接下来就要让演员们上台演戏了。把演戏的场景描述出来,就得到了用例的路径和步骤。
一个用例会有多个场景,其中有一个场景描述了最成功的情况,执行者和系统的交互非常顺利,一路绿灯直抵用例的后置条件。这个场景称为基本路径。
用例把基本路径分离出来先写,目的是凸现用例的核心价值。还是以上面的ATM为例,发生在ATM上的场景有很多:
(1)张三来了,插卡,输密码,输金额,顺顺利利取到钱,高兴地走了;
(2)李四来了,插卡,输密码,密码错,再输,再错,再输,卡被吞掉了;
(3)王五来了,插卡,输密码,输金额,今天取得太多不能取了……
只有场景(1)是银行在大街上摆放一台ATM的初衷。虽然场景(2)(3)是难以避免的,但场景(1)出现得越多越好,这是涉众对ATM的期望。
书写路径步骤的时候需要注意的一些要点如下。这些要点有重叠的地方,如果违反了其中一个要点,很可能也会违反另外的要点。
6.1.3.1 按照交互四步曲书写
执行者和系统按照回合交互,直到达成目的。需要的回合数是不定的,可能一个回合足够,也可能需要多个回合。一个回合中的步骤分为四类:请求、验证、改变、回应,如图6-18所示。
图6-18 交互四步曲
这四类步骤中,请求是必须的,其他三类步骤在某些回合中可以缺少其中一部分,但不能都缺,否则就不成为回合了。图6-19展示了一个例子。可以看到,第一个回合缺少验证和改变,第二个回合有四类步骤都有。
图6-19 回合制的交互示例
在时间作为主执行者,还不需要和其他辅执行者交互的用例中,可能会出现缺少回应的情况,而且只有一个回合。例如:
当到达时间周期时,
系统根据当前各因子值计算并保存评分。
6.1.3.2 使用主动语句理清责任
把动作的责任人放在主语的位置,用Cockburn的话说就是“球在哪里”[Cockburn 2001]。比较下面两句话:
伊布从瓦伦西亚处得到传球,舒梅切尔扑救伊布的射门……
瓦伦西亚传球,伊布射门,舒梅切尔扑救……
虽然上面一句比较文艺,但下面一句把责任理得更清晰。用例步骤也是如此:
系统从会员处获取用户名和密码(错)
会员提交用户名和密码(对)
用户名和密码被验证(错)
系统验证用户名和密码(对)
会员要是不提交,就不要怪系统没有动静;会员要是提交了,系统不动弹,那就要怪系统了。
做到规规矩矩说话,把责任理清楚,其实也不容易。经常有人会写:
会员保存订单
会员在哪里保存订单?存在自己的肚子里?应该是:
会员提交订单信息
系统保存订单
类似的还有:
会员查询商品
会员在自己肚子里面查?应该是:
会员提交查询条件
系统查询商品
系统反馈查询结果
http://www.umlchina.com/training/course170909.htm
9月9-10日(周六日)上海软件需求设计UML全程实作公开课