用户故事的估算总是不准确的,这是估算的第一要义。正因为此,我们才不能在故事估算上耗费太多时间。估算不应该由个人来进行,团队的 Planning Game 不可缺少。在估算用户故事时,不应该估算时间,而应该估算用户故事的规模。同时,在团队进行估算时,团队应对“Done”的定义达成一致。
我把这称为用户故事估算的四要素。——然而,即便你掌握了估算的要素与原则,掌握了正确的估算方法,就一定能解决故事估算的问题么?
“故事的估算是按照时间来的,这是一个大问题!”我的一名咨询客户有些心急火燎,迫不及待想要我帮助她解决这个麻烦。可在我看来,这根本不是什么问题。即便是按照时间进行估算,只要团队成员足够了解用户故事,并以团队形式开展估算,得出的工作量仍然可行。在“估算总是不准确的”大前提下,我们只需要让计划时间更接近实际工作时间即可。方法有很多,例如可以通过历史数据对估算进行调整,以及事先识别可能存在的风险等多种措施来做到这一点。
但是客户并不这样认为。——好吧,我需要实地调研。
问题是起点,如果你只看表面,绕了一圈,你还是会回到起点;只有深度挖掘背后的起因,你才能走到问题解决的终点。
当我拿到客户编写的用户故事时,我忽然明白问题所在了。用户故事的编写显然是照猫画虎描出来的。用户故事的所有要素它都具备了,然而却只得其形,却未得其神。用户故事如下:
as 一名顾客
I want to 进入商品清单后
So that 进行查询
-
Given 顾客
When 进入商品查询页面
Then 系统执行查询
用户故事的末尾,则是一大段针对界面操作流程的描述。
“知其然而不知其所以然”,对于许多敏捷实践,我们就这般浑浑噩噩地推行着,以为这是更好的方法,却从未有人去质问“为什么”。当问题暴露时,常常是头痛医头,脚痛医脚,直至病入膏肓,尚且不知病因究竟出在哪儿?
软件开发本身就是一个生态系统,诸多方法与实践其实并不能孤立去看待。这个系统的任何动作与活动,就像是蝴蝶扇动翅膀,飘飏起南美热带森林的季风,殊不知它却掀起了印度洋的飓风。
在传统的软件开发过程中,我们称需求为功能点,而 Jacobsen 则将需求提炼为用例(Use Case),在 FDD(特性驱动开发)方法中,又将功能点称为 Feature,至于 Scrum,则干瘪瘪地将其命名为 Backlog。但我,更喜欢以用户故事名之。
描述需求就像讲述故事一般的明白晓畅,甚至做到让故事的读者能够身临其境。虽然不要求将软件需求写得像古龙小说般精彩,但最好也要能做到像白居易诗一样童叟可读。其实简单说来,就是要让需求能够让人读得懂,
讲故事需要哪些基本要素?答案是 6W,即 Who、What、Why、When、Where 和 hoW。具备这六个要素,就能讲述出一个活灵活现的故事了。6W 撑起了一个场景。戏台的布景装饰出景色,演员们身着戏服演绎着春秋,演绎着人生百态与喜怒哀愁。
写用户故事当然不能天马行空。要学会取舍。高手当然可以信手拈来,若未深得其中精髓,就该退而求其次,遵循普遍被认可的模式为好,例如 as...I want to ... so that,又例如 given...when...then。
模式或许限制了你的自由发挥,但它的引导价值却可以让你快速上手。但首先你得明白这样的模式背后隐藏的道理。之所以明确提出 as...I want to...so that... 就是希望需求分析师在分析用户故事时,要思考功能的使用者是谁,要做什么,价值又是什么?这恰好对应了 6W 的 Who、What 和 Why。就像拍电影一样,我们总是要从人的角度去讲故事,去观察世界,体察人与人之间的关系。而在最后的制作阶段,则要从整部电影的风格、价值观、故事着手对内容进行裁剪。即使部分拍摄片段唯美而精彩,演员表演也极到位,若与整部电影发生冲突,导演也要忍痛割爱。需求分析师也需要站在用户的角度去体验需求,寻找业务的价值。
许多敏捷实践其实是诸多工程师千锤百炼得来的体验。这些体验如智者,一言一行,都别有深意。采用 Given...When...Then 模式,实则代表了接口设计的驱动力。这正是 ATDD 以及 TDD 的惯用手法。
Given 是什么?就像契约式设计一般,它等同于前置条件。从接口角度讲,就是输入数据。当满足这些条件(无论是数据,还是状态)后,就可以执行动作了。When 就是这个动作,其实就是我们需要开发的功能,又或者说,正是我们要驱动出来的接口。至于 Then,就是后置条件,也即是我们所谓的验证。这就很好地与测试结合起来了。
在编写 Given...When...Then 时,我们要注意,只有 Given 与 Then 可以通过 and 连接更多的内容。When 只能是一句话!
为什么?——让我们回到场景。在编写用户故事时,需要从用户角度出发,驱动出故事发生的场景。如果说故事是整部话剧,场景就应该是话剧的一个相对完整的片段。在用户故事级别,场景应尽可能保持小。就像 SRP(单一职责原则)讲的那样,最好一个场景只做一件事情。一旦发现在编写 When 时,无法用一句话表达,就应该思考,是否需要对该场景进行进一步拆分。
场景又可分为正常场景与异常场景,正如 Use Case 中提出的正常流程与异常流程。例如,以查询来说,正常场景就是查询获得了符合给定条件的记录。异常场景则可能包括两个。第一个是没有符合条件的记录;第二个则是查询过程发生异常,从而导致查询失败。
场景还可分为主要场景与扩展场景,可以对应 Use Case 中的用例与扩展用例。还是以查询为例,主要场景就是查询获得符合给定条件的记录;扩展场景则是查询结果默认以名称进行升序排列,并允许用户对指定字段重新进行排序。
当我们按照场景去编写用户故事时,我们会自然而然地去思考故事的拆分。有些场景可能确乎属于同一个故事,但当你发现一个故事包含了太多的场景时,就可以考虑把这些场景分开。例如把正常场景与异常场景分开,把主要场景与扩展场景分开,皆可。那么,我们该怎么对用户故事进行拆分?这就需要运用 INVEST 原则了。
只要了解用户故事,多数人都知道 INVEST 原则,可惜却没能将这个原则用好。如下是 INVEST 原则的说明:
我个人的理解是将这些原则分为几个层次。首先是 Valuable,即判断一个用户故事是否为好的故事,关键在于它是否提供了价值。这个价值指的是业务价值。若一个用户故事没有业务价值,要么需要删掉,要么就说明它不是故事,而应该是任务(Task)。
在满足了V原则后,就需要判断I。I是判断用户故事的独立性。理想的故事拆分应保证故事之间不存在依赖。如果存在依赖,就需要去识别产生依赖的原因。多数情况下,都是由于支撑性功能带来的依赖。例如两个用户故事都需要发送邮件通知客户。若完成了其中一个用户故事,则另一个用户故事就不必重复开发邮件通知功能了。此时,我们可以考虑将邮件通知(可能还包括邮件模板)拆分为单独的故事或任务。我们要尽量地保证用户故事的独立性,只有在无法继续拆分以隔离依赖的情况下,我们才能选择妥协。
接下来再考虑N。我们希望用户故事是描述清楚的,可以促进客户与团队以及团队成员之间的沟通。故事描述最好没有歧义。有时候,为了更好地说明故事,可以引入实例或者 UI 原型。基于这些故事,我们需要更好地与客户协作,明确满足客户的需求。
有了明白晓畅的故事描述,再来判断这个故事是否可测。故事是可测的,就意味着它定义了验收标准。只有通过了验收标准,故事才可以被标记为“Done”。若故事不具备可测试性,则用户故事不过就是平铺直叙的故事描述而已。毕竟,用户故事不是小说。
最后,我们应该合起来看待E和S。因为一个用户故事到底小不小,最直观的判断就是看它能不能被估算。如果很难估算,要么是故事没有描述清楚,要么是这个故事太大。太大的故事总是很难掌控,而功能点的叠加并不只是一加一那么简单。无论何时,分而治之都是软件开发的“不二法门”。
与其说 INVEST 是用户故事的拆分原则,不如说它是衡量一个用户故事好坏的验收准则。
在提及用户故事的 Testable 原则时,我其实有些意犹未尽。这未尽的内容就是验收条件。很多需求分析师都把它忽略了,又或者散乱地将这些验收条件分散到需求描述中。其实,在传统的制造行业,在软件开发的测试环节,我们一直都遵照着验收条件来办事。但是,对于多数需求分析师而言,他或者她可能更关注用户体验,功能细节,或者业务目标与范围,却往往缺乏逆向的思维去思考验收条件。这也是为什么我们提倡 BA 与 QA 结对编写用户故事的初衷。
验收条件可以说是沟通的“契约”,使得我们的用户故事能够成为一个“闭环”。验收条件意味着“照单验收,立此存照。”
我们可以为用户故事的每个场景编写各自的验收条件,也可以为整个用户故事编写一个整体的验收条件。验收条件必须简单清楚,每条内容都应该是可验证的。例如针对查询功能而言,我们可以写出如下验收条件:
查询结果默认以名称升序排列;
当查询结果超过 20 条时,应进行分页考虑;
分页条数的阈值可以进行设置;
如果没有查询结果,应提示“无满足条件的结果集”。
如果查询失败,应弹出“查询失败”的错误框,并在错误框中给出错误原因。
……
只有编写出好的符合 INVEST 原则的用户故事,才谈得上对用户故事进行估算。估算并不能帮助你提升团队的生产效率,更不能以估算的点作为团队及团队成员的量化指标。估算是天气预报,它唯一的作用就是提醒你明日出行带伞还是不带伞。如果带了伞,却没有雨,你也不必抱怨;如果没带伞,却下了雨,那就慢慢享受雨水的清凉吧。当然,若遇上大雨倾盆,那就找个地儿歇歇吧。你总不能因为天气预报让你饱受了淋漓之苦,而视天气预报为仇寇吧。