关于DVCS、持续集成和特性分支

本文来自于持续集成英文站的文章《On DVCS, continuous integration, and feature branches》。

为了吸引大家的注意力,我想说:“特性分支是邪恶的化身”。

自2008年起,Mercurial (最近是Git)就成了我日常工作的工具,而且我喜欢使用分布式版本控制系统。正如《持续交付》一书中讨论的那样(英文版第393页和394页),有很多理由说明,与之前已存在的同类工具相比,DVCS代表了一种巨大的转变。但正如所有强大的工具一样,你会有很多种方法来使用它们,但并不是所有的方法都是好的。这里所有的讨论不是想说DVCS不好:使用特性分支和使用DVCS完全是正交的。而且,我认为,DVCS的支持者用这种工具的功能分支来推销DVCS,是对DVCS的一种伤害。

首先看几个定义。请注意,一些人以不同的方式使用这些术语,所以你需要暂时从你的大脑中把先把其它的定义擦去,否则我的讨论就没有什么意义了。

持续集成 是一种实践,用于确保你的软件一直是可以工作的,并且在几分钟内你就能够得到关于 “你的修改是否破坏了系统”的反馈。
特性分支是一种实践, 使用它的人直到他所开发的特性“完成”后才合并回主干(这里的“完成“不是“done done1”原则中的那个“完成”)。
主干是指开发主线—— 通常是指版本控制库中的。你的某交构建就是从这个主干中的一个版本创建的,并会被放到你的部署流水线中。 注意,这些定义对DVCS和其它开源项目,甚至是GitHub,也完全适用。

先让我们解决一个象稻草人一样的争论。当使用版本控制工具时,你就使用一个分支来工作:即本地工作目录。使用DVCS,只是多了一个层次而已,因为你的本地库就是一个有效分支。我并不反对创建分支。我所不赞同的是:让你最终要发布的代码在分支上堆积起来。
下面是我所看到的状况。当你将最张要发布的代码大量地堆积在分支上,会有几个糟糕的事情发生:

  • 这种状态持续时间越长,就越难合并。因为随着其他人向主干提交,主干的代码与你的分支上的代码之间的差异会越来越大。强大的合并工具的确会在一定程度上帮助你,但是任何一个做过很多开发工作的人都会有过这样的经历,即:代码的确成功合并了,但是应用程序运行不起来。随着需要合并的那些代码数量的增加,以及初始分支与最终合并时间的增长,发生这种事情的可能性是递增的,而且不仅仅是线性递增。
  • 在分支上的工作越多,在合并到主干时越有可能破坏系统。每个人都有这样的经历:有个问题出现了,你似乎找到了一个非常聪明的解决方案。可是,几个小时(或者几天)后,你才发现需要废弃所有的东西,或者(更常见地)那些引起未预期后果的代码提交。
  • 当多个开发人员同时在这个代码库上工作,并使用特性分支开发时,使代码重构变得很难。如果我重构并且提交到主干了,而其他人在其分支上已经修改了很多东西的话,当他们向主干合并时就更难了。这就会驱使我不做重构。而不充分的重构就等于劣质代码。

当大家频繁将其代码合并到主干时,就没有这些问题了。如果不这么做的话,随着团队人数的增加,这些问题的痛苦会成指数级增加。而且,还有一个恶性循环: 这种痛苦的自然反应是:更少做合并。正如我喜欢说的,当某些事情令你很痛苦时,解决方案是更频繁地做,并将痛苦提前。在这里,解决方案是每个人都更频繁地合并到主干。

然而,如果你正在做的一个功能中需要做很多工作,或者你对系统进行大面积修改时,这么做就比较困难了。下面是一些解决方案:

  1. 将Story分解成一些更小块的工作(有时被叫做Task)。我还没有遇到过一大块工作无法分解成更小块的工作——通常少于1小时,并且肯定用不了一天时间——这样让我即能达到目标,又能保证系统可工作且可发布。这需要细心的分析、讨论、思考和严格的纪律。当我无法找到在增量开发方式下几小时内可以完成的工作时,我就会先对某些想法做一些试验(敏捷中叫做Spike2)。至关重要的点在于:我能尽早地对我的方案进行快速验证:是可以解决问题,还是会对系统造成不良后果,影响了其他人的工作,或者引出了回归缺陷(这也是持续集成的动机所在)。
  2. 在实现Story时,最后再做面向用户那部分接口。先从业务逻辑及以下部分下手。使用TDD完善你的代码。频繁提交,与主干合并。最后再完成界面部分。
  3. 使用抽象分支方法 来对复杂或大范围变更进行增量开发,同时保证系统一直处于可工作状态

你怎么知道什么时候没有合并的东西太多了呢?想像一下,假如你是一个开源项目的维护者,有一个你不认识的刚刚提交了一个补丁。你会马上合并它吗?你能记住它与主干之间的所有diff吗?你能保证在不需要问你的前提下,团队其他人员能在一分钟之内就能清楚地理解这些diff吗?如果你对上述所有问题的回答不是“Yes”, 那么你就要停止工作,将其分成更小的工作单元。
很明显,只要 “特性”足够小,我并不真正反对特性分支。然而,通常使用特性分支的人大多无法通过上一段中的问题测试。真正有经验的开发人员能理解使用特性分支与为了高效使用它而建立的纪律之间的平衡,但仍旧有一定的危险——GitHub就被Forks搞得很乱,这些Forks很多是不可合并的,因为它们与主干的差异太大了。

我更想强调的是下面这个观点,即:让“尽早地持续地交付有价值的软件” 中最重要的实践之一就是确保你的系统一直是可工作的。

对于开发人员来说,达到这一目标最好的方法是:通过确保他们可以将“提交可能对系统造成破坏”的风最小化。我们可以通过“保持小步提交、在主干持续集成,并且有全面的自动化测试套件来验收本次修改的预期行为,且不会引发回归缺陷”,从而达到这一目标。

主干开发,“关上”未完成的功能

软件开发中,“配置开关”根本算不上是一个“新”概念。无论是通过启动时加载配置,还是应用程序运行时动态刷新配置,都可以用来决定或改变应用程序的某种具体行为。比如:

  • 通过配置文件,使用应用程序连接不同的数据库
  • 通过模板配置,达到应用程序“换肤”的目的
  • 通过系统管理员的配置,使不同的用户具有不同的权限

所有这些“开关”都被认为是理所当然的,因为用户的需求就是这样的,“我需要通过xx开关来控制yy”。因此,做为交付团队,也会老老实实地把这些需求实现了。

然而,现在的很多团队,为了能够尽早发布软件,使用主干开发方式,并在程序中加入了另外一种“对用户透明”的开关。这种开关通常有两种使用场景是:

  1. 某些高级(或创新)的特性已经开发完毕,但业务人员根据市场情况,认为不需要投放市场。
  2. 市场需要某些已开发完成的特性,但还有一些特性尚未完成,仍旧处于开发中。

无论哪种情况,都是要求将某些特性进行隐藏后再发布。这种开关常常被认为是不可取的,但现在很多需要“持续交付”的公司都在使用。

(有的同学会说:“这完全可以通过按特性拉分支的方式来解决”。关于这个问题,我会在下一篇中讨论,本次仅限于讨论在使用“主干开发”的情况下,如何正确使用开关。)

开关最容易的地方就是应用程序的用户界面。当为已上线的应用程序重新设计了全新交互界面后,团队无法在一次性在同一发布周期中将其修改完成时,可以 先保留原有页面不变,同时每次仅替换少数的新页面。令所有未开发完成的页面对用户不可见,例如:原有页面的URL是 http://xx.xx.xx.xx/create_user,那么对应的新页面使用http://xx.xx.xx.xx/new_UI /create_user(要对后台逻辑的修改应能够同时响应新旧两个页面的请求)。当新页面完成后,将原来创建用户的页面链接重新指向新页面就可以了。 这样,就能在不破坏原有功能的前提下,做到持续发布。

开关实现形式

另外,对于那些无法在一个发布周期内实现的新功能,我们可以使用下面形式的“开关”:

  • 配置文件,通常在启动时加载

<features>
<feature name=”hq.remote_panel_load” />
<feature name=”el.enable_asset_library” />
…etc…
</features>

  • 在代码中实现

public function isFeatureOn(featureName:String):Boolean {
var nodes:XMLList = xml.features.feature.
(@name==featureName);
return null != nodes && 0 < nodes.length();
}

  • 设计成API

${core-url}/accounts/featureBits?userUid=&orgUid=&

而在编码中,可以使用不同的实现方式:

  • 最简单的检查

if( registry.config.isFeatureOn( featureName ) ) {
// new implementation …
} else {
// the old way …

}

  • 使用策略模式

if( registry.config.isFeatureOn(“ct.analyzer.v2”) ) {
service.analyzer = new Analyzer_v2();
} else {
service.analyzer = new Analyzer();
}

  • 使用工厂模式

public function createMainDisplay():DisplayObject {
if( registry.config.isFeatureOn( “service.panel.v2” ) ) {
return new panel2(); // which extends panel
} else {
return new panel(); // which extends DisplayObject
}
}

  • 使用责任链方式

if( registry.config.isFeatureOn(“hq.trickle_reporting”) ) {
userActionLogger = userActionLogger.setNext(new       TrickleReportNotifier() );
}

注意事项:

  • 提前评估是否需要开关,需要的话提前设计,不应该随意在代码中添加。
  • 定义一个开关命名规则,并在团队中达成共识。
  • 尽可能在设计时考虑在哪些层次上加开关。

“开关”在使用中的反模式:

  • 代码中到处都是开关,个数太多;
  • 在开关完成其使命后,未及时清理,使代码变烂。

现在,很多公司的产品都使用这种开关方式。进一步阅读,请参见:

相关工具:

代码示例来自于:Erik Sowa,”Feature Bits” on slideshare.net

本文版权归作者乔梁所有,转载请包含作者签名和出处,不得用于商业用途,作者将保留“追究法律责任”的权利!

ControlTier,基于命令的自动化部署工具

现在,服务器集群已经是司空见惯的事情了。随便一个小的互联网应用程序都需要用集群来支撑。而当采纳“持续集成”,尤其是“持续交付”实践时,在各种环境上的部署让你发疯。这些环境包括开发环境、测试环境(包括功能测试和非功能测试)、试运行环境和生产环境。而且,每种环境可能会有多套实例,而不只是一套实例。

曾经接触过的一个团队,其持续集成环境中就有260+台服务器,随着开发人员的提交,不同版本的应用程序在这些服务器上一遍又一遍地被部署、测试、卸载。而且,这些服务器的作用各不相同,有的是web服务器,有的是应用服务器,还有数据库服务器,也许还有消息服务器等等。想象一下,如果由你来管理这样一个团队中的服务器集群的部署,你的状态会是什么样子?

其实,当面对这么庞大的集群时,关键问题不仅在于知道每一步怎么做,而是:如何能够在所有的服务器上能够让所有的步骤都能够正确地被执行,而且要按照你期望的顺序,不至于各节点的执行步骤间互相受到影响。

Big problem for cluster management

其实,已经有很多工具解决这类问题了,ControlTier就是其中之一。它是一个跨平台软件系统,用于在多个服务节点中管理应用程序的相关操作。

它既有命令行工具,又有Web管理界面。用户可以自定义命令组,并重用它们。它与Puppet的一个区别在于:ControlTier是基于“对目标进行操作(Activity)”的工具,而Puppet则是基于“定义目标状态”的工具。所以,Puppet的“部署幂等性”在ControlTier里是很难办到的。它们在这一点上的区别也决定了它们使用层次上的不同。在《持续交付》的“环境管理”章节中,说环境配置管理的层次如下图所示:

而它们所服务的位置如下:

当然,你完全可以在系统层使用ControlTier,也完全可以在应用层使用Puppet,但是它们有各自的专长,在其它层次上使用意味着需要的定制工作量稍多一些而已。

你还等什么,赶快使用这些工具,去打造你的完美“持续交付”环境吧!

 

本文版权归作者乔梁所有,转载请包含作者签名和出处,不得用于商业用途,作者将保留“追究法律责任”的权利!

持续交付的8条原则

  1. 软件的发布或部署过程必须是可重复且可靠的。这就引出了下一条…
  2. 所有操作的自动化!我很难相信“手工操作是可重复且可靠的”这种说法。所以一定要将所有重复性的操作变成自动化的,从而变得可靠。
  3. 如果某件事情做起来很困难或者让你觉得很痛苦,那么就尽早且尽可能频繁地去做。乍一看上去,这么做太蠢了,因为人的直觉反应是:应该推迟这件事。然而,实际上,这句话是说:如果做某件事很痛苦,一旦要求自己更频繁地做,你就会有动力想出各种办法,来解决这个痛苦,很可能把它变成了自动化的,最终会把它变成一件简单容易的事情。就拿更新数据库结构来说吧。一般来说,没人想频繁地修改它,所以就会尽可能推迟或少做,比如一个月做一次更新,或者更长。然而,你真正需要做的却是改进数据库结构调整的流程,让它变成更容易,更频繁。甚至如果必要的话,可以一天做一次。
  4. 对所有内容进行版本控制。当今软件行业还在强调这种要求,你可能会觉得奇怪,谁现在还没有用版本控制呢?但是,我指的不仅仅是源代码哟,还包括环境、配置、数据等等。
  5. 完成意味着“已发布”。也就是说,项目的“完成”是指把它交到用户手中,并且可以正常工作。而不是“我已经提交了,后面的我不管了”,或者“我已经提测啦”,或者“我测试完了,没有问题。”
  6. 内建质量。在质量度量方面花一点儿精力。从长期维护的角度来讲,具有良好质量度量目标的项目(如单元测试覆盖、代码风格、复杂度等等) 要比没有这些度量的项目更容易一些。
  7. 每个人都要对交付过程负责。在开发人员机器上运行的程序不会为公司带来收益。没有部署的项目也一样。开发人员也应该时刻想着如何部署手中的软件。项目经理也应该关注什么时间部署。测试人员也应该进行部署测试。
  8. 持续改进。软件开发如“逆水行舟”,不进则退。持续改进意味着,你的系统需要一直改进,这样当需要时,才能很容易修改。

原文:http://www.dzone.com/links/r/the_8_principles_of_continuous_delivery.html

选择持续集成工具需要考虑的几个因素

目前,持续集成工具多达30种,每种工具都有自己的特点。在国内,软件企业很少为这类产品付费,所以国个目前最流行的包括Hudson(开源),CruiseControl(开源),TeamCity(商业版,买了IntellJ的License就能免费使用)。而在国外,还有两个比较流行的商业软件是AnthillPro和Go(原名为Cruise)。

根据目前软件发展的特点,在选择持续集成工具时需要考虑如下几个方面(不包括金钱投入):

  1. 版本控制工具的支持。在你的企业中,使用哪种版本控制工具(Git, Hg,SVN,ClearCase等等)。
  2. 每个构建是否可以支持指定多个代码源URLs。
  3. 是否支持构建产物管理库。
  4. 是否支持部署流水线,类似于一个或多个构建完成后触发另一个构建。
  5. 是否支持并行构建。
  6. 是否支持构建网格,以及对网格内机器管理的能力。即,能否将多个构建同时分配到多个构建机器上执行,以提高执行速度。
  7. 是否有良好的开放API,比如触发构建API、结果查询API、标准的Report接口等等。
  8. 对于安全性来说,是否支持企业所用的安全机制,如LDAP等等。
  9. 是否有良好的Dashboard。
  10. 与构建工具(如Maven,Make,Rake,Nant等)和测试工具的集成。

另外,“有良好的开放API”是任何工具选择的重要标准。因为,自动化是持续集成的关键。如果一个工具只能通过一个Web界面来操作的话,那简直就是一个噩梦。

详细的工具及特性列表见这里

 

配置与发布管理成熟度模型1.0版

在过去多年的持续集成咨询经历中,最常听到的一个问题是:如何来评估企业在配置与发布管理方面做得到底怎么样?还需要在哪些地方进行改进或提高?

两年前,CITCON的几个参与者给出了一个持续集成成熟度模型,聚焦于“持续集成实践”,而在《持续交付》一书中,Jez与Farley也结合ThoughtWorks在这方面的实践经验,总结了“配置与发布管理成熟度模型”,并在多个项目中进行了实际应用,效果不错。

由于这是通用模型,所以各等级中多为定性描述,在拿到具体企业使用时,可以根据实际情况,对其中一些定性描述进行量化,比如“提交不频繁”“定期沟通”等。

如何使用这个成熟度模型呢?

  1. 找出你所在团队在各维度上所处的级别。
  2. 找出你所在团队需要改进的维度,以及在该维度上的改进目标。
  3. 根据目标与现状,制定改进行动计划并实施。
  4. 重新评估,再制定下一个目标。

任何流程都是可以改进的,所以没有最好,只有更好。所以最高一级是“持续优化”。

模型内容可从以下地址下载:

http://download.csdn.net/source/3454593

持续交付-追求软件卓越的必读书目

持续交付,Jez Humble著,乔梁译

被Martin Fowler誉为2010年软件业最重要的一本技术书籍《持续交付:Continuous Delievery》于去年10月份在发布第一版。该书由ThoughtWorks公司的资深咨询师Jez Humble与David Fawley所著,可以说是该领域数年的经验总结。

自2007年开始,与Jez Humble一起,和来自四个国家的同事开发了Cruise(ThoughtWorks Studios 开发的一款持续集成与发布管理工具,现在改名为Go)。我们在两年内发布了8个版本,而且在V1.0中就引入了Pipeline的概念,而这正是本书的核心。本书是对持续集成、持续发布方面的多年经验做的精妙总结,而Cruise这款产品本身也是这些原则与实践的产物。如果你想真正了解持续集成是如何在真实项目中成功应用的,那么请一定不要错过这本书!

经过10个月的奋战,终于将第一稿译文完成,正在进行Review,希望能够尽快与读者见面。现附上前五章的中文目录。

Continue reading 持续交付-追求软件卓越的必读书目