下面是一些应对之策:
- 不要做过多的并行开发。要力图将团队的注意力每次都只放在一个故事之上。
- 如果在“开户”完成之前就有人准备开始“存款”相关的工作,要等到“开户”彻底完成之后再检入“存款”相关代码。或者可以将“存款”相关的代码检入到一个临时的分支之中,如果你喜欢操作多个分支的话。
- 如果在“开户”完成之前就有人准备开始“存款”相关的工作,可以让他先从一些相对安全和不可见的UI元素等部分开始,这些东西的变化不会影响到整个分支的可发布性。比如,“存款”需要开发一些新的代码以及对一些旧有代码的修改,可以先实现新的代码(新的方法、新的类、新的测试等等),而不是先去修改已有代码。如果“存款”需要新的GUI元素,那就让它们先不可见。等到“开户”故事完成而且发布到主干上之后,我们就可以开始实现“存款”的剩余代码了。
下面是一个合适的规则集:
- 任何针对优先级最高的故事开发的人都是“国王”。
- 团队其他的任何人都是“仆人”。
- 要是你想成为“国王”,就试着找路子帮助完成最高优先级故事的相关工作吧。
- “国王”任何时候需要帮助,“仆人”就必须马上提供相应的服务。
- “仆人”不能打扰“国王”的工作。
- “仆人”不能向工作分支中检入不可发布的代码。“国王”可以检入任何他想要检入的东西(当然,只要他不违反分支方针即可)。
- 目前优先级最高的故事一“完成”,任何开始下一个最高优先级的团队成员就成为了“国王”。
你甚至还能为团队争取到一些王冠饰品呢。:o)
总体来说,很多团队都会过高评价同时实现多个用户故事的效果。这样做从开发速度上说感觉好像不错,但却只是一个幻象;因为它将有风险和消耗实践的编码工作推到了最后——包括合并、集成与测试等工作。
这就是Scrum团队应该保持小规模的原因(少于9个人)——这样他们就可以紧密协作而且将注意力集中于自己的工作成果之上。如果任何人都在独立开发自己负责的用户故事,那大概就不会有多少协作的情形发生了。当然可以有人为将来做计划,为下一个故事做准备并做一些实现工作。但是在任何时候,团队的主要工作努力都要放在优先级最高的故事上。
多个团队就是不同的情况了。如果很想并行实现多个故事,那就不妨创建多个团队。我们不久后就会看到如何具体操作。首先我想讨论下关于回归测试,以及分叉代码的相关话题。
完成包括回归测试在内的工作
当一个故事“完成”后,我们会把它移入到“完成”一栏中,并将相关内容从工作分支拷贝到主干中。主干必须一直保持可发布状态。此处有一个重要的暗示。
规则:任何接触到主干的人,必须保证整个主干保持可发布状态——包括之前的全部功能!
实际上,这条规则意味着:对故事A的测试,同样包括运行之前实现的故事的全部相关回归测试。如果上传代码后,故事A没有问题,但是之前故事的测试却通不过了,这是不行的。
稍等。这是不是有点不合理啊?每完成一个故事就要运行所有的回归测试?
嗯,首先我没有说运行所有的回顾测试。我是说所有相关的回归测试。我们已经有了一个干净而且可发布的主干作为基础,现在只是要添加一个故事而已!这是一个很小的增量变更。如果回归测试可以自动化完成,我们就可以全部运行了。要是有需要手工完成的回归测试,那我们就要有选择性了。
最后还是归结到了对风险vs成本的权衡之上。对于每一个手动的回归测试,我们应该评估运行它的成本(比如需要多少工作量来完成测试),同时评估发现任何重要缺陷的可能性,对二者进行权衡。当然还要加入自动化该测试的成本。:o)
分叉代码(合并冲突)
假设我正在兴高采烈地编写调用Widget类的代码,我却不知道团队成员Jim在一个小时之前进行重构时移除了Widget类。现在我们就有了< b>分叉代码。在花费更多时间编写其他调用Widget类的代码前,我希望尽早发现类似问题。
规则:持续不断地(等同于尽早)将你的代码同步到工作分支中。
这种同步是双向的。从工作分支中取得并合并最新的代码,然后检入你的代码。第一步可以叫做“跟上”(等同于我要知道其他人检入了哪些内容)。第二步可以叫做“公开”(等同于我希望我所做的更新可以让团队其他人都知道)。
每小时同步一次是好习惯,基本上在进行任务切换或者没有处于某项工作进行中时,就可以进行同步。这不只是关于“我要尽快知道别人的代码是否与我的相冲突”,还包括“我希望其他人尽快知道我的代码是否与他们的冲突”。要记得不要违反工作分支的方针(通过单元测试等等)。
该规则听起来很浅显,但是请允许我不断重申。我希望大家都能对其一清二楚,因为涉及到多个团队协作时,我们会再次使用类似的思维方式。
多个团队——如果其他团队同时向主干中发布代码该怎么办?
假设有团队A和团队B。他们都是跨职能团队,并且一起开发一个航班订票系统。团队A的注意力放在开发订票流程之上,而团队B主要负责开发后台相关功能。
假设他们现在要开始一个sprint,每个团队有两个用户故事要开发(通常一个sprint中会有更多的故事)。
由于每个团队都要在发布代码到主干前进行测试,因此他们有各自的工作分支。
现在我们遇到了一个有趣的问题。假定我在团队A中,而且有我们自己的工作分支。变更可能先在主干中发生,而不会先出现在我的工作分支中!为什么?恩,因为有另一个团队啊,他们每完成一个故事就会将其发布到主干中!
所以在任何给定的时刻,在主干上都可能有我不知道的新代码。而且这些代码可能(但愿不会如此)会与我的代码冲突!也许团队B中的某人会重新命名Widget类,可我已经在代码中调用了它而且……呃……等一下,我们不是刚讨论过这个话题了么?
没错,是同样的问题。解决方案也相同。但是范围有一点点不同。
规则:每天都从主干向你的工作分支中合并代码。
每天开始工作时,我所在团队中的某人负责将代码从主干中合并到我们的团队工作分支中(等同于“跟上”主干中发生的变化)。
如果我的团队(团队A)发现一个代码冲突,我们会马上解决它——这是优先级最高的事情!如果我们需要团队B的帮助(或者是开发与我们发生冲突的代码的负责人),找他们过来,一起工作,把问题解决掉。重要之处在于我的团队要负责解决问题,而且我们要在自己的工作分支上(而不是在主干上)完成。
规则:在最不稳定的分支上解决冲突。
当然,如果人们不经常向主干发布代码,那从主干合并代码就是浪费时间了。除非有人发布工作到主干上,团队A和团队B之间的任何分歧都会是不可见的。所以接下来的规则如下:
规则:经常从工作分支向主干合并代码,例如每完成一个故事之后。不要等到sprint结束后再做这个工作!
注意这里有一个有趣的副作用:
副作用:先检入代码者为王!
如果两个团队正在开发的代码互相冲突,后一个检入的团队必须负责解决冲突问题。这是一个好的副作用,因为它可以鼓励团队尽早检入代码。:o)
下面是一个完整Sprint的示例图:
两个团队进行了一次6天的sprint。团队A准备实现“预订”和“取消”。团部B准备实现“发票”和“黑名单”。让我们看看发生了什么。
Sprint完成了!除“黑名单”之外,所有的故事都完成了。但是没关系,我们还是可以发布的!因为我们是以增量的方式完成合并与集成的工作。如果我们等到sprint结束再做,任何分叉的代码就会在错误的时刻发现——此时我们能够用来修复问题的时间最少。
发布分支
假定我们完成了sprint 1并发布了系统的1.0.0版本。现在,我们在进行sprint 2之中的工作时,有人报告说之前发布的版本中发现一个严重的缺陷!不!我们该怎么办?
最简单的方式是:在主干上修复该问题,并发布1.1.0版本。这就是说在sprint 2中任何新实现的故事都会包括在新发布版本中。理论上来说,这样做没有问题;因为主干是“完成”分支,而“完成”的定义就是“可发布的”。所以主干上的内容无论何时都是我们要发布的东西。
不过还是会有一些原因让我们不想马上发布新故事。例如:
- 发现了严重的缺陷,实质上意味着主干在发布时就已经有问题了。也就是说sprint 2的故事都是在一个有问题的基础上构建的。在必须处理新的故事之前,我们会想要修复这个基础。
- 也许利益相关者不希望在sprint当中发布新的功能。
- 从主干中发布包含新功能和全部已有功能的新版本,也许需要一段时间才能完成;所以我们需要一个“hotfix”机制来更快地修复问题。
我们具体该如何做呢?
- 创建一个名为“发布1.0”的发布分支,基于它在发布时的主干内容。
- 在发布分支上针对缺陷打补丁。
- 在发布之后,马上将发布分支合并到主干之上(这样补丁程序就会包含在未来的发布版本之中)。
注意我们在发布1.0.0版本时没有必要创建“发布1.0”分支,可以等到问题出现时再做。这样以来,除非真的需要建立某个分支以让我们对其做些什么,我们就不必创建额外的分支。
大图景
好,现在我已经给出了如何使用该模式的详细范例。让我们往后站一点儿,看看整个的图景是什么样子吧。
在主线模型中,一个分支被称为一条代码线(实际上,分支可以被人物是一条代码线的实现)。有时这些被成为流。
一条代码线的上级(也就是该代码线的起源代码线)被称为它的基线。主线是没有基线的代码线。
所以在上面的例子中,我们可以总结出:
- 主干是我们的主线。它不就是没有上级么?
- 其他所有代码线(发布版本1.0,团队A的工作分支,团队B的工作分支)都以主干作为基线。
下面是一个更复杂的例子:
这个图告诉我们:
- 项目X的代码线衍生自主线。该项目目前已完成,所以分支就结束了。
- 团队A有一个衍生自主线的活跃工作分支。
- 团队A还有一个衍生自工作分支的、正在进行中的Spike(译注:Spike是指团队集中精力在短时间内尝试实现一个功能的活动。)。
- 发布版本2.3已关闭,因为2.3已经从生产系统中撤出而且不再会被维护。
每条代码线有一个相对其基线的坚固水平,也就是说每条代码线要不就比其基线更坚固,要不就不及其基线坚固(更柔软)。
- 一条坚固的代码线是稳定的,通过测试的,很少变更而且临近发布。
- 一条柔软的代码线不稳定,很少测试,经常变更而且远离发布。
在绘制代码线时,坚固的代码线分支向上,而柔软的代码线分支向下。观察上图,我们可以总结出:
- 发布版本2.3比主线更坚固。
- 团队A的工作分支比主线更柔软。
- 团队A的spike比团队A的工作分支更柔软。
使用上述描述绘制图表,对于展示分支历史来说很有用;但是如果同时有很多分支存在,就可能带来混乱。下面是一个更清晰的格式,仅展示了当前存在的代码线以及它们的衍生出处。
我建议以此格式绘制你的分支图,而且可以将其挂到团队所在房间的墙上。在讨论集成问题时参考此图真的很有帮助。
所有的变更都应沿着所在的线索发展,这是一条很重要的规则!所以不能直接从团队A的工作分支向团队B的工作分支中合并代码,这会导致很多混乱。实际上,在团队A工作分支中发生的变更应该流回到主线中,再向下进入到团队B的工作分支中。
任何位于主线之上的代码线都可以称为发布代码线,意味着一个比主线更坚固的代码线。
任何位于主线之下的代码线都可以称为开发中代码线(或工作代码线),意味着一个比主线更柔软的代码线。
协作黄金规则:
-总是接受稳定的变更。
-绝不强制使用会导致不稳定的变更。
那么这对于不同类型的代码线意味着什么呢?
上图以彩色的方式说明了:
- 任何时候在发布代码线上的变更,都应该立即合并到其基线中,并发布到主线上。
- 例:在2.4.2版本中修复了一个bug。这应该立即合并到2.4版本中,并将其合并到主线上。
- 一个发布代码线永远不要从其基线接受变更。
- 例:新的代码检入到主线中。该变更不应进入2.3版本和2.4版本。
- 变更应持续从基线流入到开发代码线中。
- 例:任何针对主线的变更应该迅速向下流入到团队A和团队B中,并从团队A向下流入团队A的spike中。
- 开发代码线的变更只有处于稳定点时才能发布到基线中。
- 例:团队B只有在一个故事完成并通过测试后才能向主线合并变更。
无论变更何时应用到代码线及其基线,有些合并是必须要做的。因为代码合并是一个很容易犯错误的操作,我们希望在两条代码线中稍柔软的一条上进行。一旦合并完成而且通过检验,我们就可以将合并的代码复制回更坚固的代码线。
将坚固代码线比柔软代码线绘制得更高,使用这个惯例,我们可以推出一个简单的规则:
规则: 向下合并,向上复制
示例:团队A注意到主线已经更新了。他们将变更向下合并到自己的开发代码线,并修复任何冲突。只要团队A的开发代码线达到稳定的一个点,他们就可以将代码复制回主线。当然,他们必须检查同时主线上没有发生任何变更。
模型的变种
“版本控制模式”一节描述了一个如何实施主线模型的范例。
“大图景”一节以更通用的方式描述了主线模型。
本节中,我将针对如何实施该模式提出一些典型的变种。
“完成”的定义不必一定是“可发布的”。
先确定“完成”一词的任何定义,然后确保有一个分支可以容纳根据该定义已经“完成”的故事。不过还是要注意别遗漏重要的内容。如果集成测试没有包含在“完成”中,那什么时候进行集成测试呢?
主干不必是主线
这个模式需要一条主线才能进行下去,不过不必是主干(虽然在大多数情况下,使用主干是很自然的选择)。
团队不一定必须有自己的分支
当然可以有多个团队共享同一分支,甚至直接在主线上展开工作。只要保证遵循分支的方针即可。
通常,团队希望有自己的分支,以避免未完成的故事在多个团队之间造成干扰。
不必每次都创建一个全新的发布分支
可以使用同样的发布分支,而不是在每个sprint结束后都创建新分支。那个分支可以称为“最新生产系统”或其他类似的名字。如果在生产系统中总是保持只有一个版本,这当然是很好的模型。
不必在每个sprint结束后都进行发布
可以在每个故事完成后进行发布。或者每三个sprint完成后发布一次。确定你自己的步伐。
不必针对每个故事都运行回归测试
如果在你的环境中,回归测试或集成确实很难实际操作,那么可以在sprint接近尾声时进行。这样就可以针对一批故事进行测试和集成的工作。你自己要承担相关风险。如果回归测试和集成属于“完成”的定义,这可能意味着你在sprint末尾时可能会遇到问题,导致没有任何故事完成的风险。
FAQ
持续集成(CI)在这个模式中如何使用?
CI服务器应该针对哪个分支运行?这要根据具体情况,不过下面的叙述是一个好的起点。
假定主干的方针是“完成而且可发布”,而每个工作分支的方针是“通过单元测试”:
- 对每个工作分支来说,CI服务器自动并持续地检查构建和运行所有单元测试的状况。
- 如果有任何失败,就给出一个红色警告。让机器冒烟……
- 对每个工作分支来说,CI服务器自动并有规律地(如果不能持续地)运行集成测试和回归测试。
- 如果有任何失败,就给出一个分离的警告。因为这不是当前分支的方针。
- 当有人考虑从工作分支向主干发布代码时,要触发该手工测试,以检查故事是否“完成”。
- 对主干来说,CI服务器自动并持续地运行集成测试和回归测试。
- 如果有任何失败,就给出一个红色警告。让机器冒烟、触发警笛、USB火箭发射器,再把国民防卫队叫来。
该版本控制模型使用哪种工具最合适?
不确定。我知道Perforce是可以的,我想subversion应该也没有问题,但是对于CVS我不敢打包票。欢迎任何新的建议。
不过要记得一个重要的事情——切换工具的成本要比不能有效协作产生的成本低得多!所以搞清楚你想怎么做,然后找到合适的工具来支持。
与用户故事无关的检入怎么处理?
不是所有的代码变更都必须与某个用户故事相关的,在例子中,我只是为了描述的清晰才这样做。无论检入何种类型的代码(或文档之类),同样的模型也是可用的。
合并代码很麻烦,所以我想做得越少越好!
恭喜,你得了合并恐惧症——对于合并代码的非理性恐惧!合并让你觉得麻烦,是因为做的太少了。合并得越多,痛苦就越少。:o)
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/14780914/viewspace-410107/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/14780914/viewspace-410107/