14.3 速度驱动的迭代规划
从广义的角度来看,迭代规划的方法有两类,我把它们分别称为速度驱动的(velocity-driven)和承诺驱动的(commitment-driven)方法。不同的开发小组会采取不同的方法,并且都可以取得成功。此外,可以在不同程度上混合这两种概括的方法。在这一节中,我们将考虑速度驱动的迭代规划;而在下一节中,则将关注承诺驱动的迭代规划。
图14-2显示了速度驱动的迭代规划所包含的步骤。首先,开发小组共同调整优先级。他们在前一次迭代中也许了解到一些会改变用户故事优先级的东西。接下来,他们确定接下来这次迭代的目标速度。然后开发小组选择一个迭代目标,这个目标是对他们希望在接下来这次迭代中完成的工作的概括说明。在选择了迭代目标之后,小组选择具有最高优先级的支持该目标的用户故事。根据需要选择用户故事直到它们的理想日或故事点总和等于目标速度。最后,把每个选中的故事都分解成任务,对每项任务分别进行估计。本节接下来的部分将更详细地说明这些步骤。

图14-2 速度驱动的迭代规划的步骤
14.3.1 调整优先级
假设所有的用户故事都被有形地堆叠起来或者在电子表格中进行了排序,具有最高价值的用户故事位于最上面而价值最低的用户故事位于最下面。项目可以在启动每次迭代的时候都从这个优先级列表的顶端选取用户故事。但是,商业条件和项目条件都可能会迅速地发生变化,所以迅速地重新考虑一下优先级总是值得的。
优先级变化的一个来源是迭代回顾会议(iteration review meeting),在每次迭代结束后都会进行这样的会议。在迭代回顾中,要向项目利益相关者、扩展的项目开发群以及其他所有感兴趣的人演示在这次迭代中增加的新功能和新能力。在这些迭代回顾中常会得到有价值的反馈。产品所有者本人在迭代回顾中通常不再提出新的想法或是变化,因为他平时都参与到了迭代中。但是,很多其他的人(包括潜在的客户和用户)也许是第一次看到迭代的结果。他们常会提出一些好的新想法,会取代以前确定的高优先级对象的位置。
正如第9章“确定主题的优先级”中所述,用户故事和主题的优先级是根据它们对产品的经济价值、它们的成本、小组可以学到的内容的量和重要性,以及减少的风险量来确定的。理想情况下,开发小组应该先等待前一次迭代回顾会议的结束,再讨论接下来一次迭代的优先级。因为他们在迭代回顾中听到的意见可能会对他们产生影响,而在完全确定下一次迭代中要完成什么工作之前是很难决定迭代中工作的优先级的。不过,我发现在很多公司中在一次新迭代开始之前几天举行确定优先级的会议是有益的。这样做可以更容易把迭代回顾和迭代规划会议放到同一天进行。
一次迭代回顾一般要花30~60分钟。对于由多个小组开发的大型产品,很可能需要产品所有者和确定优先级时必需的其他关键利益相关者在迭代回顾中花上半天的时间。加上规划一次迭代还需要再花4个小时,这样同一天中可能就没有时间来讨论优先级了。
我通常会把优先级会议安排在一次迭代结束前2天的时间。在那个时候,通常已经很清楚当前这次迭代是否会有未完成的工作。产品所有者就可以决定完成这些工作是否将是接下来的迭代中的优先考虑。产品所有者引导优先级会议,并找来所有他认为会对确定项目优先级的讨论有贡献的人。在这个会后,产品所有者通常根据在迭代回顾中发生的情况对优先级作即时调整。
14.3.2 确定目标速度
速度驱动的迭代规划中的下一步是确定开发小组的目标速度。大多数小组默认的假设是他们在下一次迭代中的速度等于最近一次迭代中的速度。Beck与Fowler(2000)称其为昨日天气(yesterday’s weather),因为我们对今天天气的最佳猜测就是它会和昨天的天气一样。其他小组偏好使用一个移动均值,这个均值也许是对过去3次迭代做出的。
如果开发小组以前没有合作过,或者是新采用敏捷开发过程,他们就必须预计自己的速度。第16章“估计速度”中讲述了完成这项工作的方法。
14.3.3 确定迭代目标
在得到了优先级和目标速度之后,开发小组需要确定他们在迭代中想要达到的目标。这个目标简要地说明了在这次迭代期间他们想完成的工作。作为例子,SwimStats的开发小组也许会选择“完成所有与人口统计信息有关的功能”作为一次迭代的目标。SwimStats Web站点的其他迭代目标例子可能包括下列的内容:
● 在报告生成上取得进展。
● 完成所有的项目成绩报告。
● 让安全机制生效。
迭代目标是对迭代中将要完成工作的一体的陈述。它并不需要非常明确。例如,“在报告生成上取得进展”是一个很好的迭代目标。并不需要使它更为精确,成为例如“完成15个报告”或是“完成比赛结果报告”。如果“在报告生成上取得进展”是对将在接下来的迭代中处理的工作的最佳说明,它就是对该目标的最佳陈述。
14.3.4 选择用户故事
接下来,产品所有者和开发小组选择用来组合以满足迭代目标的用户故事。如果SwimStats开发小组选择了“完成所有与人口统计信息有关的功能”作为迭代目标,他们将会处理所有尚未完成的与人口统计信息有关的用户故事。这项工作可能包括:
● 作为运动员,我可以更新我的人口统计信息。
● 作为教练,我可以输入我的每个运动员的人口统计信息。
● 作为教练,我可以导入包含所有人口统计信息数据的文件。
● 作为教练,我可以把所有人口统计信息数据导出到文件。
在选择将要处理的用户故事时,产品所有者和开发小组要考虑每个故事的优先级。例如,如果“导出人口统计信息数据文件”接近于产品需求优先级列表的底部,也许就不会包含在迭代中。这时,对迭代目标更合适的说法是“完成最主要的人口统计信息相关功能。”
14.3.5 把用户故事分解成任务
一旦选择了合适的用户故事集合,就要把每个用户故事分解成一组为了交付这一功能所必需完成的任务。假设具有最高优先级的用户故事是“作为教练,我可以把运动员分配到即将到来的比赛中的项目上”,这个用户故事会转化成一个任务列表,例如:
● 确定影响到谁会被分配到哪个项目的规则。
● 编写显示该功能能否工作的接受性测试用例。
● 设计用户界面。
● 从教练处获得对用户界面的反馈。
● 对用户界面编码。
● 对中间层编码。
● 向数据库增加新数据表。
● 自动化接受性测试。
有关迭代规划的一个常见问题是它应该包括哪些内容。把用户故事变成可用的、完成的产品所需要进行的所有任务都应该被鉴别出来。如果需要进行分析、设计、用户交互设计,以及其他任务,就需要对它们进行确定和做出估计。由于每次迭代的目标都是产生潜在可以交付的产品,因此还需要注意包括对产品进行测试和编写文档的任务。包含测试任务的重要性在于,小组在迭代开始的时候就需要考虑应该如何测试用户故事。这可以帮助测试人员从迭代一开始就参与进来,从而改善小组的跨功能行为。
1. 只包含为此项目增加价值的工作
迭代计划应该只确定那些能够为当前项目带来直接价值的任务。显然,应该包括可以看作是分析、设计、编码、测试、用户界面设计等的任务。不要包含您在早上回复电子邮件所花去的时间。这些邮件信息中的一部分确实是和项目有关,但是不要在迭代计划中包含类似于“回复电子邮件,1小时”之类的任务。
与之相似,假设您需要会见公司的人事主管,了解新的年度总评过程。这也不应该包含到迭代计划中。即使项目小组成员将要使用这个新的过程来进行年度总评,讨论它的会议(以及任何所要进行的后续工作)并不是与开发这个产品有直接联系的。所以与之相关的任务都不应成为迭代计划的一部分。
2. 尽量明确直到养成习惯
新的敏捷开发小组往往不熟悉或者不能熟练地编写自动化的单元测试。不过,在最初的几次迭代中,他们就可以逐渐培养出这一技能。在培养这一技能的过程中,我鼓励程序员明确地指出单元测试任务并估计。例如,一个程序员也许会确定对一个新功能进行编码需要8个小时,而编写它的单元测试需要5个小时。渐渐地,随着单元测试成为他的习惯,他会只写下一张卡片说明要对新功能进行编码,并给出一个包含了单元测试自动化时间在内的时间估计值。一旦类似于单元测试的任务成为了习惯,就可以包含在另一项任务中。不过,在养成这个习惯之前,明确这项任务可以帮助大家更注意它。
3. 考虑到会议(很重要)
您需要为与项目有关的会议确定、估计并包含相应的任务。对会议进行估计的时候,一定要考虑所有参与者都需要花时间,还要考虑用于准备会议的时间。假设小组安排了一次会议来讨论来自用户的反馈。所有的7名项目成员都计划要参加这个1小时的会议,而分析员计划花2小时来为这次会议进行准备。对这项任务的估计就应该是9个小时。我通常把它作为一个9小时的任务输入到迭代计划中,而不是为每个小组成员单独建立任务。
4. 故障
敏捷开发小组的目标之一是在发现故障(bug)的迭代中就修复它们。在他们逐渐精通采用小迭代周期以后,尤其是通过对自动化测试的利用,他们能够达到这个目标。当程序员做出对某项编码任务的估计时,这个估计值就包含了用于修复在实现过程中发现的故障的时间,否则就应该确定和估计一个独立的任务(“修复故障”)。我的偏好是只确定一项任务,但是在它通过所有的测试之前不认为已经完成。
后来发现的(或者在发现它的迭代中没有修复的)故障应该按照与用户故事一样的方法来对待。应该按照与用户故事一样的方法确定缺陷修复工作的优先级,分配到后续的某次迭代中。只要超出一次迭代的范围,就不再有什么故障的概念。修复一个故障和增加一个功能只是一件事的两种说法。
5. 处理依赖性
实现一个用户故事常常会依赖于首先实现另一个故事。在大多数情况下,这种依赖性不是什么大问题。我认为实现用户故事时总是有一种自然的顺序—— 也就是说,有一种对开发人员和产品所有者来说都有意义的顺序。
当用户故事之间存在的依赖性导向的是按照自然顺序开发它们的时候,不会有什么问题。自然顺序通常就是开发小组对故事进行估计的时候所设想的顺序。例如,SwimStats开发小组很可能设想运动员首先能够添加到系统,然后才能删除。如果处理用户故事的顺序和进行估计时的设想不一样,小组在进行迭代规划的时候往往就需要包含附加的任务来使得他们可以按照新顺序处理这些用户故事。
作为一个例子,SwimStats Web站点的自然顺序是先完成可以让用户向系统增加新运动员的功能,然后完成允许用户查看一名运动员在每个项目上的最好成绩的功能。先考虑查看运动员的最好成绩,再完成向系统添加运动员的界面,就有些不同寻常了。不过,如果产品所有者和开发小组想要按照这个顺序来开发这两项功能,也是可以完成的。当然,如果要这么做,他们就需要完成足够的数据库设计来保存运动员和成绩的信息。他们还需要在数据库中放入至少一个运动员以及他的成绩。由于这是他们不想首先开发的功能的一部分,所以他们就要直接在数据库中添加运动员(及其成绩),而不是通过他们开发的用户界面或是软件来完成这项工作。
如果SwimStats开发小组要这么做的话,他们在迭代规划的时候就需要确定一些任务,而这些任务在他们按照自然顺序处理这两项功能的时候则是不需要的。例如,如果系统已经具备了添加运动员的能力,开发小组就不需要包含一项任务来“设计用于运动员个人的信息数据表”。但是,由于对两个故事的处理顺序不同于自然顺序,就必需包含这项任务。
这是否就意味着不按照自然顺序开发会导致项目花更长的时间?有两个答案:也许不会,即使会也没关系。
首先,项目也许不会花更多的时间;我们所做的是把某些任务从一个用户故事移动到另一个中。这个例子中的设计运动员数据表迟早都要发生。在处理向系统添加新运动员这个用户故事的时候,它可以更快地完成,因为部分工作早已完成了。
您也许会担心这种工作的移动对两个用户故事的规模估计所产生的影响。例如,我们也许把一个用户故事中的一个故事点或是理想日移动到了另一个故事。在大多数时候,这不算什么大交易,项目的进程会消除它带来的差异。就算真有什么问题,我所观察到的也是一个保守的移动,比如说一个5点的故事变成了6点的故事。但是由于开发小组在结束的时候只给了自己5点的记分,他们略微低估了自己的速度。由于它所带来的影响只是稍微保守的偏差,我一般不会担心它。不过,如果您很关注这些影响,或者移动的任务显著得多,那么,一旦您决定不按照自然顺序进行处理,就应该对涉及的用户故事进行重估。
其次,即使按照这个顺序处理用户故事导致项目花了更长的时间,也没有关系。因为应该有一些很好的原因才会导致不按照自然顺序来处理它们。开发小组也许想按照特定顺序处理用户故事,以便能够更早解决一个技术风险。或者也许是产品所有者想更早获得某个故事的用户反馈,而按照自然顺序的话,更晚的时候才会开发它。通过比自然顺序提前开发这些用户故事,小组可以获得早期反馈,潜在地避免在临近项目结束时花去一两个月的时间进行返工(这时的进度表更不大可能容纳这样的变化)。
6. 难以分割的工作
某些功能尤其难以分解成任务。例如,我最近参加的一次规划会议讨论过对一项遗留功能的小变动。每个人都不太满意自己对这一变动所造成的所有影响的考虑。我们很确定某些部分的代码会受到影响,但是不确定其他部分是否也会受到影响。在我们能够确认的部分中,变化不大;我们估计总共需要4个小时。如果其他部分也受到了影响,我们认为估计值要高得多,可能会高达20个小时。只有查看了代码前我们才能确定,但我们又不想停止规划会议去干这个。我们写下了两项任务作为替代:
● 确定受影响的部分—— 2小时。
● 进行改动—— 10小时。
第一项任务称为探针。探针(spike)就是包含在迭代计划中,专门用来获取知识或者回答问题的任务。在这个例子中,小组难以推测某件事,所以建立了两项任务:一个探针和一个占位符,占位符中是对持续时间的猜测。这个探针可以帮助小组了解他们应如何处理另一项任务,从而允许他们对它进行估计。
14.3.6 对任务进行估计
速度驱动的迭代规划的下一个步骤是对每项任务进行估计。一些小组选择在确定了所有任务之后对它们进行估计;其他小组则每确定一项任务就估计一项。对任务的估计是以理想时间来表述的。所以如果我认为一项任务需要我花6个小时的工作时间,我给它的估计值就是6小时。即使这个6小时的任务要花去我一个8小时的工作日,我也应给出这个估计值。
虽然我也赞同为许多人所接受的建议,就是最佳的估计来自于将要处理这项工作的人(Lederer and Prasad 1992),但我仍然相信在敏捷开发项目中对任务的估计应该是一项团体活动。这样说的原因有4个。
首先,由于在迭代规划中并没有把任务分配到个人,就不可能让那个特定的要完成这项工作的人来做出估计。
其次,即使我们预期特定的某个人来负责某项任务,而且即使他对这项任务有最多的了解,也不是说其他人就不能做出任何贡献。假设在一次迭代规划会议上,James说:“我需要花2个小时来写这部分程序—— 它太微不足道了!”但是,您记起就在上个月,James处理一个类似的任务时也是这样说的,而实际上他花了将近16个小时。这一次,当James说一项类似的任务只需要2个小时的时候,您也许会补充说:“但是James,上一次你处理一项类似的任务时也觉得只需要2个小时,最后可花了16个小时。”最可能的是,James会回答一个相当合理的原因,说明为什么这一次的情况确实不一样,或者他会承认他系统地忘记了这类任务中的某个难点或额外的工作。
第三,听到预期需要多长的时间来完成某项任务,常常可以帮助小组发现对用户故事或是任务的误解。在听到一个期望之外的高估计值的时候,产品所有者或是分析师也许会发现开发小组正在走向一个超过必要的详细解决方案。因为是在小组成员间对估计进行讨论,所以可以在付出不必要的努力之前就纠正这个问题。
最后,如果由负责这项工作的人做出估计,他的自豪感和自尊心可能会妨碍他在以后承认做出的估计不正确。如果估计是共同做出的,就不会存在这种对承认错误的不情愿。
1. 可以进行一些设计
很自然地,建立任务和估计值列表的时候进行一些设计是必需的。如果我们完全不知道将如何开展工作,就无法建立任务列表。不过幸运的是,在规划一次迭代的时候,不需要在功能的设计上走得很远。
产品所有者、分析员和用户界面设计师可能会讨论产品设计、应该把功能实现到何种程度,以及应该如何把它显示给用户。开发人员也许会讨论如何实现所需功能的不同做法。两种类型的设计讨论都是必需的,也是合适的。不过,在我参加过的迭代规划会议中,从来都没有达到需要画类图或者类似模型的地步。进行这种行为的需求本身可能就是最好的警告标志,显示出在迭代规划中的设计已经走得太远了。把那些讨论留到迭代规划以后再做。
并不需要达到要绘制一个设计图的地步,因为目前所需要的所有东西仅仅只是对完成这些功能所需工作的推测。如果进入迭代以后发现确定的任务不正确,就扔开最初的任务,建立新的。如果估计值不正确,就划掉它写上一个新值。在记录卡片上写下任务和估计值是个很好的方法,因为每张卡片都可以微妙地提醒大家它只是暂时性的。
2. 任务的合适规模
建立的任务应该具有适当的规模,这样每个开发人员大致上能够每天完成一项。这样的规模可以有效地让工作顺畅地流过您的敏捷开发过程。更大规模的任务可能会在一两个开发人员手上产生瓶颈,小组的其他人就只能等着他们完成任务。此外,如果开发小组每天举行简短的例会(Schwaber and Beedle 2002;Rising 2002),这样规模的任务可以让每个开发人员在大多数的日子里都能够汇报完成了至少一项工作。
很自然地,常常会有比这个规模大的任务。但是通常应该把更大的任务看作是占位符,用于在有了更深入的了解后尽快增加一项或者多项任务。如果需要在迭代规划的时候建立一个16小时的任务,就那么做。但是,一旦对该任务有了更为充分的了解,就应该调整或者替换它。这可能意味着用不等于最初估计的16小时的任务卡片来替换原始的卡片。







