72 | 发布单元与版本管理
前言
如何确保软件工程质量?
如何管理版本?
如何确保迭代的稳定性?
源代码版本管理
对可执行程序进行版本管理,仅仅是召回上个版本
如果想要深究问题的根源 ,那么我们就要对其源码进行管理。
我们很容易想到将源码和可执行程序的版本对应起来并且保持一致
但是事情没那么简单
请看下图:
不同模块可能由不同团队开发,甚至有些模块是外部第三方团队开发。这意味着,从细粒度的视角来看,一个软件工程的生命周期中,包含着很多个彼此完全独立的子软件工程。这些子软件工程它们有自己独立的迭代周期,我们软件只是它们的 “客户”。
发布单元的概念
拥有独立的迭代周期的软件实体,我们称之为 “发布单元”。你可能直觉认为它就是模块,但是实际上两者有很大的不同。
对于一个发布单元,我们直观的一个感受是它有自己独立的源代码仓库(repo)
发布单元可能是:
- 可执行程序
- 动态库
- 虚拟机自定义动态库
- 静态库
- 源码
发布单元的输入,包含如下两个部分:
- 若干自己独立演进的模块
- 自己依赖的发布单元列表
以 github 为例:
它提供了以下源代码质量的管理手段:
- 团队成员开发活动的独立性
- 完善的代码质量检查机制
- 完善的回滚机制(revert
代码质量检查过程,需求显然比较易变。所以在这里 github 做了开放设计。我们再一次感受到了开闭原则的威力。
发布单元的外部依赖管理,通常不同语言有自己的惯例
所有外部依赖管理无非要达到这样一个目标:指定我这个发布单元依赖的各个模块(嗯,这是通俗说法,其实是指依赖的发布单元)的建议版本是什么
前提假设
- 我们不能修改发布单元自身包含的各个模块的的代码
- 我们不能修改发布单元依赖的外部模块(同样地,其实指依赖的发布单元)的版本。
还有两个东西没有做到只读:
- 操作系统内核
- 编译器
软件发布的版本管理
我们大家可能都接触过各种软件发布的管理工具,比如 apt、rpm、brew 等等。
但不是每一次软件安装过程都是OK的
用户之间系统环境的差异太大了。让每个软件的发布者都能够想到多样化的环境并加以适配,这是非常高的要求。
解决方式:
容器化
容器的镜像(image),不只是包含了软件发布的可执行程序本身,也完整包含了运行它的所有环境,包括依赖的动态库和运行时,甚至包括了它依赖的 “操作系统”
只读设计的确定性
软件项目的管理期望达到确定性。但软件工程本身是快速变化的,是不确定的。这就是软件工程本身的矛盾。我们的目标是在大量的不确定性中找到确定性,这其实就是软件工程最核心的点。
在业务只读,接口稳定的预期下,模块与模块之间就可以自由组合,构建越来越复杂的系统。
我们开发的时候,有时候会倾向于变量只读,以提高内心对确定性的预期。我并没有去用严谨的方式实证过变量只读的收益究竟有多大,但它的确成为了很重要的一种编程流派,即函数式编程。
函数式编程从编程范式来说比较小众,但是其只读思想被广泛借鉴。
这里面最典型的就是大数据领域的 Spark。
对一个只读的 RDD 施加一个变换(transform),即得到另一个 RDD
版本的兼容问题
让一个模块依赖另一个模块(严谨来说是发布单元)的特定版本,这解决了版本的确定性问题。
为什么依赖模块的重构会给我们的系统带来未知风险?
但有时候我们无法放弃兼容。这发生在我们在做一个互联网服务时。一旦我们发布了一个 api,它就很难收回,因为使用这个 api 的客户端可能有很多。如果我们放弃这个 api 就意味着我们放弃了很多用户,这是不可接受的。
比较常见的做法是为所有 api 引入版本号,如 “/v2/foo/bar”。当我们对 api 发生不兼容的修改时,就升级版本号,比如 “/v3/foo/bar”。
好处:
如果我们对某个复杂模块进行了全局重构,并且兼容老版本的行为细节非常困难时,我们可以直接升级所有 api 的版本号

本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。
- 上一篇: 71 | 如何阅读别人的代码?
- 下一篇: 09 | 面向对象:实现数据和方法的封装
目录