InfoQ

InfoQ

文章

我的书签

登录注册 以永久保存书签。

该内容已经被标记书签!

标记书签错误,请重试!

环境无关的环境

作者 李光磊 发布于 2009年7月9日

领域
语言 & 开发,
架构 & 设计,
运维 & 基础架构,
过程 & 实践
主题
配置管理 ,
编程 ,
Java ,
.NET ,
敏捷技术 ,
敏捷 ,
Ruby
标签
持续集成 ,
生产力 ,
ThoughtWorks

软件开发过程中常常需要搭建各种环境:开发环境、测试环境,集成构建环境等等。一个不可复制的环境是低效的根源,它引起的常见问题比如:

  1. 产品只能在你的机器上编译通过
  2. 产品在你机器上运行正常, 可在测试环境中总是出错
  3. 新加入一个项目成员,需要一天时间来为其建立开发环境
  4. 把测试环境和集成环境迁移到另外一台服务器上花了几天时间

这些问题的原因以及解决方案,在最新出版《The Productive Programmer》(卓有成效的程序员)中,Neal Ford给出了详细的介绍。我们列举几种细化的方案,作为书中提到的“间接”、“规范性”等原则的实践。

试图解决的问题:环境的各个部分散落在不同角落,不是少了这个就是少了那个,或不同机器上版本不一样;环境配置依赖全局环境变量或属性,硬编码的绝对路径等。

目标:机器环境虽然各有各的不同,但依然有可能创建一个“环境无关的环境”。

1. 使用相对路径代替绝对路径

关键是如何获得当前路径,如何确定根路径,如何确保目录结构。

获得当前路径

  1. Windows和Unix都有内置的环境变量来表示当前路径,分别是%cd%$PWD
  2. Windows批处理脚本中,还可以使用%~dp0%获得脚本所在路径
  3. 而在Unix Bash 脚本中,则可以使用“pwd”,即获得pwd命令的输出
  4. Makefile中, 也可以获得shell命令的输出, 比如this_dir = $(shell pwd)
  5. Ant脚本中, 内置的basedir属性缺省代表的是脚本文件所在的路径

%cd%%~dp0%的区别:

  1. %cd%是脚本运行时的当前工作路径,与脚本所在位置无关
  2. %~dp0%则相反,是脚本所在路径,与运行时的工作路径无关

举例来说,有如下内容的D:\project\run.bat

echo %cd%
echo %~dp0%

如果在D:\盘根目录下运行project\run.bat,则输出如下

D:\>project\run.bat
D:\
D:\project\

一般来说%~dp0%%cd%更常用

下面是一个环境无关的makefile的例子(只列出了变量定义部分):

PROJ_ROOT=$(shell pwd)
INCLUDE += $(PROJ_ROOT)/include
LIB += $(PROJ_ROOT)/lib

当然也可以根本不定义变量表示当前路径,而直接以相对路径的形式引用子目录,和通过“..”来引用父目录及兄弟目录,然而显式的变量定义提供了一层间接,你可以通过多种方式覆盖它的缺省值,从而适应不同的环境。参见后面的“缺省值 + 用户自定义属性”。

确定根目录

如果总是需要引用某个根路径的话,则可以使用环境变量来定义根路径(参见后面的环境变量)。其实相对路径配合固定的目录结构,大大削减了对显式定义根路径的需求。

前面makefile的例子可以改写如下:

ifeq "$(origin PROJ_ROOT)" "undefined"
PROJ_ROOT = You_should_firstly_specify_$${PROJ_ROOT}_in_your_environment_or_by_command_line
endif

INCLUDE += $(PROJ_ROOT)/include
LIB += $(PROJ_ROOT)/lib

保证目录结构的固定,自然是使用配置管理系统。

2. 使用配置管理系统(版本控制系统)

这是一个“规范性”或者“标准化”的问题。配置管理系统不只是放源代码的,只要有配置管理需求或配置管理能带来好处,或需要有唯一的官方来源,都可以使用配置管理系统来管理;环境的配置文件,环境本身,都可以置入配置管理之下。有些公司的版本控制系统只放源代码,连测试代码都分开另放,耗费很多精力来维护源代码之外的文档。

  1. 强制使用配置管理可解决固定的目录结构的问题
  2. 使用配置管理还可以解决丢三落四的问题
  3. 自然也可以解决所用工具版本不一致的问题

这里有几个常见的问题:

  1. 大型的系统软件如何处理,比如JDK、VC++编译器等,是否也需要置入版本控制:这个基本不用,如果对其特定的版本有需求,可在脚本中加入检查其版本的逻辑,不满足则提示并退出即可
  2. 依赖的大量二进制库如何处理:如果有必要可使用依赖管理工具如Maven,Ivy 等,而用于存放依赖的本地仓库依然应该置入配置管理,哪怕不用其版本控制功能,而只是利用其官方来源/备份存档等好处
  3. 必须放在特定位置的文件如何处理,比如某个文件必须放在/etc目录下:这个文件还是可以放在配置管理下的目录中,而在/etc下创建符号链接来指向它;并提供脚本来干这件事。

3. 环境变量

必要时使用环境变量来引用环境。全局的环境变量可用作缺省值,在脚本中覆盖它(基本上,这是“用户自定义属性”的一个实例)。

4. 缺省值 + 用户自定义属性

这是创建“环境无关的环境”的核心机制。无论如何,环境要在不同机器上部署,总会需要修改某些配置,以适应宿主机器。然而前面我们提到,所有配置都已置入版本控制。如果我们直接修改,则每个环境中都会存在未提交的本地修改。这是我们不希望看到的,因为当我们升级配置并更新到所有部署时,可能会产生冲突。这里其实是两个层面的问题:

  1. 提供一种机制,当环境与缺省配置不一致时,允许用户修改
  2. 用户修改的文件应避免与官方文件更新的冲突

先说第一个。

缺省值当然可以直接定义在脚本或配置文件中。而多数常用的脚本和配置系统都提供了用户定义属性覆盖缺省值的机制。比如:

  1. Windows批处理:set path=my_extra_path;%path%
  2. Bash:export PATH=my_extra_path:$PATH
  3. Ant:
    <property file="user.properties"/> <!-- user.properties 中可定义任何后面引用到的属性,以覆盖其缺省值-->
    <property name="src.dir" path="${basedir}" /> <!-- 定义"src.dir"的缺省值-->
  4. CruiseControl:
    <property name="src.dir" value="." /> <!-- 定义"src.dir"的缺省值-->
    <property file="user.properties"/> <!-- user.properties 中可定义任何后面引用到的属性,以覆盖其缺省值-->
  5. 注意Ant的属性是只读的,先入为主。CruiseControl的属性则是后发制人。
  6. Makefile则可以直接在命令行覆盖文件里面定义的缺省属性。如覆盖前面例子中的PROJ_ROOT
    make PROJ_ROOT=/home/mike/project

再说第二个。

有一个很简单的解决办法,就是把用户自定义属性置入单独的文件,并且不要把它提交到版本控制系统中(一个理由是这一部分相对整个组织来说,不存在也不需要唯一的官方来源)

前面例子中的user.properties就是一个用户自定义属性文件,只存在每个用户自己的机器上,不在配置库中。在Windows和Bash脚本中也可以类似处理:

  1. Windows: call user_env.bat
  2. Bash: source ./user_env.sh . ./user_env.sh

随之而来的一个问题是,这个用户相关的文件应该放在何处。这里其实约定好就可以了,比如当前路径,根路径,甚至user的home路径都可以。

参考借鉴

  1. 至此,这套东西在不同的机器上部署时,只要从版本控制系统中check out出来即可使用了。更进一步,还可以提供脚本,即生成器,自动探测用户的环境,来生成全套配置,类似Rails生成应用框架那样。
  2. 其实,这是一个规范性或标准性的问题。Neal Ford 在《卓有成效的程序员》中,用一章的篇幅详述了各种解决方案,包括配置管理,符号链接等。除此之外,他还建议可以/应该使用虚拟机来统一项目组的开发环境等。参见《卓有成效的程序员》第五章。
  3. 另请参阅《CruiseControl Enterprise 最佳实践 (3) : Configuring CruiseControl the CruiseControl way》,是创建环境无关的持续集成环境的实例。

作者简介

李光磊,软件工程师,同时还是一位敏捷教练,就职于ThoughtWorks。他还是活跃的blog作者,了解他最新的想法,请访问http://blog.csdn.net/chelsea

相关阅读

[ ThoughtWorks实践集锦(1)] 我和敏捷团队的五个约定

[ ThoughtWorks实践集锦(2)] 如何在敏捷开发中做好数据迁移

[ ThoughtWorks实践集锦(3)] RichClient/RIA原则与实践(上)(下)

[ ThoughtWorks实践集锦(4)] 为什么我们要放弃Subversion

[ ThoughtWorks实践集锦(5)] “持续集成”也需要重构

[ ThoughtWorks实践集锦(6)] Mock不是测试的银弹


给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家加入到InfoQ中文站用户讨论组中与我们的编辑和其他读者朋友交流。

不是根本问题 发表人 周 海冰 发表于
不以善小而不为 发表人 Chen David 发表于
说的根本没什么意义。 发表人 Ma Karl 发表于
终于成文啦,收藏 发表人 Xiao Peng 发表于
  1. 返回顶部

    不是根本问题

    发表人 周 海冰

    太初级了吧
    真正进行持续集成的话,还会存在环境依赖问题?

  2. 返回顶部

    不以善小而不为

    发表人 Chen David

    注重开放中点点滴滴的细节,是每个开发者应有的好习惯。

  3. 返回顶部

    说的根本没什么意义。

    发表人 Ma Karl

    说的根本没什么意义。

  4. 返回顶部

    终于成文啦,收藏

    发表人 Xiao Peng

    评论家讨论风格画派时,画家在讨论颜料和松脂。

深度内容

应用云平台的可用性——从新浪SAE看云平台设计

云计算平台的可用性,相比传统互联网服务而言,更加复杂和困难,也更具有挑战性。本文借助新浪SAE云平台为读者讲述了云平台可用性的定义、如何打造高可用的平台,以及对云计算的用户提出了建议。

JVM定制改进 @ 淘宝

淘宝高度重视Java平台的健康发展,组建了一个团队专注于Java平台的底层部分的性能、功能与稳定性改进;工作主要基于OpenJDK中的HotSpot VM开展,其中一些通用的功能随后也会逐渐反馈给OpenJDK社区。希望能与使用Java平台开发应用的大家交流经验。
本次演讲视频录制于QCon杭州2011

"伤得起"的云计算应用——对云端应用之架构的思考

2011年4月21日至22日是值得云计算从业者纪念的日子。Amazon的IaaS服务出现故障,导致许多商业网站的服务中断,影响非常严重。作为云计算用户,我们需要思考的是,如何保证即便在云服务不可用的情况,我们的应用架构仍然能够屹立不倒?本文正是站在云计算用户的角度试图探讨这一问题。

让交付的速度跟上思考的速度

12人的技术团队,4组刀片服务器,每月20亿的访问量,每日1次准时部署,99.9%的可用性。这可能吗?当然。想知道如何做的吗?百姓网将与您分享他们在DevOps实践过程中的经验和技巧。
本次演讲视频录制于QCon杭州2011

架构之路——穿行在产品和业务之间

篱笆作为一家起源于社区的电子商务公司,反映到技术层面就是同时要面对产品和业务,以及经营战略的变化调整。如何在产品和业务的夹缝之间完成技术架构的抽象与平衡,寻找更有效的价值定位,这当中有些经验教训和个人感悟愿与众人分享。
本次演讲视频录制于QCon杭州2011

特性注入:成功三部曲

本文将对特性注入以及相关方法做一个扫盲性的介绍。我们会解释这个框架的关键要素,并附上实例来证实它们。为了让文章保持相对较短,我们不会深入到某个工具或方法中,而是会给出一些参考资料,以便大家做进一步的研究。

解析JDK 7的动态类型语言支持

随着JDK 7的发布,字节码指令集终于迎来了第一位新成员——invokedynamic指令。这条新增加的指令是JDK 7实现“动态类型语言(Dynamically Typed Language)”支持而进行的改进之一,也是为JDK 8可以顺利实现Lambda表达式做技术准备。在这篇文章中,我们将去了解JDK 7这项新特性的出现前因后果和它的意义。

Java Remoting远程服务(下)

随着互联网应用的发展,Java分布式远程服务技术受到越来越多的关注,本文将对各种相关实现以示例的形式逐一介绍,并总结其中的优缺点,使读者能够在技术选型时有所准备。这是文章的下篇。