6章 需求 之 系统用例规约

那许多简单情节,那许多复杂表情。

《岁月》;词:沈庆,曲:沈庆,唱:沈庆;1995

 

6.1 用例规约的内容

用例图表达了用例的目标,但是对于完整的需求来说,这是远远不够的。用例的背后封装了不同级别的相关需求,我们需要通过书写用例规约把这些需求表达出来。用例规约就是以用例为核心来组织需求内容的需求规约,也就是说,有了用例规约,不需要另外再写需求规约。用例规约的各项内容用类图展示如图6-1所示。

6-1 用例规约的内容

6-1的各项内容中,执行者和用例在用例图中已经存在,照搬到用例规约中就可以。剩下的内容用例图上没有,需要另行添加。目前UML并未包含用例规约的表示法。过去常见的做法是用Word等文字处理器来书写用例规约,不过扁平文本形式难以高效建立和维护用例各项内容之间的关系。现在已经有专门用于编写用例规约的工具,例如Case CompleteVisual Use Case等,而且越来越多的UML建模工具也开始提供编写用例规约的功能,使得需求人员能够以“立体”的方式来书写和保存用例规约,并以文本、图形、表格等各种视图查看或输出,如图6-2

6-2 制作用例规约的工具

接下来,我们逐个看看除了用例名称和执行者之外的其他各项内容的要点。

6.1.1 前置条件和后置条件

用例通过前置条件(precondition)、后置条件(postcondition)以契约的形式表达需求。用例相当于系统的一个承诺:在满足前置条件时开始,按照里面的路径步骤走,系统就能到达后置条件。

前置条件:用例开始前,系统需要满足的约束。

后置条件:用例成功结束后,系统需要满足的约束。

在本书中,后置条件不再像有的方法一样分为最小后置条件和成功后置条件,只写出最想要的那个状态,这样就避免掉入“从实现角度看这样可以那样也可以”的陷阱。

前置条件、后置条件必须是系统能检测的。

6-3中“录入保单”用例的前置条件是错误的。业务代表是否已经把保单交给内勤,系统无法检测,不能作为前置条件;同样,“收银”用例的后置条件也是不对的。顾客是否已经带着货物离开商店,系统也无法检测,不能作为后置条件。

6-3 系统必须能检测前置、后置条件

前置条件必须是用例开始前系统能检测到的。

如图6-4所示,储户开始取现金的交互前,系统不知道储户是谁,要取多少钱,所以无法检测“储户账户里有足够的金额”这个条件。

6-4 前置条件必须是用例开始前系统能检测到的

前置后置条件是状态,不是动作。

例如,“经理批假的前置条件不能写“员工提交请假单,因为是一个动作不是状态,应改为“存在待审批的请假单”。特别要注意的是,写成员工已经提交请假单很可能也是不对的,因为状态和导致达到某个状态的行为不是一一对应的,请假单未必是员工自己提交,也可以组长负责帮本组人员请假,也可能是从另外的系统批量导入。

如果分不清楚状态和行为的区别,建模就会遇到很大的麻烦。后面的建模工作中,还会不断讨论状态和行为的问题。

前置后置条件要用核心域词汇描述。

“系统正常运行”、“网络连接正常”等放之四海而皆准的约束,和所研究系统没有特定关系,不需要在前置条件中写出来,否则会得到一堆没有任何增值作用的废话。

后置条件也不能简单地把用例的名字加上成功二字变成××成功。例如,用例“顾客下单的后置条件写顾客已经成功下单,这不是废话吗?更合适的后置条件是订单信息已保存

“已登录”不应作为前置条件。

一些用例规约会有这样的前置条件:××已经登录。下面花一些篇幅来讨论这样做是否合适。

以购物网站为研究对象,登录不是用例。这一点在第5章已经阐述过了。如何处理登录?在过去的不少书和文章里可以看到如图6-5的做法:

6-5 画法一:把其他用例作为登录的扩展

会员登录以后可以下单,也可以查看以往订单,还可以退货……所以图6-5下单查看以往订单画成登录的扩展。这是错的。并不是先做A然后可能做BCBC就成了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元的倍数……如果能有12元的ATM,储户会非常高兴(没有零钱坐公交车?到ATM那里取去!),但银行不高兴――成本太高了。业务规则单次取现金额不超过3000元;当日取现金额不超过20000。这同样是考虑银行节约运营成本的利益。当然,银行方的解释会是为了保护储户的利益,防止被冒领等等。

系统记录取现信息,更新账户余额。这是储户看不见摸不着的,为什么要写出来?因为要是系统不做这件事,银行方就吃亏了。

认识到需求由涉众利益的冲突和平衡来决定,我们的需求过程就会充满“人”的味道,变得乐趣横生。扩展开来看,我们为什么在现在的公司工作,为什么选择现在的配偶,甚至午餐吃什么,都是权衡了各种涉众利益的结果。

为了寻找用例的涉众,可以用“醉酒法思考。假设台上的演员“喝醉”(“喝醉”加了引号是因为在台上的未必是人)了在台上表演,谁看到这个场面会担心自己的直接利益受到侵害?担心的人就是这个用例的涉众。主要有以下来源:

涉众来源一:人类执行者

用例的执行者如果是人类,当然是用例的涉众。执行者如果不是人类,就不是涉众,因为它没有利益主张。针对图6-11保险系统内勤录入保单用例,内勤是人类,是涉众,而OA系统不是人类,不是涉众。

6-11 考虑人类执行者之后的涉众

涉众来源二:上游

执行者要使用系统做某个用例,可能会需要一些资源,这些资源的提供者很可能是该用例的涉众。还是以“内勤→录入保单”为例,保单由业务代表提供给内勤。如果内勤喝醉了酒乱录,信息错得一塌糊涂,业务代表的利益就被损害了。考虑上游之后,“内勤→录入保单”用例的涉众如图6-12所示。

6-12 考虑上游之后的涉众

涉众来源三:下游

执行者使用系统做某个用例,产生的后果会影响到其他人。受影响的这些人也是涉众。还是以“内勤→录入保单”为例,如果系统做得不好,没有检测内勤录保单时是否填了必填项就放了过去,后面负责审核的经理工作量增加了。

还有,OA系统虽然不是“内勤→录入保单”用例的涉众,但这个用例产生的结果会影响到OA系统背后的人。假如保险系统不停向OA系统发垃圾数据包,导致OA系统瘫痪,OA系统维护人员的工作量就增加了。所以OA系统维护人员也是下游的涉众。考虑下游之后,“内勤→录入保单”用例的涉众如图6-13所示。

6-13 考虑下游之后的涉众

涉众来源四:信息的主人

用例会用到一些信息,这些信息可能会涉及到某些人。虽然这些人也许并不知道这个系统的存在,但他们是用例的涉众。还是以“内勤→录入保单”为例,保单的信息涉及到了被保人投保人受益人,如果信息出错或泄漏,这些人就会遭殃,所以他们是涉众。因为这类涉众可能和系统没有接口,比较容易被忽略,要特别注意。考虑信息的主人之后,“内勤→录入保单”用例的涉众如图6-14所示。

6-14 考虑信息的主人之后的涉众

说到这里,也许您已经看出来,业务建模对于识别涉众非常有帮助。如果我们在需求之前做了业务建模,会更了解一件事情的前因后果,大多数涉众都能够从业务序列图中看出来。例如,从图6-15的业务序列图中,业务对象内勤、业务代表、经理投保人很容易看出来。另外,消息参数“保单信息”提醒建模人员还会涉及被保人受益人。这样,图6-14中所列举的内勤录入保单的涉众就找齐了。

6-15 业务建模可以帮助寻找涉众

最后回答一个经常被问到的问题:系统的研发团队成员是该系统的涉众吗?不是。根据第2章所说的投币法,系统是投币得来的,考虑需求时根本没有“研发团队”。这里的意思并不是说工作性质为软件开发的人就不能当涉众,当他充当投币者的角色时是可以的。例如,以建模工具EA为所研究系统时,使用EA来建模的软件研发团队成员是涉众,但EA的研发团队成员不是涉众。

寻找涉众利益

寻找涉众利益时,要“亲兄弟,明算账,把不同涉众各自关注的利益体现出来,而不是写成一模一样。家里两夫妻对同一件事情都还有不同的立场,更不用说一个组织里面形形色色的涉众了。

司机开车进厂装化肥,工作人员通过地磅系统操纵地磅给车称重。针对这件事情,不同的涉众可谓是“各怀鬼胎”:

化肥公司老板―担心公司内部人员贪污

地磅操作员―希望操作简便;担心承担责任;担心系统坏掉影响工作量

仓管人员―担心称不准导致无谓的返工装包

买主―担心进去时称得轻了,出来时称得重了,导致给少了化肥

司机―担心等侯时间太长导致每天拉货次数减少

即使有些利益有时不方便白纸黑字写出来共享,但至少建模人员要心知肚明,不能一团和气了事。

建模人员要仔细观察和揣摩涉众的痛苦,才能找到真正的涉众利益,否则写出来的“涉众利益”往往很苍白。例如,前面提到的储户取现金用例,涉众利益写储户担心取不到现金,这就是废话了。

再看下面这个例子,一个输液系统,涉众利益写:

护士—担心出错

正确的废话,谁不担心出错,但为什么还是出错?仔细调研过之后写出来就生动多了:

护士—担心自己的药理学知识记错,对药物名称相近的药物计算错剂量,导致给药错误

善于积累涉众利益

需求是不断变化的,新系统肯定在功能或性能上和旧系统有所不同,否则还做什么新系统?但是,背后的涉众利益要稳定得多。我们来看之前ATM例子中出现的涉众利益:

储户—希望方便;担心权益受损

银行负责人—希望安全;希望节约运营成本

这些涉众利益不止适用于ATM,也适用于清朝的钱庄柜台、现在的银行柜台、网上银行和手机银行。

6-16 系统的具体形态变了,涉众利益不变

很多需求中的两难,都是因为信息不足导致的。如果我们能善于积累涉众利益,把目标组织内部各种人的小算盘搞得一清二楚,对方稍微说句话,我们就已经知道他心里的小九九,而且还知道他的要求对谁有利,对谁有害,从而可以自如应对。

6.1.3 基本路径

观众已经一排排坐好,接下来就要让演员们上台演戏了。把演戏的场景描述出来,就得到了用例的路径和步骤。

一个用例会有多个场景,其中有一个场景描述了最成功的情况,执行者和系统的交互非常顺利,一路绿灯直抵用例的后置条件。这个场景称为基本路径。

用例把基本路径分离出来先写,目的是凸现用例的核心价值。还是以上面的ATM为例,发生在ATM上的场景有很多:

1)张三来了,插卡,输密码,输金额,顺顺利利取到钱,高兴地走了;

2)李四来了,插卡,输密码,密码错,再输,再错,再输,卡被吞掉了;

3)王五来了,插卡,输密码,输金额,今天取得太多不能取了……

只有场景(1)是银行在大街上摆放一台ATM的初衷。虽然场景(2)(3)是难以避免的,但场景(1)出现得越多越好,这是涉众对ATM的期望。

书写路径步骤的时候需要注意以下一些要点。这些要点有重叠的地方,如果违反了其中一个要点,很可能也会违反另外的要点。

6.1.3.1 按照交互四步曲书写

执行者和系统按照回合交互,直到达成目的。需要的回合数是不定的,可能一个回合足够,也可能需要多个回合。一个回合中的步骤分为四类:请求、验证、改变、回应,如图6-17所示。

6-17 交互四步曲

这四类步骤中,请求是必须的,其他三类步骤在某些回合中可以缺少其中一部分,但不能都缺少,否则就不成为回合了。图6-18展示了一个例子。可以看到,第一个回合缺少验证和改变,第二个回合则四类步骤都有。

6-18 回合制的交互示例

时间作为主执行者而且不需要和其他辅执行者交互的用例中,可能会出现缺少回应的情况,而且只有一个回合。例如:

当到达时间周期时                     请求

系统根据当前各因子值计算并保存评分   改变

在书写步骤时要注意以下一些形式上的问题:

1)对于时间为主执行者的用例,回合中的请求步骤不写时间告知时间周期到了,而是写当到达时间周期时

2)验证步骤不写“是否。例如图6-18中,第4步写系统验证注册信息充分,不写系统验证注册信息是否充分,目的是要表达充分是基本路径期望的验证结果。

3)系统和辅执行者之间的交互可以看作是一种回应步骤,写成“系统请求辅执行者做某事,例如:

系统请求邮件列表系统群发邮件

但是要注意写到这里足够了,不能再往下写:

邮件列表系统群发邮件

这已经不属于系统能感知和承诺的责任范围,不能写在步骤中,否则就会让人以为只要用上了这个系统,这些事情就一定会发生。这样就模糊了系统的契约,也抹杀了进一步改进的可能。

例如以下规约片断:

……

4. 系统反馈应收总金额

5. 顾客付款

6. 收银员提交付款方式和金额

7. 系统计算找零金额

8. 系统反馈找零金额,打印收据

9. 收银员找零

……

顾客付款收银员找零是系统无法感知和承诺的。如果写在步骤里,会让人产生误解:只要用了本系统顾客就会乖乖付款,收银员会乖乖找零——也许顾客忘记付款和收银员忘记找零正是商场要解决的一个头痛问题。

6.1.3.2 使用主动语句理清责任

把动作的责任人放在主语的位置,用Cockburn的话说就是“球在哪里”[Cockburn 2001]。比较下面两句话:

伊布从瓦伦西亚处得到传球,舒梅切尔扑救伊布的射门

瓦伦西亚传球,伊布射门,舒梅切尔扑救

虽然上面一句比较文艺,但下面一句把责任理得更清晰。用例步骤也是如此:

系统从会员处获取用户名和密码(错)

会员提交用户名和密码(对)

用户名和密码被验证(错)

系统验证用户名和密码(对)

会员要是不提交,就不要怪系统没有动静;会员要是提交了,系统不动弹,那就要怪系统了。

做到规规矩矩说话,把责任理清楚,其实不容易。经常有人会写:

会员保存订单

会员在哪里保存订单?存在自己的肚子里?应该是:

会员提交订单信息

系统保存订单

类似的还有:

会员查询商品

会员在自己肚子里面查?应该是:

会员提交查询条件

系统查询商品

系统反馈查询结果

再列举一些常见的“胡说八道”如下表:

错误示例

评价

会员打开系统

用手撕开?

会员进入系统

像黑客帝国一样脑门插插头进去?

系统自动计算订单总价

系统当然是自动的

会员手动输入订单信息

会员当然是手动的

会员提交订单信息给系统

“给系统冗余

6.1.3.3 主语只能是主执行者或系统

写需求,就是要把系统当作一个黑箱,描述它对外提供的功能以及功能附带的质量需求。系统如何构造,不属于需求描述的范围,除非是涉众强加的设计约束。所以步骤里不能出现“执行者请求前端系统做某事,前端系统请求后端系统做某事”、“执行者请求客户端做某事,客户端请求服务器做某事”、“执行者请求A子系统做某事,A子系统请求B子系统做某事”,就算这个系统最终的组成是分解成很多个部分,分布在一百多个国家运行,需求里也只有两个字:系统。前文已经说过,系统边界是责任边界,而非物理边界。

6-19 需求把系统看作黑箱

可能会有人问如果我要做的就只是客户端,难道不能写客户端请求服务器做某事吗?,这时所谓的客户端就是所研究的系统,而原来所认为的服务器已经是系统责任边界之外的一个外系统,用例规约里仍然不会有客户端的字样出现。

6.1.3.4 使用核心域术语描述

路径步骤应该使用核心域的术语来描述,也就是说,要说“人话”。

以一个零件采购系统为研究对象,比较以下两句话,哪一句是“人话”,哪一句是“鸟语”?

1)系统建立连接,打开连接,执行SQL语句,从“零件”表查询…

2)系统根据查询条件搜索零件

您一眼就能看出来,第一句是“鸟语”,第二句是“人话”。关键在于:为什么说第一句是“鸟语”?可能有的人说,因为第一句涉及到了技术,所以是“鸟语”。这个解释是有问题的。第2章我们说到要从开发人员使用的术语中删去“用户”二字,现在我们还要删去“技术”二字。

之前在第3章中也已经提到,不少开发人员说到“技术”的时候,含义就是“我懂的或感兴趣的那点东西”,不懂且不感兴趣的就称为“业务”,甚至“忽悠”。例如,程序员认为“Java编码是技术,需求人员做的是业务”;需求人员则认为“业务建模、需求也有很多技能,我做的当然是技术,我们的客户——医院的医生做的才是业务”;医院的医生更是嗤之以鼻“老子一个月拿的红包比你一年薪水都多,还说我做的不是技术?”。其实大家做的都是“技术”,只是领域不同而已。应该用“核心域”、“非核心域”来代替“业务”、“技术”。关于核心域和非核心域,本书在第8章还会深入讲解。

如果所研究系统是一个关系数据库的脚本工具,核心域是关系数据库领域,上面提到的“系统建立连接,打开连接,执行SQL语句”就成了合适的需求。

如果所研究系统仍然是零件采购系统,不过前排涉众强制要求“一定要这样实现”,那么“系统建立连接,打开连接,执行SQL语句”也是需求,但不能写在路径步骤里,只能写在设计约束里。

6.1.3.5 不要涉及界面细节

很多人写需求的时候,会把界面细节带进来,例如:

会员从下拉框中选择类别

会员在文本框中输入查询条件

会员单击“确定”按钮

这些界面细节很可能不是需求,只是开发人员选择的设计方案,应该把它们删掉,然后问“为什么”,找到背后隐藏的真正的需求,也许是可用性需求操作次数不超过5次”,也许是性能需求“反馈速度应该在3秒以内”,也许两者兼有。

如果前排涉众明确要求一定要用下拉框,那么下拉框也是需求,但是依然不能写会员从下拉框中选择类别,因为这里涉及到了两类需求,它们的稳定性和变化趋势不同,应该分开描述:

会员选择类别(这是步骤)

通过下拉框来实现(这是设计约束)

如图6-20所示,用例的需求组织方式是分层的,从用例到路径、步骤、约束,需求的稳定性越来越低。这样,稳定和不稳定的需求就分开了。平时遇到的大部分“需求变更”发生在补充约束级别,例如输入会员信息时加个微信字段(字段列表变了)、调整结账时的打折规则(业务规则变了)。级别越高的需求,内容越稳定。

6-20 用例规约的需求层级

要避免步骤里出现界面的细节,要点是把和核心域无关的内容清除掉,得到基于核心域视角的描述,例如:

系统显示订单信息→系统反馈订单信息

系统反馈查询到的商品列表→系统反馈查询到的商品

会员拖动商品到购物篮,勾选优惠券,点击结算按钮→会员输入商品和优惠券信息,请求结算

前面说到“建立连接,打开连接,执行SQL语句”不是零件销售系统的需求,可能大家觉得比较好理解,毕竟发生在系统“里面”,看不见,但是步骤中出现界面细节“点击确定按钮的时候,有的人就觉得这样写好像可以,因为看得见!在需求规约中,在每个用例最后贴一张或几张界面图,大家也觉得很正常。

需求判断的标准不是涉众是否看得见,而是涉众是否在意,否则盲人怎么办?两个非人系统交互怎么办?像之前ATM的例子中,涉众看不见“系统记录取现信息,更新账户余额”,但是在意,必须写,而“点击确定按钮即使看得见,也不能写,因为涉众不在意。

界面组件和数据库组件一样,都是系统设计的一部分。以人体作为例子,藏在人体内部的心脏是人的设计,露在外面的眼睛也同样是设计。人体系统的需求是能看,未必需要单独分出“眼睛”这样一个器官。如果有一种新人类,没有分出眼睛耳朵鼻子等器官,只是头上有一个360°接收器接收各种声光和气味信号并传到内部处理,没准这样的新人类更适合在这个社会上生存。另外,人光有眼睛这个输入设备,没有血液循环系统和神经系统的帮忙,也无法让大脑感知到外部信息,达到“能看的目的。第一章已经说过,需求和设计不是一一对应,而是多对多的。

可能有的人会想,没有眼睛耳朵鼻子,那还算是人吗?正常的人不应该是那样的吗?如果先入为主这样想,那就不用做需求了,直接复制现有系统的文件就行了嘛。新系统要打败旧系统,肯定要和旧系统在某些方面不同。特别是,软件系统的“新”和实物的“新要求还不一样。对于实物,我们可以说这瓶酒喝完了,开瓶新的,而以软件系统的标准看,另一种型号的、口感或包装不同的酒才能算""

6.1.3.6 不要涉及交互细节

在步骤中,除了避免描述界面细节,还要避免描述交互细节。例如,有人会这样写:

会员每输入账户名称的一个字符

系统在界面中验证当前输入信息合法

写的人有他的道理:系统不是等待提交后才验证输入信息是否合法,而是随时验证立即反馈,这样使用体验更好。不过,这只是交互设计的一些技能。忍不住要在需求规约里描述界面和交互的细节,背后的原因和忍不住要思考内部代码如何实现的原因是一样的,都是对自己的设计技能没有信心,害怕“现在想到了如果不记下来以后就忘记了”。

交互设计和数据库设计、编码一样,都有特定的技能。例如,针对“一个订单有多个订单项”这样一个核心域描述,数据库该如何映射,界面该如何布局,都有特定的套路。这些套路只和数据库和界面平台的特点以及一对多关联的结构有关,和“订单”、订单项等核心域概念无关。

用例的步骤应该把焦点放在系统必须接收什么输入、系统必须输出什么信息以及系统必须做什么处理这三个重点上,加上字段列表、业务规则、可用性需求等约束,足以表达各种需求。

关于用例的交互该怎么写,是一个比较头痛的问题。即使不涉及交互设计细节的问题,也免不了混进交互设计的成分,例如,为什么分两个回合交互而不是一个回合?实际上涉众更希望一个回合就能达到用例的目标。

例如之前提到的ATM的“取现金”用例的步骤:

1. 储户提交账户号码

2. 系统验证账户号码合法

3. 系统提示输入密码

4. 储户输入密码

5. 系统验证密码合法、正确

6. 系统提示输入取现金额

7. 储户输入取现金额

……

可以思考,如果不分多个回合输入和验证,是不是一定会损害涉众的利益?如果不是,那么可以合并为一个回合,留下余地给更专业的交互设计人员。

1. 储户提交取现所需信息

2. 系统验证取现信息符合取现要求

……

字段列表:

1. 取现所需信息=账户号码+密码+取现金额

业务规则:

……

但是这样的写法会使得各种用例的交互最后都长得差不多,区别只在于输入输出的字段列表和处理的业务规则。这是让人左右为难的问题。我的观点是在用例规约中把路径步骤删掉,只保留输入输出、涉众利益和补充约束,交互的路径步骤由交互设计人员决定。

6.1.3.7 需求是不这样不行

说到界面和交互,在这里还要多说几句。许多需求人员之所以在需求岗位上,并不是因为他掌握了该掌握的需求技能,可能只是因为他工作年限足够长该换到需求岗位了——和许多年龄到了就上岗的夫妻和父母相似。

这样的需求人员硬着头皮做需求时,最常用的一招就是托着脑袋想这个东西是什么样子呢?,然后画一个界面原型拿去和涉众确认。一旦涉众说差不多就这样吧,就把这个界面原型作为需求交给分析设计人员。在这一点上,互联网公司的产品经理表现得尤为明显。如果侥幸成功,就拼命鼓吹“原型大法好,因为他只会这个。

这里就引出一个问题:假如需求人员画了一张界面图,拿去问涉众,界面这样做可以吗?涉众说可以,甚至在上面还签字确认。那么,这个界面方案是需求吗?或者我们问得再极端一点,《王者荣耀》是最近两年最受欢迎的游戏,为腾讯公司带来了巨大的利润(单季度120亿人民币),请问《王者荣耀》的源代码是需求吗?

以上两个问题的回答都是否。因为需求人员问的问题都是“这样可以吗”,相当于:

需求人员:界面这样布局可以吗?

涉众:(好用就行,我又不会做界面,问我可不可以我当然说可以了)可以。

需求人员:代码这样写可以吗?

涉众:(好用就行,我又不会写代码,问我可不可以我当然说可以了)可以。

如果问的问题改为不这样可以吗,像下面这样:

需求人员:界面不这样布局可以吗?

涉众:不可以,这是政府的规定,你们不要自己乱发挥啊!

需求人员:代码不这样写可以吗?

涉众:不可以。这段代码是我小舅子写的,一定要这样,否则不给钱。

这时,界面和代码就成为了需求,当然,只是补充约束级别的需求。

说到这里,我们归纳出需求的判断标准:需求是不这样不行,而不是这样也行

大家可以尝试用“不这样行吗”这个问题去过滤一下自己或其他人以前写的“需求”,可能会过滤出一大堆假的需求,然后问“为什么”,找出背后“不这样不行的真正需求。

对于需求的一个误解是以为“写的细的就是好需求”。需求确实要细,但是很多时候需求人员写的“细”不是需求(问题)的细,而是设计(解决方案)的细。我看到过不少所谓“需求规约”,篇幅巨大,从数据库每个字段的设计到界面控件详细布局,甚至编码规范,都包含在其中。作者也很得意“我这份需求可谓是无二义了吧!”说到无二义,源代码就更无二义了,但源代码不是需求。

在需求里大量描述设计,相当于医生没有能力去定位患者得的什么病,干脆拍脑袋开药,然后用正楷把药的说明书抄一遍,抄到自己都感动了,以为这样就可以治好患者了。诊断能力不足,开的药不对症就不对症,说明书抄得再认真仔细也没用的。

6.1.4 扩展路径

基本路径上的每个步骤都有可能发生意外,其中某些意外是系统要负责处理的,处理意外的路径就是扩展路径。因为一个步骤上出现的意外及其处理可能有多种,所以同一步骤上的扩展路径可能有多条。

对于扩展路径及其步骤的标号,本书采用的是[Cockburn 2001]中推荐的方法。扩展路径的标号方法是在所扩展步骤的数字序号后面加上字母序号,例如2a表示步骤2的第a条扩展路径,2b表示步骤2的第b条扩展路径。扩展路径条件的最后加上冒号,接下来是该扩展路径的步骤,标号方法是在扩展路径编号后面加上数字序号,例如2a1。也就是说,步骤的编号以数字结尾,扩展路径编号以字母结尾。如果有多重扩展,那就继续按此形式标注,如图6-21所示。

6-21 扩展路径和步骤

还是以之前的ATM“储户取现金用例为例。该用例规约加上扩展路径之后如下:

基本路径

1. 储户提交账户号码

2. 系统验证账户号码合法

3. 系统提示输入密码

4. 储户输入密码

5. 系统验证密码合法、正确

6. 系统提示输入取现金额

7. 储户输入取现金额

8. 系统验证取现金额合法

9. 系统记录取现信息,更新账户余额

……

2a. 账户号码不合法:

2a1. 系统反馈账户号码不合法

2a2. 返回1

5a. 密码不合法:

5a1. 返回3

5b. 密码合法但不正确:

5b1. 系统验证当日取款累计输错密码次数不超过3

  5b1a. 当日取款累计输错密码次数超过3次:

    5b1a1 系统关闭账户

    5b1a2 用例结束

5b2. 系统反馈密码不正确

5b3. 返回3

8a. 取现金额不合法:

8a1. 返回6

从以上例子可以看到,有扩展的步骤258都属于验证类步骤。验证的结果有通过和通不过。在验证通不过的情况下,系统肯定要做相应处理,否则就白验证了。验证类步骤肯定会出现扩展。

和辅执行者交互的步骤很有可能会出现扩展。在系统请求辅执行者做某事时,如果辅执行者出现故障,系统无法得到想要的结果,很有可能会导致系统行为的变化。例如:

4. 系统请求公司网站发布信息

……

扩展

……

4a. 公司网站无响应:

  4a1. 系统反馈公司网站无响应

……

注意,如果系统不需要从外系统那里得到任何结果,这个外系统就不是辅执行者,所以它出现故障会不会导致扩展的讨论是没有意义的。例如下面这一句:

5. 系统向经理的电子邮箱发邮件通知有新的待审批申请

系统只是负责发邮件,无法感知经理是否收取电子邮件以及是否审批通过申请,经理不是辅执行者,更不存在经理没有收到邮件经理审批未通过之类的扩展路径。

除了这两种步骤之外,从其他步骤产生扩展路径一定要非常谨慎,否则容易让不属于需求的内容混进用例规约中。特别要注意下面几点:

能感知和要处理的意外才是扩展。

不是所有的意外都产生扩展路径,有些意外是系统无法感知和处理的,不产生扩展路径。ATM例子中,“储户心脏病发作”是意外,会导致“取现金”用例无法完成,但ATM没有办法感知“储户心脏病发作”事件,也不负责处理这样的事件。不过,“长时间无操作”则是可以被感知而且可能要处理的。

可能有人会问,要是有一种高级ATM能感知“储户心脏病发作”呢?没问题,上面讲的道理依然适用。

设计技能不足导致的错误不是扩展。

即使是系统能感知的意外,也未必产生扩展。这种意外必须要符合需求的要求才行。例如,经常有人把系统保存数据失败当成扩展,这是错误的。保存数据为什么会失败?程序员编码错误、数据库设计错误或者网络故障呗,换程序员或数据库设计人员或改善网络环境就能避免,这和需求有什么关系?根据第二章的投币法,做需求时应该把研发团队看作不存在,系统是投币得来的,不存在程序员编码错误等问题。

用执行者来对比可以帮助理解。执行者是外系统,就算我们的系统做得没有错误,也无法保证外系统一定会给我们想要的结果。也就是说,这样的意外和设计错误无关。所以,上面的系统验证****系统请求某某系统****等步骤会产生扩展路径。

系统的可靠性确实也是需求,不过应该写在补充约束里,而不是写在路径步骤里搞得到处都是扩展。

不引起交互行为变化的选择不是扩展。

执行者需要从若干选项中做出选择,如果选择不同选项没有引起交互行为的变化,扩展是不存在的。像下面的写法就是错误的:

……

4. 收银员选择

    不让利

    单条商品折扣

    单条商品折让

    削价

……

扩展

4a. 不让利:

    4a1. 系统按照不让利方式计算应收金额

……

4b. 单条商品折扣:

    4b1. 系统按照单条商品折扣方式计算应收金额

……

无论选择哪一个选项,系统都是计算应收金额,只不过适用的规则不同,在这里加入扩展没有意义,应该把这些选项写在字段列表和业务规则部分,像下面这样:

4. 收银员选择打折方式

5. 系统计算应收金额

……

字段列表

……

4. 可选打折方式有:不让利、单条商品折扣、单条商品折让、削价

……

业务规则

……

5. 计算应收金额的规则:******

……

界面跳转不是扩展。

现在软件系统的图形界面上往往布满了链接,执行者在任何步骤都可以选中某个链接,系统立即跳转到其他用例的界面。不能把这些跳转当作扩展,否则的话任何步骤都会有扩展了。

在书写用例规约时,应该把具体的界面看作不存在,把其他用例也看作不存在,专注于典型执行者为了达到本用例目标必须要和系统发生的交互以及不可避免要处理的意外和分支。看下面的例子:

1. 会员选择订单

2. 系统反馈订单明细

3. 会员可以

    取消订单

4. 会员请求结算

5. 系统反馈结算界面

……

扩展

3a. 会员取消订单:

    3a1. 会员请求取消订单

    3a2. 系统取消订单

在本书上一版中,我认为这样写是合适的,但现在我认为不合适,因为这样写很容易掉入可以这样做的陷阱(参见第5章取款机治疗小崔失眠的片段)。事实上,进行到步骤45时改主意取消也是可以的。那么,可不可以像下面这样写呢?

*a. 会员取消订单:

    *a1. 会员请求取消订单

    *a2. 系统取消订单

这样写的意思是在任何一个步骤都可以取消订单,但是还是不合适。如果这样写可以的话,任何一个步骤都可以跳转到”设置账户“也要写了。“取消订单是另一个用例。在具体的设计中,只要符合用例的前置条件,可以在任何地方开始“取消订单用例,但是这个用例和会员结算用例不是扩展关系。

6.1.5 补充约束

路径步骤里描述的需求是不完整的。例如:

用例名:发布讲座消息

……

1. 工作人员输入讲座信息,请求发布

2. 系统验证讲座信息充分

3. 系统生成发布内容

4. 系统请求公司网站发布信息

5. 系统保存讲座信息和发布情况

6. 系统反馈信息已经保存并发布

……

步骤1中“讲座信息”包括哪些内容?需要添加字段列表。步骤2中“充分”指什么?需要添加业务规则。从步骤1到步骤6有没有速度上的要求?需要添加质量需求。

如果补充约束的内容只和单个用例相关,可以直接放在该用例的规约中;如果补充约束适用于多个用例,可以单独集中到另外的地方,从用例规约引用。

补充约束前面的编号不代表顺序,而是表示该约束绑定的步骤的编号。以上面“发布讲座消息用例为例,如果有这样的补充约束:

5. 发布情况=发布时间+工作人员

表示这一条约束是步骤5系统保存讲座信息和发布情况的补充约束。

如果某条补充约束不是针对某一步骤,而是针对多个步骤甚至整个用例,前面的编号可以用“*”

补充约束的类型用类图表示如图6-22

6-22 用例的补充约束

6.1.5.1 字段列表

字段列表用来描述步骤里某个领域概念的细节。例如上面“发布讲座消息用例的步骤中,步骤135都需要分别添加字段列表。

字段列表可以用自然语言表达,例如:

字段列表

1. 讲座信息包括:举办时间、地点、专家信息、主题、简介。专家信息包括:姓名、单位、头衔。

也可以用符号表达,例如

字段列表

1. 讲座信息=举办时间+地点+专家+主题+简介

1. 专家=姓名+单位+头衔

表示的符号可以采用过去数据字典常用的符号。例如+表示数据序列,()表示可选项,{}表示多个,[| | | ]表示可能的取值。例如:

注册信息=公司名+联系人+电话+{联系地址}

联系地址=+城市+街道+(邮编)

保存信息=注册信息+注册时间

客房状态={空闲|已预定|占用|维修中}

字段列表写到涉众有共识就可以,并不是越“细”越好。例如,说到“电话号码”,所有涉众都知道指的是什么,不必再作进一步说明,如果写成“电话号码 varchar(255)”,那反而没有共识了,因为涉众不了解这样做是好是坏。

字段列表不同于数据模型。有的人为了省事,直接贴上一个数据模型图,这是不正确的。不同的用例,不同的步骤,涉及到的输入输出信息不同。数据模型是设计,设计应该来源于需求,而非空想一个设计,然后把它当成需求。

字段列表不等于数据字典。过去的开发方法学会有“数据字典”这样的工件,本书不推荐花时间做这个工件,因为它会容易让建模人员过早把时间花在细节上,造成一种做了很多事情的错觉,其实还没触碰到核心域真正的难题。

6.1.5.2 业务规则

业务规则描述步骤中系统运算的一些规则,例如上面“发布讲座消息用例的步骤2中的“充分”没有说清楚,需要添加业务规则,例如:

业务规则

2. 必须有的项包括:时间、地点、专家、主题

如果用文字说明业务规则比较困难,可以使用一些辅助的手段,例如决策表、决策树。图6-23就是一张决策表,描述了宾馆打折的规则。

6-23 决策表

只要涉众能理解,行业上适用的任何方式(例如数学、物理公式)都可以用来表达业务规则。

描述业务规则时要注意的是:业务规则不等于实现算法。业务规则是需求的一种,也是从涉众的视角看“不这样不行”的。例如,研究一款为盲人或残疾人而做的语音输入软件,用例规约有如下片断:

3. 系统将语音输入翻译为文字

……

业务规则

3. 采用××识别算法

……

这条业务规则可能是有问题的,如果前排涉众是盲人或残疾人,他们不知道什么叫“××识别算法也不在意是否用了这个算法,所以这不是需求,删掉它,然后问“为什么?”,得到涉众真正在意的需求:“背景噪音强度为××的情况下,识别率应在××以上

当然,如果涉众的排位发生变化,例如该软件的初衷是为某个厂家推广它的识别技术,那么,“采用××识别算法”也可以成为需求,“背景噪音强度为××的情况下,识别率应在××以上”反而不是了。

6.1.5.3 质量需求

按传统的需求分类,用例、路径、步骤、字段列表和业务规则可以归属于功能需求。系统满足功能需求,说明系统能把事情做正确。在做正确的基础上,系统还需要在做的过程中满足一些指标,这些指标就是质量需求。

“质量需求”在包括本书上一版在内的很多书里被称为非功能需求,本书统一称为“质量需求”。产品的竞争往往先从功能开始,当类似产品越来越多时,质量需求可能就成为激烈竞争的决胜点。

可用性

可用性需求是对人类执行者和系统之间交互质量的度量。如果系统仅能正确达到用例目标,但交互太繁琐,人类执行者是不喜欢用的。这里提到“人类执行者”,说明可用性需求仅和主执行者是人的用例相关,毕竟机器不会因为交互繁琐而感到烦躁。

在表达可用性需求时,仅仅说“系统应容易使用“是不行的。对年轻人容易的,对老太太容易吗?对篮球运动员容易的,对普通人容易吗?合适的可用性需求应该是可度量的,例如:

平均5次操作之内能完成客人入住

也要防止另一种错误:直接画一个界面贴上去就当成可用性需求。关于界面是不是需求,在本章前面部分已经有阐述。下面用一个表格比较一下:

工作流

表述

谁的责任

需求

平均5次操作之内能完成客人入住

需求工程师

设计

什么样的交互界面能满足以上需求

交互设计师

设计

如何用软件组件实现以上交互界面

程序员

6-24 不同工作流的界面

性能

性能包括速度、容量、能力等,例如:

系统应在0.5秒之内拍摄超速车的照片(速度)

应允许1000个执行者同时使用此用例(容量)

在标准工作负荷下,系统的CPU占用率应少于50%(能力)

在寻找质量需求时,性能类型的质量需求往往是最多的。

可靠性

可靠性表示系统的安全性和完整性,通常用平均无故障时间(MTBFMean Time Between Failures)和平均修复时间(MTTRMean Time To Repair)表示。

可靠性需求往往不是针对单个用例,而是针对整个系统,可以在所有用例规约的最后,单独用小篇幅描述。

可支持性

可支持性表示系统升级和修复的能力。例如:

95%的紧急错误应能在30工作时内修复

在修复故障时,未修复的相关缺陷平均数应小于0.5

升级新版本时,应保存所有系统设置和个人设置

可支持性有时会被需求人员写成解决方案。例如,“系统应采用面向对象的方式开发”,涉众根本不清楚面向对象为何物以及和搞对象有什么区别,这不是合适的需求,背后的需求还是可支持性。

和可靠性一样,可支持性需求往往不是针对单个用例,而是针对整个系统,可以在所有用例规约的最后,单独用小篇幅描述。

以上介绍了质量需求的种类。很多时候质量需求不用刻意去寻找,按照前面说过的“不这样行吗”的标准,把混入需求的设计删掉,然后问为什么,背后往往隐含的就是质量需求。例如:

设计

需求

系统采用冗余磁盘阵列

存储故障平均发生间隔大于50000小时

系统在服务器端计算应收款项,然后把结果传回客户端

系统应在3秒内向顾客反馈应收款项

6-25 设计背后隐含的质量需求

6.1.5.4 设计约束

设计约束是在实现系统时必须要遵守的一些约束,包括界面样式、报表格式、平台、语言等。

设计约束既不是功能需求,也不是质量需求。例如,“Oracle数据库保存数据”,其实用DB2也可以满足同样的功能需求和质量需求,但必须要用Oracle,因为客户已经采购了许多Oracle数据库软件,如果不用,成本就会增加。

设计约束是需求的一种,也一样要从涉众的视角来描述。在很多需求规约中,不少来自研发人员视角的设计伪装成设计约束,混进了需求的队伍。例如前些年相当时髦的说法“系统应采用三层架构方式搭建”,涉众并不了解“三层”好在哪里,为什么不是四层、五层?或者一层、二层?删掉它,然后问“为什么”,背后的真正需求可能还是性能需求。

还有这样的假“设计约束”:

录单界面应分为3个页面,每个页面填写完毕,单击“下一步”,出现下一页面

这也是来自研发人员视角的设计,不是需求。同样问:为什么要分三个页面?回答是“一个页面放太多信息,加载太慢,涉众等待太久”,真正的需求可能是以下性能需求:

系统应在3秒内显示录单界面

需求是问题,设计是解决方案,二者稳定性不同。就拿上面的“3个页面”为例,过去网速慢,为了快点显示,“3个页面”的解决方案是合适的,现在网速快了,单个页面更好,这样可以减少操作的次数,但“3秒内显示”的需求没有变,它只和涉众的耐心有关。