【IT168 技术文章】
XP方法中把日构建列为软件开发管理中的非常好的实践;敏捷软件开发中也把持续集成当作是保证软件项目成功的一个原则。无独有偶,2003中国软件技术大会,上海微创软件公司技术总监蔡培讲述了微软公司的软件开发管理,演讲中提到微软软件开发管理中的一个重要实践,也就是日构建。(日构建和持续集成本质上是一样的,只是前者的频度是每日一次,而后者则并未限定频度。为了便于讲述,本文中不再区分这两个概念。)
关于日构建的作用和意义,书籍和网络上已有相关文章介绍,本文不再赘述。本文假设读者已经充分认识到日构建或者持续集成的好处,并且正打算把它应用到企业的开发管理流程中去,但是对于如何做日构建和持续集成,无论从技术上还是管理上还比较迷茫,那么这里将务实地与大家交流一下相关的一些的经验。
1.1. 日构建中的要素
归纳一下,日构建中有如下几项要素:
1.1.1 版本管理
所有参与构建的开发工件都应该纳入版本管理。有了版本管理,才能为了日构建稳定而可靠的输入。版本管理工具要为日构建提供的支持仅需包括一个命令行客户端工具。
1.1.2 流程自动化
日构建的处理复杂性依赖项目的大小以及加入的实际过程数量和内容而不同,但是不管怎样,努力达到流程的全部自动化是日构建的一个要求。流程的自动化有利于提高日构建的运行效率,减少人工干预引入的错误可能。
1.1.3 为日构建定制的管理制度
日构建完整的流程不仅只涉及到开发人员,实际还涉及到需求人员、设计人员、测试人、员、项目经理等多个角色,是一天中各方工作成果的集中体现。如此重要的活动,并涉及到多个责任主体,必然也要制定相应的管理制度来规范各个角色的行为,以便保障该项重要活动的有序、有效进行。
1.2. 日构建的一些策略
1.2.1代码统一管理
一般来说,只要软件开发中全部使用了版本管理工具来管理开发输出的工件,就具备了日构建的该项要素。实际情况中,无论采用微软的VSS,Rational的ClearCase,还是开源的CVS,都可以通过特定的客户端工具,以命令行的形式来获得所需项目的最新版本(或者特定版本)的工件来进行构建。(这里之所以说是工件,而不特指代码,是因为实际构建中的元素不仅有代码,还可能包括其他一些参与编译或者运行的工件,比如说元数据。)
日构建虽然是一个软件企业的日常开发管理行为,但是它一般是以项目作为构建基础的。不同的项目尽管不建议,但是还是允许采用不同的日构建工具、流程。
一般来说,我们会是先有版本管理工具,再有日构建的相应工具,而且,可能存在多个项目所使用的版本管理工具不一致。比如一个企业可能同时采用VB和Java开发不同的项目。那么采用VB的项目,基于与VirtualStudio的集成,可能更多选择了VSS作为版本管理;而采用Java开发的项目则可能选择CVS作为版本管理。对于一个软件企业来讲,为不同的项目选择不同的配置管理工具可能是个无奈之举,但是在选择配置管理工具的时候,该工具是否能够很好支持日构建应该成为一个考虑项。
下表给出了可以成功构造日构建系统的常见模式。
简单的日构建流程包括:下拉源代码->编译代码->运行测试。
复杂一点的日构建流程可能包括:
1. 下拉工件
从版本管理工具获得源代码、参与运行的资源文件(图标和图片)、配置、元数据等。
2. 工件统计(Statistic)/审计(Audit)
统计代码的有效行数以衡量生产率,个人工作量。
审计则可能包括审计工件命名风格,命名规范,多语言处理,异常处理等等。自动审计功能配合合适的管理制度,有助于帮助企业实现开发的规范化。
3. 编译或加工
编译很容易理解,这里的加工指对源代码进行某种事先处理,比如根据源代码生成配置文件和部署描述文件。另外,还可能包括特殊目的的代码替换和测试覆盖插入(Source Code Instrumentation)。
4. 部署、配置
让一个软件运行起来可能需要进行配置和部署。比较典型的是j2ee的程序,需要配置客户端和在应用服务器部署EJB描述符。
5. 运行
让软件按照特定的要求运行起来。(比如按照单元测试的要求,或者按照性能测试的要求)
6. 测试
日构建中可以引入各种测试。比较常见的是单元测试,实际上能够够自动化验证的集成测试和性能测试也可以加入进来。
7. 测试统计和审计
测试过后可能留下许多测试数据,比如单元测试的成功比例,每个单元测试执行的时间,性能测试留下的分析数据等等,这些数据可以在这一步充分利用起来,通过分析得出某种结论。
8. 发布报告和邮件通知
日构建过后,除了输出各种工作组件,重要的还要发布日构建报告。日构建报告最好以网页的形式在一个固定的地方公布,内容可以保罗万象,比如模块的责任人、编译成功与否、部署成功与否、单元测试的个数、单元测试的成功率、代码覆盖率、工件统计等等。为了便于大家察看,可以附带简要的邮件通知。
根据经验,流程自动化会涉及到几个难点:
第一, 如何解决编译的粒度和循环依赖问题
对于一个小项目来说,整个项目参与构建的工件可以作为一个整体一起拉下来,然后编译。如果编译未过,则日构建整体不成功。典型的例子是建立一个src目录,不同开发人员共享,所有的原代码都放在该目录中。
对于大项目而言(尤其是产品),由于有上百人参与开发,可能需要按照项目模块来组织开发,这个时候会存在多个src目录。在日构建中可以处理成把所有的src下拉后重定位到一个src目录进行编译。这种方案称之为大编译。但是这种方案有个问题,就是一旦某个模块的代码编译不能通过,整个大项目的编译也不能通过,除非这个编译问题能够立即解决,否则这将影响那些正常模块的日构建输出和功能验证。尽管大家会认为,编译问题自然要立即解决。但是这也就成为了影响日构建自动化的一个障碍因素。实际上,编译只是影响成功构建并达到可测试状态的其中一个影响因素,比如还可能存在元数据发布不成功,EJB部署不成功等。有些因素一旦发生,可能在某种特殊情形都无法短时间解决,这个时候就凸现了下面一种方案的价值。
这种方案的基本原则是每个模块单独编译,元数据单独发布。但是这种方案要面临如何解决模块之间编译存在依赖链,并且这个链还可能成为环状的问题。这里与大家分享的一种做法是先执行前面提到的大编译,然后再把大编译的输出作为其它各项目编译的输入。这样可以解决编译次序和循环依赖问题,同时可以真实判断出哪些模块成功,那些模块存在问题,为针对单个模块的报告提供依据。
实践中,曾经把一个三百号人同时开发的项目分成了60个模块。在项目的初期,一般情况都会存在个别模块在当日日构建无法编译通过,并且可能出现特殊因素无法立即解决。但是采用上述方案,这个有问题的模块并不会在编译期间过大制约其它多数模块的正常编译通过。
第二, 对于大型项目如何控制个别子模块不成功对全局的影响
一般来说,模块之间存在依赖关系,比如Module C依赖Module B,Module B又依赖Module A ,那么是否只要Module A 出现了编译问题,Module B和Module C在当日日构建就都不输出呢?这是个局部影响全局的问题。作者建议的做法是,Module A本次提交如果出现了问题,立即进行修复,特殊情况不能修复,由日构建系统自动根据编译结果判断启用Module A历史上最近一次成功版本。
有人可能会问,这种做法是否存在潜在的问题呢?的确存在,但是这个问题并不会想象的那么可怕。只要不是接口变更,一般来说ModuleA的问题只会影响ModuleA的内部,而不会殃及依赖ModuleA的其它模块。如果ModuleA 在今日日构建做了一个接口的变更,而且这个变更也通知了依赖它的Module B,并让Module B做修改,那么启用Module A的最近历史版本的确会存在运行时接口对不上的错误。对于这样的情况,可以不予处理,等待这个问题下一次日构建再解决。最好的办法是管理上要求当日必须解决ModuleA的编译问题。
上面提到的方法在几百人协同开发, 需求验证和测试并行的场景中经过检验,值得推广。
第三, 复杂环境的部署
可能很多人会认为解决了编译问题就解决了日构建的主要问题。其实不然,日构建的目的是产生一个最新的、可供测试的输出。为了达成可供测试的输出,首先日构建就要尽可能进行自测,以期在把工作产品传递到测试服务器、测试人员那里之前发现尽可能多问题。比如整个产品是否可以稳定的在应用服务器中加载?系统的登陆模块是否可以正常工作?菜单项是否可以正常读取?
基于上述目的,日构建自身要配置出可以运行的实际环境以便运行验证测试程序。比如BVT(Build Verification Test)。对于J2EE的应用,大家都知道,视产品部署模型的复杂程度这项任务可以变得非常复杂。
环境部署问题没有可供借鉴的统一模式。以笔者个人的经验,在日构建系统设计上如果能够做到与开发人员、测试服务器的部署模型和部署位置保持一致,就会大大减少环境的复杂性,这个时候可能需要人为制造一种统一环境。
1.2.3报告机制
日构建在某种程度相当于对项目的一次“体检”,体检的结果自然需要公而告之。公告的目的,是以实际数据来说明问题,并让项目中的所有成员对项目的现状达成一致的理解,并且这种理解是充满说服力的。日构建的报告可以做得很简单,也可以做得很复杂。形式可以采用web发布,也可以采用邮件通知。从项目管理的意义上,建议在这方面投入尽可能的精力和资源。
以本人参与设计的一个日构建系统为例,其日构建报告为web形式,左右结构框架。左边显示日期,点击该日期后在右边显示具体的报告。其内容包括:
按模块进行报告,报告中显示该模块的责任人(一般是负责该模块的项目经理)
每个模块的编译情况。通过或者未通过。未通过则显示编译出错信息。
每个模块的部署情况。成功或者失败。失败则显示失败的信息。
每个模块的单元测试数目,单元测试的时间,成功率,代码测试覆盖统计。
每个模块的代码行数,EJB数目,各种元数据的分类统计
每个模块代码的风格审计,异常处理审计,命名规范审计,多语言处理审计等等
1.2.4为日构建定制的管理制度
专门的日构建设备
必须为项目安排专门的日构建机器,而且要求机器的处理性能较高,内存容量较大。以目前PC机的处理性能,150人以下的项目,实践证明足以胜任。
日构建的时间管理
日构建并非每日仅构建一次。从持续构建的意义来看,日构建可以根据需要每日进行很多次。什么时候进行日构建?每日进行几次日构建非常好的?没有非常好的答案。但是根据经验,在项目的初期,由于功能大批量加入,测试还未全面开展,每日进行一次构建就可以了。随着、项目的推进,系统功能逐渐丰满、稳定,测试和需求验证也逐渐铺开,这时每日可以进行两次或者更多次构建。
日构建的时机一般放在下午为佳,如果个别模块发生问题,相关人员可以利用晚上的时间留守解决。微软的日构建发生在凌晨,并且自动调度执行。开发人员次日来了关心的头一件事就是自己负责的模块是否在日构建中进行得顺利。
午餐休息时间也是进行日构建的另一种选择。对于不大的项目,一个小时一般可以产生出日构建报告,这样开发人员中餐回来后可以直接对问题进行处理。而测试人员和需求人员也可以对上午的工作产品进行验证和测试。
日构建轮值
尽管完善的日构建是通过调度自动运行,但是对于应付一些突发的事件,比如产品临时检查,开发人员对发现的重大bug的立即修复等,可能需要人为调度日构建运行。原则上建议为日构建设立一位管理员。由于事物处理并非持续,这个管理员兼职即可。
建议采取管理员在各部门或者项目组轮值的方式。这样有利于加强各方对日构建的重要性和处理过程形成共同的认识,同时也可以避免出现关键人物依赖。
接口变更管理
不同模块之间可能存在接口的依赖,这里的接口并非专指程序语言中的Interface,而是包括所有在编译和运行期间依赖的部件。接口的变更管理是开发管理过程中的一个难点,尽管从软件设计角度,我们可以采取一些方法来尽量避免这种依赖,比如接口倒置,接口分离;但是在现实中,这个问题难以回避。持续集成的一个主要目的就是解决这个问题。
接口的变更原则上要求变更方主动通知接口依赖方,并且要求在日构建之前提前通知,以便接口依赖方有时间在集成编译之前能够调整原来调用老接口的代码。很多时候,这需要借助管理来保证。实际情况中,无法避免接口依赖方没有收到接口变更通知、或者来不及调整接口的情况,这种情况一旦发生,便导致接口依赖方的代码编译无法通过,问题就出来了。问题需要尽量避免,但是其产生并不可怕,这正体现了持续集成的作用——让问题尽早暴露。
日构建中的行为规范
日构建是一天中所有工作产品的一次集中验证,并且输出的组件要影响第二天的所有工作,为了保证其正确进行,需要规范参与者的行为。这种行为规范包括开发人员在日构建前的准备工作,日构建后的响应和处理。为了便于大家理解,这里以举例的形式介绍给大家。比如:
1. 开发人员在日构建前15分钟必须提交代码,以保证处理流程可以下拉正确的代码。