在多年的IT生涯中,发现一个有趣的现象。项目或者产品出现问题的时候,大家痛定思痛总结教训,第一个被拎出来说事的,总是需求——需求不明确,需求总变更,需求不全面。我们写过大而全的需求文档,也用过敏捷的故事卡片,却总是无法将需求问题消弭于无形之中。近来有机会接触到了《实例化需求》的方法论,才真正感觉看到了一丝曙光。所学所练所想,分享于此。
1 什么是实例化需求
实例化需求这个概念来自于一本书《实例化需求:团队如何交付正确的软件》(Specification by Example: How Successful Teams Deliver the Right Software)。
书的作者叫Gojko Adzic,是一名英国的战略软件交付顾问。在近十几年来,一直在各种行业领域(例如财务和能源交易平台、移动定位、电子商务、在线游戏、复杂配置管理系统等等)从事着程序员、架构师、技术指导和顾问等工作。
实例化需求就是Gojko在这十多年的软件生涯中总结出来的一套行之有效方法。对于它的介绍是这样的:实例化需求是一组方法,它以一种对开发开发团队有所帮助的方式(理想情况下表现为可执行的测试)描述计算机系统的功能和行为,让不懂技术的利益相关者也可以理解,即使客户的需求在不断变化,它也具有很好的可维护性,可以保持需求的相关性。从而帮助团队交付正确的软件产品。
在我看来,这就是这套方法最牛的地方——帮助团队交付正确的软件产品。正确这个词,说起来简简单单,但实现起来却真的很难。
2 我们为什么使用实例化需求
我们都明白,需求是一切软件开发的源头。有了需求,开发才能开始。但,实际情况往往是,当项目发生延期,或是项目测试投产出了问题的时候,第一个被揪出来当替罪羊的也往往是需求——需求不明确!需求总变更!需求不全面!需求没写对!
你们开发也逃脱不了干系!不是应该搞明白了需求才开始开发的吗?怎么还会错?
造成这个问题的无非下面几种情况。
1)真的没好好搞清楚需求就开始了开发。
在需求的讨论沟通中,很常见的一种情况就是,刚开始说的是要做什么,为什么要做,讲着讲着,大家的探讨焦点就变成了怎么做,如何实现,然后迫不及待地投入到软件开发之中。很多团队都会有这样的误解:只有开始写代码才是真正的干活,澄清需求的时间并不多。 也许输入的需求并不足以具体到支撑起一个系统的开发,但却没人发现,大家只管埋头苦干,这为之后的问题埋下了很大的隐患。
2)项目干系人并没有对需求有着一致的理解。
常规的需求通常是用自然语言编写而成的文档,自然语言本身就会存在着一千个人有一千个汉姆雷特这样的理解偏差。如果团队使用的是需求-开发-测试这样的瀑布流程,需求在产生阶段并没有整个团队的介入,仅由需求人员负责,在开发之初才移交给开发团队,即使需求人员和开发人员有理解偏差也很难在第一时间发现。
3)不是我不明白,是这世界变化快。
互联网时代,软件致胜只有一个法宝——快。很多人慨叹,现在的时代过于浮躁,精雕细琢的东西不复存在。而事实是,整个IT界的业态变化得如此之快,如果你再按照从前的项目节奏走下去,当你的产品交付之时,也将是被淘汰之时。如今,大多相互的项目周期是按月来衡量的,项目阶段则是以周甚至是天来计数的。 需求变更是常态。 如果团队期待的是一份大而全,且永不变更的需求文档,并以此为依据进行开发,那项目延期或者出问题简直是必然的情况。
4)过于依赖个人的认知结构。
需求从哪里来?
有的团队的做法是把用户提出的需求当做用户故事的源头,竭尽全力去实现。但是用户对于系统和软件开发并无概念,所以他所说的有可能超过了系统的范畴。再加上用户也许并不真的像他以为的那么了解自己,往往把真实的需要隐藏在一个他自以为的解决方案中提出。如果真的照他说的做,那就惨了,只有在交付的时候才能听到他说——这不是我想要的。
另一种做法是由产品人员把用户的需求翻译过来。但是在翻译的过程中只有产品人员孤军奋战。而每个人总有每个人思维的局限性,一定会有想不到的地方。
除了上面的需求问题,还有一些问题也是让软件开发团队深恶痛绝的。
5)可怕的文档。
传统需求文档的养成过程是这样的:虽然极力想在开发之前全面描绘出软件理想中的样子,怎奈各种局限,只有寥寥数笔。此时的需求身材单薄,长相有些刻板但算整洁。
开发中不断发现需求问题,甚至于到了不改需求没法开发下去的地步,于是需求被改得日渐臃肿,从瘦弱少女变成身材走样的中年大妈。
到了开发完成进入测试,测出来bug但赶工期没时间回归,这时的法宝又是——改需求!谁让需求和测试结果一致就不算bug呢?可怜的需求再次挨刀,一副整容手术失败的沮丧模样。
这样的需求可读性差,可维护性更差。读的人一头雾水,写的人苦不堪言。但非常不幸的是,我们面对的需求文档却大多是这副丑陋模样。
而在所有的需求文档中,最可怕的莫过于遗留系统的需求。尤其是在你要改造或是重建遗留系统的时候,你徒劳地想从它浩如烟海又不说人话的需求文档中揪出蛛丝马迹,最后却只能仰天长叹。
6)返工危机
无论是传统的瀑布模型,还是敏捷的Sprint中,赶在工期之前把代码写完,再潇洒地丢给测试,都是能令程序员开心的事情。
什么会令程序员不开心呢?那就是测试工程师测出bug,再潇洒地丢给程序员。
如此往复,没完没了。
程序员向测试工程师大喊:你们就不能一次发现所有问题吗?
测试工程师向程序员大喊:你们就不能早点发现问题改了再交给我吗?
而项目经理向所有人大喊:再改就要延期了!!!
让我们来看看实例化需求可以做什么吧。
实例化需求的核心是,让项目的所有干系方进行有效的协作和沟通,用实例的方式说明需求,用自动化测试的方式频繁地验证需求,从实例化的需求说明和自动化测试用例中演进出一套“活文档系统”。这套“活文档系统”既可以有效地对系统进行说明,又可以当做交付验收的标准
- 有效的交流沟通确保有足够的时间澄清需求。
- 使用举例的方法澄清需求能在第一时间识别出需求是否足以支撑开发。
- 所有的干系方参与需求讨论,可以确保大家对于交付哪些东西有一致的理解。
- 具有不同领域背景的干系方一同参加需求讨论,可以规避因个人认知局限带来的需求问题。
- ”活文档系统”对于变更有着先天优势,可以以最少的维护成本维持文档的相关性和可靠性。又能避免过度说明需求而产生浪费,避免花时间在开发前有可能发生变化的细节上,对于变更天然友好。
- 采用自动化测试的方法实现业务实例,代码开发出来即可以验证,无须经过冗长的手动回归测试,降低返工。
完美解决上述问题。
3 实例化需求怎么做
Gojko在书中提出了实例化需求的主要过程模式,包含以下几个环节:
1、从目标中获取范围。
与客户沟通协作,以用户的业务目标为起始,通过团队协作找出可以实现目标的范围。
Tip1:不要把用户自以为的解决方案当做系统需求。问他为什么,想解决什么问题。由你的团队讨论解决方案,划定范围。
Tip2:需要牢记业务目标,为什么做这件事情,因为开发团队很容易把关注焦点转移到怎么做上。
2、从协作中制订需求说明。
协作是关键词。目的是让项目的干系人,包括产品、设计、开发以及测试都参与进来,发挥整个团队的知识和经验,排除理解的不一致性,尽量减少个人认知造成的局限。
Tip1:只有团队准备好实现的时候才开始实例化需求,例如迭代开始时。如果提前开始实例化,有可能在真正开发的时候需求已经发生了变更,又要重来。
Tip2:在协作的过程中需要大家共同建立起项目的领域模型,并在讨论中严格遵循领域模型,这样能确保大家对于术语和概念的认知是一致的,讨论是在共同的语境中进行。
Tip3:在外部需求不明朗的情况下(例如遗留系统的迁移项目),可以从系统的工作流入手,梳理需求。在梳理系统工作流的时候,不仅关注系统间的调用关系,也要识别出系统间的数据传递,识别得越明确,对于举例越有帮助。
Tip4:需求说明描述的是系统和用户之间的交互,不应该描述系统流程。也不应该和代码绑定紧密,陷入技术细节。也不应该过度关注界面。
3、举例说明。
举例说明是项目需求交流过程中不可或缺的,团队中的人领域背景不同,对同一个事物的理解也可能不尽相同,通过举例说明的方式可以让目标更一致。
在书中,作者提出,功能模块的例子必须具有精确性(不是简单的是或否的答案,使用具体的例子)、真实性(使用真实数据,从客户那儿获取真实的例子)、完整性(使用不同的数据组合去试验,利用其他方式去检验和测试),并易于理解(不用试验所有组合,寻找隐含的概念)。
Tip1:例子应该关注用户和系统之间的交互,而非关注系统本身的处理流程。因此,例子应该包含前置条件、输入、输出。前置条件指的是场景发生时,未作为输入传递到本系统中,但是已经存在,且对业务产生影响的数据。
Tip2:当大家用说的方式解释不清的时候,举例子是自然而然的选择。事实上,即使你觉得能说清楚,也应该举例,以免大家的理解有误差。例子应该具体而精确,避免使用范围。例如,不要用某一值“小于10”这一表述,而应该用某一值等于“9”来举例。
Tip3:在场景特别复杂的情况下,还可以使用流程图来辅助举例。使用什么样的方式不重要,重要的是这种方式能够达到在团队中澄清需求的目的。
Tip4:如果发现实例太复杂,就把它的复杂度降低,分解成若干个实例。例如,对于“如果数量大于10件,或者重量大于50kg,则收取50元运费”这个规则,可以拆分为“数量大于10件”和“重量大于50kg”两个规则,再来举出数量为20件和重量为60kg两个实例。
Tip5:在举例说明的过程中极有可能会发现之前未能识别出来的潜在概念。当潜在概念出现时,应当把它加入到领域模型之中。
4、提炼需求说明。
虽然协作过程中的需求讨论可以建立大家对相关领域的共识,但得到的实例往往包含很多不必要的细节。关键实例是从这些实例中提炼出来的,虽然精简但足以说明业务的实例。并且这些提炼好的实例本身就可以当作交付的验收条件。
Tip1:摒弃对业务走向没有影响的实例。例如,当输入中的购买者字段是“中学生”“小学生”“大学生”时,如果它们的区别仅在于名称不同,系统对业务的处理完全一样,此时应该只保留其中一个实例作为代表。
** Tip2:**提炼实例可以由简入繁。可以先把基本的成功情况提炼出来,再逐步推及到各种异常和失败。关注影响业务规则的实例,关注边界条件的实例。
Tip3:实例应当有正反两个方面。比如,有一条业务规则是:对于购买重量在10kg以上的订单,才收取6元运费。所举出的实例就应该有正反两个:一个是重量是11kg,收取运费6元;一个是:重量为5kg,收取运费0元。
5、在不修改需求的前提下,用自动化测试来验证需求。
Tip1:测试是为了验证需求,因此不可以只偏重于测试本身,而忽略了测试和需求之间的联系。过度饱和的数据和测试用例的大爆炸是不可取的。
Tip2:还是那句,测试是为了验证需求,因此不要在测试代码中引入业务流程或者业务逻辑,不要验证系统是怎么做的,而要验证系统做的事对不对。
Tip3:自动化测试有很多种优秀的工具,但不要执着于于特定工具,认为工具才是解决问题的王道。工具是为了测试服务的。
6、频繁验证。
在传统的流程中,庞大的需求说明往往跟不上实际开发中的需求变更,导致需求文档在交付之时就已经过时。代码才是唯一能真正信任的。但是,如果对于需求说明进行频繁验证,我们就能及时发现需求说明和系统代码之间的差异,及时解决它们,从而保证需求说明和代码是一直同步的。可以像信任代码一样信任需求说明。
频繁验证的方法有很多,自动部署,持续集成,排除不确定性,对外部系统使用Mock,等等,作者在书中给出了很多建议。
7、利用工具,提取组织良好的、易于寻找的、前后一致的活文档。
维护需求文档,通常是件让人头疼的事情。过于繁琐的需求文档会让人丧失维护的动力。可是,系统的重构和更新又偏偏需要这样一份文档。怎么办呢?所幸的是,实例化需求为我们提供了这样一种省时省力的方式——从自动化测试用例中提取文档!
提炼出来的需求实例是对系统做了什么事最有力、最准确、最新鲜的描述。而自动化测试用例又使用了可执行的方式实现了这些需求实例,我们再使用一些工具把自动化测试用例提炼为HTML或是PDF的文本,那么,砰!一份简单易懂的活文档就产生了!水到渠成,简单易懂,永不过时!
在作者的书中关于活文档有句话我特别喜欢——如果没有活文档,任何重大的重构都是自寻死路。
Tip1:活文档和敏捷中的用户故事卡有什么区别吗?好像很类似的样子。当然有所不同。用户故事是用故事的形式描述需求,活文档则是运用实例。另一个不同是,敏捷的故事卡在Sprint结束之后就没有用了,通常不会长久保存,而活文档则是易于保存,同步更新的。
写到这里,实例化需求的主要环节已经结束了。毫无疑问,实例化需求是一个好方法。但是,再好的方法也需要人的有力实施才能发挥效果。这其实也是本书作者的一种态度——实例化需求的方法更多的是确保团队成员之间的协作和沟通,而非推广工具。希望看到本文的人能让好方法发挥效用,令产品经理、程序员、测试工程师们的世界变得更美好。