QCon 全球软件开发大会(北京站)门票 9 折倒计时 4 天,点击立减 ¥880 了解详情
写点什么

环境无关的环境

2009 年 7 月 09 日

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

  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\
{1}

一般来说%~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 中文站用户讨论组中与我们的编辑和其他读者朋友交流。

2009 年 7 月 09 日 00:093753

评论

发布
暂无评论
发现更多内容

(转)程序员的写作课

Leo

学习 前端进阶训练营 技术博客

技术实践丨手把手教你使用MQTT方式对接华为IoT平台 华为云开发者社区

华为云开发者社区

技术 物联网 mqtt

区块链交易所系统开发,OTC场外交易所

135深圳3055源中瑞8032

硬核系列 | 深入剖析字节码增强

高翔龙

JVM 字节码插桩 bytecode JVM虚拟机原理 字节码增强

架构师训练营第五周作业

邓昀垚

极客大学架构师训练营

USDT承兑商支付系统开发,USDT跨境支付

135深圳3055源中瑞8032

开始真正的学习吧 -- 2020-10-20

BlueVitamin

「2020年字节秋招超万人」那么程序员跳槽时,如何选择公司

Java架构师迁哥

程序员

播客有没有未来?

善宝橘

播客

Flink中CoProcessFunction6-7

小知识点

scala 大数据 flink

晦涩难懂的CAP,是否完全正确?

架构师修行之路

架构师训练营第 1 期第 5 周作业

业哥

合约跟单APP开发,智能合约跟单软件

135深圳3055源中瑞8032

做好提醒巧防范 守好钱包防诈骗——南京移动防通讯信息诈骗志愿者服务进社区

Geek_459987

为什么迫切需要一套直接可落地的中台开发框架

高鹏

中台 业务中台 DDD 中台架构 业务架构

被延伸的“五感”:OPPO联合丹拿发起TWS耳机音质革命

脑极体

法定数字货币对银行存在潜在冲击,可能是第六版的人民币

CECBC区块链专委会

数字货币 金融

阿里P8架构师“墙裂”推荐:Java程序员必读的架构进阶热门书籍,值得学习!

Java架构之路

Java 程序员 架构 编程语言 推荐书籍

一个优秀的程序员,不仅要会编写程序,更要会编写高质量的程序

Java架构之路

Java 程序员 架构 性能优化 编程语言

甲方日常 34

句子

工作 随笔杂谈 日常

【高并发】学好并发编程,关键是要理解这三个核心问题

冰河

并发编程 高并发 同步 分工 互斥

用Python加载数据的5种不同方式

计算机与AI

Python 数据处理

从资金荒、恒大事件看区块链技术在供应链金融上的应用价值

CECBC区块链专委会

区块链 供应链物流

央视多方视频连线演播厅系统

dwqcmo

音视频 集成架构 解决方案 智能硬件

亚马逊向世界各地逾1000家慈善组织捐赠数百万件物资

爱极客侠

MapReduce简介及过程详解

犟马骝

hadoop mapreduce

LeetCode题解:98. 验证二叉搜索树,使用栈中序遍历,JavaScript,详细注释

Lee Chen

算法 LeetCode 前端进阶训练营

区块链钱包系统定制开发,去中心化钱包平台搭建

135深圳3055源中瑞8032

简约而不简单的分布式通信基石

架构师修行之路

TCP 分布式 微服务 udp

必须收藏:20个开发技巧教你开发高性能计算代码

华为云开发者社区

性能 并发

1分钟带你入门React Context

Leo

前端 React useContext Context 前端进阶训练营

边缘计算隔离技术的挑战与实践

边缘计算隔离技术的挑战与实践

环境无关的环境-InfoQ