2. 编写故事

User Story INVEST

用户故事(User Story)是从用户角度对功能的简要描述。

可以使用如下用户故事格式:

作为一个<角色>,可以<活动>,以便于<价值>。

  • 角色: 谁要使用这个功能?
  • 活动: 需要执行什么操作?
  • 价值: 完成操作后带来什么好处?

为了构造好的故事,我们关注六个特征

一个优秀的故事应该具备以下特点:

  • 独立的(Independent)
  • 可讨论的(Negotiable)
  • 对用户或客户有价值的(Valuable to Purchasers or Users)
  • 可估计的(Estimatable)
  • 小的(Small)
  • 可测试的(Testable)

《探索极限编程》和《重构工作手册》的作者Bill Wake,建议用英文缩写INVEST来代表这六个特征。

2.1. 独立的

要尽量避免故事间的互相依赖。

在对故事排列优先级时,或者使用故事做计划时,故事间的相互依赖会导致一些问题。

例如,假设客户团队已经选择了一个高优先级的故事,但它对一个低优先级的故事有依赖,就会出现问题。

故事间的依赖也会使做估计变得更加困难。

比如,我们正在开发那个招聘网站,现在需要编写客户公司如何对发布职位进行付费的故事。我们可以编写这些故事。

  • 公司可以用Visa信用卡对发布职位进行付费
  • 公司可以使用万事达信用卡对发布职位进行付费
  • 公司可以使用运通卡对发布职位进行付费

假设,开发人员估计支持第一种信用卡(不考虑是哪一种)需要3天,而然后支持第二种和第三种各需要一天时间。对于这些很高相互依赖的故事,你不知道给每个故事估计多少时间--哪个故事应该给3天的估计?

当出现上述的依赖时,有两个方法可以绕过这种依赖。

  • 将相互依赖的故事合并成一个大的、独立的故事
  • 用一个不同的方式来分割故事

在上述的案例中,合并成一个大的故事是非常可行的,因为这个合并后的故事仅需要5天时间。

如果合并后的故事需要远远大于5天时间,那么最好找一个不同的维度来分割故事。比如下面的分割方法。

  1. 客户可以用一种信用卡(Visa)支付
  2. 客户可以用另外两种信用卡(万事达、运通卡)支付

如果你实在是不想合并故事,也找不到合适的方法来分割它,还有一个简单的方法,就是在故事上记录两种不同的估计方法

  • 如果早于另一个故事的估计
  • 如果晚于另一个故事的估计

2.2. 可讨论的

故事应该是可以讨论的

故事不是签署好的合同或软件必须要实现的需求。故事是功能的简短描述,细节在客户团队和开发团队的讨论中产生。因为故的作用是提醒客户团队和开发团队在以后要进行关于需求的对话,它并不是具体的需求本身,因而,他们不需要包含所有的相关细节。

如果,我们在编写故事的时候已经知道了一些重要的细节,那么应该在故事上以注释的形式记录这些细节。

一张提供有额外细节的故事卡

公司可以用信用卡支付发布工作信息的费用。

备注:接受Visa、万事达和运通卡,考虑支持发现卡。

这是一个非常好的故事,因为它提供了适量的信息给开发人员和客户团队进行交流。当一个开发人员开始编码和实现这个故事时,这张故事卡可以提醒他也能根据故事上的注释去询问客户团队是否已经做了决定。

理想情况下,不论对话的双方无论是开发人员还是客户人员是否与原来相同,这种对话一般都很容易继续进行。

把细节加入故事时,请参考上面这个故事卡。

细节太多的故事卡

公司可以用信用卡支付发布工作信息的费用。

备注:接受Visa、万事达和运通卡,考虑支持发现卡。 当支付金额超过100美元时,需要提供信用卡背面的ID号。 系统可以根据卡号的前两位数字识别客户使用的是何种类型的信用卡。 系统可以保存卡号以备用将来使用。 搜集信用卡的过期月份和日子。

这个故事有太多的细节(“搜集信用卡过期月份和日期”),同时也合并了本该成为单独故事的部分("系统可以保存卡号以备将来使用")

处理这个故事是很困难的。大部分人阅读到这种类型的故事时,会过多的关注本不应该关注的细节。在许多的案例中,过早的制定细节只会带来更多的工作量。

另外,这样的故事会更容易让人觉得确定和真实。这会导致一种错觉:这个故事反映了所有细节,没有必要跟用户进行下一步的讨论。

如果我们将故事永远提醒开发人员与客户团队进行关于需求的讨论,那么故事包含下面的信息就变的有意义了。

  • 一两句短语,用于提醒开发人员和客户团队进行对话
  • 一些注释,用以表明在对会中等待解决的问题

讨论中确定的细节将变成测试,这些测试可以留在故事中。

修正后的故事,只有故事和将要讨论的内容

公司可以用信用卡支付发布工作信息的费用。

备注: 我们将支持发现信用卡吗? 用户界面备注:不需要专门的字段来输入信用卡的类别卡片种类(可以从卡号的前两位数组获得该信息)。

2.3. 对用户或客户有价值的

"每个故事必须对用户有价值",这句话说起来很诱人。但是许多项目中包含了对用户没有意义的故事。

要记住用户(软件的使用者)客户(购买软件的人)之间的区别

假设,一个开放团队正在构建一个支持大量用户的软件,可能需要在公司内5000台电脑上实施。像这样的客户比较关心5000台电脑是否在使用同样的软件配置。这就会产生一个例如这样的故事:“所有的配置都从一个中心读取”。但是用户不关心配置信息在哪里存储,但是购买者可能比较关心。

隐含测试用例的细节要和故事本身分开

用Visa信用卡、万事达信用卡和运通卡测试(通过) 用大来卡(Diner's Club)测试(失败) 用有效、无效和丢失卡ID号的信用卡测试 用过期卡测试 用高于100元和低于100元测试

类似,下面的故事显示客户在购买时要考虑的价值,却不是用户所需要考虑的。

  • 整个开发过程中,开发团队要提供符合ISO9001标准审核的文档
  • 开发团队要按照CMM3级的标准来构建软件

应当避免那些只对开发人员有价值的故事。

例如,应该避免下面类似的故事。

  • 所有的数据库连接要通过一个连接池
  • 所有的错误处理和记录应在一系列公共类中完成

这些故事都在关注技术实现细节。很可能这些故事背后的想法是好的,但是故事的编写方法应当体现对客户或用户的价值。这样使客户团队能方便的在开发中对那些故事排优先级。

更好的故事版本

  • 这个应用软件,最多50位用户能使用一个5用户的数据库许可
  • 所有的错误应以统一的方式呈现给用户并做记录

同样,也应当避免在故事中出现用户界面和技术方面的定义。

保证每个故事对客户或用户有价值的最好方法是让客户来编写故事。开始时,客户一般都会觉得不舒服,可能因为他们觉得写下了的东西都有可能成为未来对他们不利的证据(“好吧,需求文档并没有这么说.......”)。但是故事只是为了提醒他们需要之后进行需求讨论,而不是一个正式的承诺或某个功能的具体描述。大多数的用户一旦接受这个概念,就会开始自己写故事了。

2.4. 可评估的

对于开发人员来说,能估算故事的大小(至少能猜一下),或者把故事变成可用代码的时间是很重要的。一般有以下3个原因会导致故事不可评估。

  1. 开发人员缺少领域知识
  2. 开发人员缺少技术知识
  3. 故事太大了

首先,开发人员可能缺少领域知识。如果开发人员不理解故事,他们应该和写故事的客户一起讨论。同样没有必要理解故事所有细节,但是开发人员需要对故事有个大概的了解。

其次,故事无法苹果干是因为开发人员不掌握所涉及的技术。比如,在一个Java项目中,我们需要提供一个CORBA接口给系统。团队没有人有相关经验,所以当然无法评估这个任务。这种情况下,可以让一个或多个开发人去实施极限编程(XP)中所谓的探针试验(spike)。这是一个简短的试验,用于研究应用程序的某一方面。在做探针试验的时候,开发人员不需要做十分深入的研究,只要能大体了解足够信息来估计这个任务即可。

探针试验本身总是会限定一个最大时间量(称为时间箱,TimeBox),用这个时间量作为探针试验的估计。

如此,一个不可估计的故事变成了两个故事:一个快速的探针故事(用来获得足够的信息)和一个故事(真正实现功能)。

最后,如果故事太大了,我们要估计它,就要把它分解成多个更小的故事。

即使故事太大不可能进行可靠的评估,有时候编写例如“一个找工作的人可以找到一份工作”这样的史诗故事也是很有用的,因为它们可以做为系统中有待讨论的一大块功能占位符或提示。

如果希望暂时不细化系统的一部分功能,可以考虑写一两个史诗故事。也可以给史诗故事一个大的、比较虚的估计值。

2.5. 小的

有些故事可能太大,有些可能太小,有些则刚刚好。

故事的大小很关键,故事太大或太小都无助于制定计划。

使用史诗故事来开展工作会很困难,因为它们通常包含多个故事。

举个例子:在一个旅行预订网站,“一个用户可以计划一次度假”是一个史诗故事。对于任何旅行预订系统,计划一次度假都是非常重要的供,包括一系列任务。史诗故事需要分成更小的故事。

合适的故事大小最终取决于团队、它的容量以及所使用的技术。

分割故事

史诗故事通常分为以下两种。

  • 复合故事(compound story)
  • 复杂故事(complex story)

复合故事

复合故事有多个小故事组成的史诗故事。在做系统初始设计计划是,复合故事可能是比较合适的,但是通常它都能分割成多个更小的故事。

比如,“用户可以发布他的简历”这个故事,可以分割成以下的故事。

  • 用户可以创建简历,包含教育情况、工作经历、薪资历史、出版物、演讲情况、社区服务和求职目标
  • 用户可以修改简历
  • 用户可以删除简历
  • 用户可以有多份简历
  • 用户可以激活简历,也可以让简历失效

一般有很多方法来分解一个复合故事。上面的分解方法,沿用一种常见的分解方式,即按照“创建”、“编辑”和“删除”这些动作来分解故事。

另一个可行的方法是根据数据边界来分解。比如,我们将简历的各种部分当成单独的部分来增加和修改。

  • 用户可以增加、修改教育信息
  • 用户可以增加、修改工作经历信息
  • 用户可以增加、修改薪资历史信息
  • ......

复杂故事

不同于复合故事,复杂故事是本身就很大并且不容易分解的故事。如果一个故事因为不确定性而复杂,可以将它分为两个故事:一个调研的故事和一个开放的故事。

例如,假设给开发人员这样一个故事:“公司可以用信用卡支付发布职位的费用”,但没有一个开发人员曾经做过处理信用卡相关的工作。他们可以将故事这样分割:

  • 调研网络上处理信用卡的相关技术
  • 用户可以用信用卡付费

第一个故事会让一个或多个开发人员实施探针试验。这样分割复杂故事时,我们仍然非常可能定义需要花多少时间来进行调研。

当我们开发新的算法或扩展已知算法时,复杂故事是很普遍的

考虑将探针试验放在不同的迭代里

如果有可坑,一种较好的做法是把调研的故事放在一轮迭代中,另外的故事放在接下来的一轮或几轮迭代中。

一般我们只能对调研的故事做评估,将另一个无法估计的故事与调研故事放在同一轮迭代中,不确定性会高于平春,因为我们无法知道在哪一轮迭代中能完成多少工作。

对无法估计的故事进行分解,主要的好处是允许把调研工作从新功能中分离出来,以便对调研工作排列出优先级。

合并故事

有时候,故事太小了。对于太小的故事,开发人员会说,他不想写下这个故事或者对它进行评估,因为那么做可能比实施该故事花的时间更长。

一个比较好的方法通常是将这些太小的故事合并到需要半天活几天完成的故事中。给合并后的故事命名后,就可以同其他故事一样计划实现它。

2.6. 可测试的

故事必须是可测试的。成功通过测试可以证明开发人员正确的实现了故事。如果故事不能被测试,开发人员怎么知道他们什么时候才算是完成了代码?

通常,不可测试的故事发生在一些非功能性的需求上,这些需求和软件有关,但不直接与功能有关。

例如,下面两个非功能性的故事。

  • 用户必须觉得软件好用
  • 用户绝不需要花很长时间等待界面出现

前面两个故事都是不可测试的。

无论什么时候,只要有可能,就要把测试自动化。 这意味着我们需要争取99%都自动化,而不是10%。

能自动化的测试基本上总是比你认为的要多。

当产品是增量开发的,很多东西变化得很快,昨天能工作的代码,今天就会出现问题。这需要自动化测试来帮助你尽早发现这些问题。

实际情况中,总有极小部分的测试是不能进行自动化测试的。比如上面的故事“用户绝不需要花很长时间等待界面出现”是不可测试的,因为它用了“绝不”,而且,“长时间等待”没有明确定义。要想演示某些东西永远不会出现时不可能的。一个更容易、更合理的目标是演示某些东西极少出现。这个故事可以改为“在95%的情况下,界面会在2秒内打开”。这样就可以测试,并且最好是写一个自动化测试来验证它。

2.7. 职责

客户团队职责

  • 编写故事,这些故事要能提醒你们同开发人员交谈,而不是记录详细的需求定义,它们对用户或你们自己是有价值的,它们时独立的、可测试的、大小合适的。

开发人员职责

  • 负责帮助客户团队编写故事,这些故事要能提醒你们同客户团队交流,而不是记录详细的需求定义,故事应该对用户和客户有价值,它们是独立的、可测试的、大小合适的
  • 如果被问及实现故事所用的技术或基础架构信息,应该使用对用户或可以有价值的术语来描述

2.8. 小结

  • 理想情况下,故事之间是独立的。有时很难做到这一点,但我们哟啊尽量实现这一目标。故事之间的交付顺序应该是无关的,可以任意拿一个故事来实现
  • 故事细节有用户和开发人员讨论得出
  • 故事应该很清晰体现对用户或客户的价值。最好的做法是让客户编写故事。
  • 故事可以注释一些细节,但是过多的细节会使故事难以理解,也可能给人一种开发人员和客户无需交流的错觉
  • 给故事加上注释的最好方式是给它编写测试用例
  • 如果故事太大,复合故事和复杂故事可以拆分成多个小故事
  • 如果故事太小,几个小故事可以合并成一个较大的故事
  • 故事应该是可以测试的