01 什么是实例化需求?
实例化需求的英文是 Specification by Example,简称 SBE,直译过来就是用实例说明需求。
实例化需求是一组方法,它以一种对开发开发团队有所帮助的方式(理想情况下表现为可执行的测试)描述计算机系统的功能和行为,让不懂技术的利益相关者也可以理解,即使客户的需求在不断变化,它也具有很好的可维护性,可以保持需求的相关性。从而帮助团队交付正确的软件产品。
为避免需求沟通过程中的「知识诅咒」,“实例化需求”方法从场景出发,以用户的操作实例来澄清需求。
相比一般的规格说明,实例更加场景化,能够激发参与和深度讨论;同时,实例是具体的,其典型形式是:「在什么情况下,做什么操作,会得到什么结果」。基于具体的实例,更加便于沟通中的双向确认,保证理解的一致和场景覆盖。
上图是对实例化需求的概念说明:
- 用例子来分析和澄清需求。
- 这些例子随后会转化为测试用例。
- 最后再通过测试验证需求。
如此形成闭环,这个三角是实例化需求的核心概念。
在「实例化需求」中,开发、测试和业务人员一起沟通需求,避免信息传递的噪音和损耗。
02 为什么使用实例化需求?
实例化需求的核心是,让项目的所有干系方进行有效的协作和沟通,用实例的方式说明需求,用自动化测试的方式频繁地验证需求,从实例化的需求说明和自动化测试用例中演进出一套“活文档系统”。这套“活文档系统”既可以有效地对系统进行说明,又可以当做交付验收的标准。
- 有效的交流沟通确保有足够的时间澄清需求。
- 使用举例的方法澄清需求能在第一时间识别出需求是否足以支撑开发。
- 所有的干系方参与需求讨论,可以确保大家对于交付哪些东西有一致的理解。
- 具有不同领域背景的干系方一同参加需求讨论,可以规避因个人认知局限带来的需求问题。
- ”活文档系统”对于变更有着先天优势,可以以最少的维护成本维持文档的相关性和可靠性。又能避免过度说明需求而产生浪费,避免花时间在开发前有可能发生变化的细节上,对于变更天然友好。
- 采用自动化测试的方法实现业务实例,代码开发出来即可以验证,无须经过冗长的手动回归测试,降低返工。
03 产生哪些输出?
实例化需求有9个过程模式:从目标获取范围、协作产生需求说明、举例说明、提炼需求说明、在不修改需求说明的情况下实现自动化验证、频繁验证、以及演化出一个活文档系统。这 9 个过程模式涉及了如下的输出(制品):目标和范围、需求说明、例子、自动化测试及活文档系统。
下面我们一个个来详细说明。
1. 澄清价值,定义目标和范围
实例化需求的第一步“澄清价值”,它包含两个子步骤:
- 描述背景。也就是需求的业务背景和系统的上下文。这一步形式相对自由。上面的示例图中,我使用了面向对象分析中的常用的系统上下文图(SCD,System Context Diagram),定义了系统的边界、所处的环境以及主要组成部分,你也可以使用更自由的线框图来表达系统的上下文。系统上下文相对稳定,并不需要对每个需求重复这样描述,只要在必要时(比如发生变化时),做出澄清就可以了。
- 澄清用户问题和业务目标。需求最终要解决用户的问题,从而实现产品的业务目标。因此,在讨论具体的需求前,我们还要澄清用户是谁,要解决他们什么问题。
针对目标和问题的典型挑战性检验是:
- 如果不做这个需求会怎么样?
- 有没有其它替代方法?
认真回答上面两个问题,往往能挖掘出需求的本质,确保我们在解决的是真正的用户或业务问题。
2. 需求说明
在实践中,我们所产生的“需求说明”更具体的体现形式是:
- 工作流
- 领域模型
- 业务规则
3. 工作流
工作流也称为业务流程或业务场景,是用户通过一系列步骤,达成系统业务目标的一种实现方式。在实例化需求工作坊中,参与人员在白板上使用顺序图、活动图、带有泳道的活动图等方式,共同绘制出工作流。当然,如果工作流足够明显,也可以直接采用业务用例的形式,列出标题,理清它们之间的先后关系即可。
简单来说,就是为了实现上面的目标,系统需要支持哪些用户操作?这些操作的流程是什么样的?具体分为两个子步骤:
1)列出用户的操作。产品的功能体现为它所支持的用户操作,列出用户操作,可以涵盖其功能性需求。如下图,我们通过用例图描述了用户的操作。用例图定义了为完成特定目标,操作者和系统之间的交互,它包含操作者和操作两个部分。
用例是需求工程中获取和列举功能需求最常用的手段之一,如果不习惯用例的表示法,更简单的方法是直接列出用户和用户的操作。
2)画出用户操作的流程步骤。这一步的目的是:定义操作的具体交互流程。下图使用了活动图和时序图两种形式,来表达操作步骤。其中,活动图形式上与流程图类似,只不过它表达的是用户操作流程,而不是技术实现流程;时序图则在表达系统间的交互方面更胜一筹,例如针对金融类系统的需求,可以用时序图表示系统间的资金往来和记录存取等。
活动图示例 带泳道的活动图示例:
时序图:
这两种表示法,都表示了操作的步骤,团队应该灵活选取适合的表达方式,当然也可以选取其它表示法如通信图、状态机等。
状态机:
用户注册通信图:
针对操作和步骤的典型挑战性检验是:
- 这些操作能解决识别出的用户问题并实现业务目标吗?
- 操作步骤合理吗?
- 操作流程可以更简单吗?认真应对这些的挑战,将确保功能和操作的合理性、简单性和完整性。
或者就是简单地采用列表的形式,列出几个相关的用户场景:
- 新开通一个子代理商。
- 为子代理商充值。
- 为子代理商修改密码。
- 修改子代理商的基本信息。
- 查询子代理商列表。
- …
绘制工作流的目的不是为了描述,而是为了信息发现和挑战(challenge, 这里是褒义),即既有的工作流是否合理,是否存在遗漏,是否需要进一步讨论其中的业务数据和业务规则,等等。
在上面的工作流中,我们可以看到的挑战包括:
- 子代理商的初始化密码怎么设定?
- 子代理商首次登录时是否要被提示修改密码?
- 子代理商可以自己添加用户吗?
- 子代理商的基本信息包括哪些?哪些信息需要校验?
- 是否需要在创建时就为子代理商进行充值?
- 创建的子代理商可以进一步创建子代理商吗?
- …
回答上述挑战的过程,事实上是一个需求澄清的过程,它也会对应的更新工作流、领域模型和业务规则。在进行挑战和回答挑战的过程中,也一般都会包含了对每个业务流程或规则背后的业务价值的讨论,以及紧急程度的讨论。例如:
- 密码的一般性规则(例如必须包含特殊字符)是否要适用于初始密码?这会不会对线下过程带来困扰?
- 我们为什么要把联系人和管理员区分开?
- 在我们的当前阶段,能否延迟支持代理商的多管理员功能?
- …
在上述案例中,一个可能的讨论结果是:代理商的多管理员功能虽然是需要的,但是在当前阶段可以暂缓实现,等等。
4. 领域模型(统一术语)
大多数讨论都是很热闹的,但是效果却不尽相同。其实,热闹的背后是七嘴八舌还是井然有序,常常是因为概念引起的。大多数人都有过这样的经验:两个人争论的面红耳赤,但是最终发现我们说的其实根本不是一件事情!或者发现我们说的其实就是同一个观点!根本就无需争论!
在讨论中,我们常常看到有这么一种人能够打破僵局,推动讨论:“来,让我们看看每个人说的是什么”。
每个人在内心所建的概念的差异,往往是导致争论的根源。
领域模型有很多方面的价值,但是让我们首先聚焦于它所表达的概念,对业务实体和它们之间的关系进行建模。例如下面的这个领域模型:
有了上述的模型,当我们在讨论中说到代理商基本信息的时候,所有人就都知道哪些是基本信息。说到子代理商的时候,所有人都知道子代理商其实也是一个代理商。这其实就是领域驱动设计(Domain Driven Design, DDD)中所讲述的“统一语言”的应用。
同时,领域模型带来的统一语言,使得业务规则讨论变得更加精确,也更加高效。
例如:如果子代理商权限为”无“,则代理商不能创建子代理商。代理商可以查询子代理商的基本信息。
像上面的这种模型如何获得?尽管存在各种各样的方法,例如关注需求描述中的名词等等,但是可以肯定的一点是,模型是通过讨论获得的。它是持续演进的结果。这个过程常常是这样一个模式:
- 发现既有词汇不足以表述需求,或者发现参与人员在过程中产生了困惑;
- 试图提出一些关于新的词汇的假设,对既有模型进行修正和补充
- 辩驳这些假设;
- 修正,推翻,达成一致。词汇存在歧义是一个常见的问题,这类问题如果不通过讨论,很难快速取得一致。上述模型的达成过程事实上首先是在白板上完成的。
具体领域模型使用 UML(统一建模语言)表示法,并体现为不包含操作的类图,领域模型由三个要素构成:
- 领域中的实体对象(如: 房产,房主等)和概念(如:交易,按揭方案等)。
- 对象或概念间的关系。如:房主拥有房产等,”拥有“就是房主与房产之间的关系。
- 这些对象或概念所包含的属性。如:房产的属性包含房龄、价格、面积等。
5. 业务规则
实例化需求的最重要输出是需求验收标准,它们可以表达为一条条的业务规则。上一步,我们已经列出了操作和操作步骤,用它们来组织业务规则非常合适,对于操作中的主要步骤,团队可以列出它们的业务规则。
最常见的业务规则可以表达为三段式的结构,也就是「Given(当),When(如果),Then(那么)」。比如:
- 当用户是 VIP 会员时,如果其购买金额为100元,那么运费为0元。
- 当用户是 VIP 会员时,如果其购买金额为99元,那么运费为0元。
- 当用户是普通会员时,如果其购买金额为100元,那么运费为0元。
- 当用户是 VIP 会员时,如果其购买金额为99元,那么运费为10元。
实例的形式表达场景和业务规则,这是”实例化需求“这个词的来源。之前对用户操作步骤的分析,正好可以用来组织这些实例。上图所示,正是按操作步骤分别列举对应的实例(业务规则)。
这些实例可以用条目化的方式表达。同时,当规则组合较多时,可以将他们抽取为数据表格。上图中关于”共同贷款人审查“这一步骤的规则,就被抽象成了表格。这一方面让规则更清晰和易于理解;另一方面,将来表格在映射为测试用例时,能自然的做到测试流程和测试数据分离,更易于阅读、维护和扩展。
以上列出的都是功能性的需求和规则。在实际过程中可能还会涉及非功能性需求,如可用性、可靠性、性能、安全性等。非功能性需求大部分体现在系统级别,而实例化需求针对的是单个需求。实例化需求过程中,一般只需要列出与特定功能相关的非功能性需求,如针对某一特定操作的特定安全性和性能要求。
针对业务规则的典型挑战性检验是:
- 相关业务规则考虑全面吗?
- 特殊情况,异常或错误处理包含了吗?
- 是否考虑了不同用户、数据和操作类别?认真应对以上的挑战,将确保规则的完整性,以及产品交互细节的合理性。
先来一起看一下某测试:
- 首先添加员工信息。
- 执行发放工资的动作。
- 检查工资数值是否正确。
- 检查付款支票号码正确。
这是一个反例。需求必须精确,不能在用户故事开始实现时仍处于模糊的状态。例如,付款支票应该包括哪些信息?这属于领域建模的范畴,应该通过领域模型予以回答。除此之外,我们仍然会提出下面的一些问题,例如:付款支票日期究竟应该使用发薪日期还是当前日期?工资发放的计算规则是否在本需求范围内?付款支票号码的编号策略有没有什么要求?等等。
即使一个看起来很清楚的需求,我们也建议举一两个实际的案例来说明。就像下面这种图片展示的那样:
6. 例子
举例说明是项目需求交流过程中不可或缺的,团队中的人领域背景不同,对同一个事物的理解也可能不尽相同,通过举例说明的方式可以让目标更一致。
功能模块的例子必须具有精确性(不是简单的是或否的答案,使用具体的例子)、真实性(使用真实数据,从客户那儿获取真实的例子)、完整性(使用不同的数据组合去试验,利用其他方式去检验和测试),并易于理解(不用试验所有组合,寻找隐含的概念)。
- Tip1:例子应该关注用户和系统之间的交互,而非关注系统本身的处理流程。因此,例子应该包含前置条件、输入、输出。前置条件指的是场景发生时,未作为输入传递到本系统中,但是已经存在,且对业务产生影响的数据。
- Tip2:当大家用说的方式解释不清的时候,举例子是自然而然的选择。事实上,即使你觉得能说清楚,也应该举例,以免大家的理解有误差。例子应该具体而精确,避免使用范围。例如,不要用某一值“小于10”这一表述,而应该用某一值等于“9”来举例。
- Tip3:在场景特别复杂的情况下,还可以使用流程图来辅助举例。使用什么样的方式不重要,重要的是这种方式能够达到在团队中澄清需求的目的。
- Tip4:如果发现实例太复杂,就把它的复杂度降低,分解成若干个实例。例如,对于“如果数量大于10件,或者重量大于50kg,则收取50元运费”这个规则,可以拆分为“数量大于10件”和“重量大于50kg”两个规则,再来举出数量为20件和重量为60kg两个实例。
- Tip5:在举例说明的过程中极有可能会发现之前未能识别出来的潜在概念。当潜在概念出现时,应当把它加入到领域模型之中。
虽然协作过程中的需求讨论可以建立大家对相关领域的共识,但得到的实例往往包含很多不必要的细节。关键实例是从这些实例中提炼出来的,虽然精简但足以说明业务的实例。并且这些提炼好的实例本身就可以当作交付的验收条件。
- Tip1:摒弃对业务走向没有影响的实例。例如,当输入中的购买者字段是“中学生”“小学生”“大学生”时,如果它们的区别仅在于名称不同,系统对业务的处理完全一样,此时应该只保留其中一个实例作为代表。
- Tip2:提炼实例可以由简入繁。可以先把基本的成功情况提炼出来,再逐步推及到各种异常和失败。关注影响业务规则的实例,关注边界条件的实例。
- Tip3:实例应当有正反两个方面。比如,有一条业务规则是:对于购买重量在10kg以上的订单,才收取6元运费。所举出的实例就应该有正反两个:一个是重量是11kg,收取运费6元;一个是:重量为5kg,收取运费0元。
一个好的需求说明的例子:
一个劣质的需求说明的例子:
根据原则改进的例子: