向后兼容

兼容性指的是源码的兼容,当你更新Go版本之后,需要重新编译,我们可以添加新的API,但是不应该破坏现有代码的使用。Go 1 and the Future of Go Programs对Go1的兼容性进行了描述,以及不保证兼容性的多个例外场景。

简单来说,就是老版本的Go代码可以使用新版本的Go工具链正确地编译和运行。

保持兼容性的主要手段

Golang保持兼容性的主要手段有两个方式:API检查和测试。

API检查

Go使用一个工具维护每个package export出来的API

api/ (master) $ ls
except.txt  go1.13.txt  go1.17.txt  go1.20.txt  go1.4.txt  go1.8.txt  README
go1.10.txt  go1.14.txt  go1.18.txt  go1.21.txt  go1.5.txt  go1.9.txt
go1.11.txt  go1.15.txt  go1.19.txt  go1.2.txt   go1.6.txt  go1.txt
go1.12.txt  go1.16.txt  go1.1.txt   go1.3.txt   go1.7.txt  next
$ cat go/api/go1.21.txt
pkg bytes, func ContainsFunc([]uint8, func(int32) bool) bool #54386
pkg bytes, method (*Buffer) AvailableBuffer() []uint8 #53685
pkg bytes, method (*Buffer) Available() int #53685
pkg cmp, func Compare[$0 Ordered]($0, $0) int #59488
pkg cmp, func Less[$0 Ordered]($0, $0) bool #59488
pkg cmp, type Ordered interface {} #59488
pkg context, func AfterFunc(Context, func()) func() bool #57928
pkg context, func WithDeadlineCause(Context, time.Time, error) (Context, CancelFunc) #56661
pkg context, func WithoutCancel(Context) Context #40221
pkg context, func WithTimeoutCause(Context, time.Duration, error) (Context, CancelFunc) #56661

golang会有一个测试专门来检测实际的package APIs是否匹配这些文件。如果新增了新的API到一个package中,除非将相关信息添加到这个api文件,否则这个测试就会break。

Dave Cheney的how-go-uses-go-to-build-itself,介绍了go tool api的使用。

实际上在go1.20go1.21中都会显示go: no such tool "api",不是很清楚是要被替换掉,还是说未来会修复,起码我没在release note和issue中看到相关信息。

但这种方式只能检测某种类型的问题,也就是API变更和移除。还是有很多其他方式会改变go的兼容性。

测试

和所有软件一样,版本升级都需要靠测试来保证兼容性和质量。Golang是怎么做的呢?

  • 对下个版本的代码运行现有的测试用例
  • 定期对Google内部的Go代码进行新版本的go测试。
  • 当测试通过时候,提交安装为google的生产工具链
  • 如果一个变更破坏了google的内部测试,可以认为它也会破坏外部测试,需要寻找减少影响的方法。
    • 回滚变更
    • 找到一种方式重写,使其不破坏任何程序
    • 如果这个变更很重要,即便破坏了程序也需要发布,那么会尽力减少影响,并在发布声明中记录可能的问题。

PS:其实这种策略在基础组件开发中都是可以借鉴的:组件本身测试、小范围运行测试版本、最后release正式版本。只是大多数公司,没有google这种内测规模也没有稳定的版本发布周期。

Go1.21的改进

为了提高向后兼容性,Go1.21正式规定了Go使用GODEBUG环境变量来控制那些根据Go1的兼容性承若是非breaking,但可能导致已有程序行为受影响的默认行为变更(比如,bug的修复就不会考虑对现有程序行为的影响)。

为了兼容性添加的GODEBUG最少会被维护2年(4个Go版本),某些可能会延长,甚至无限期。

在可能的情况下,每个GODEBUG的设置,都有一个关联的runtime/metrics 名为/godebug/non-default-behavior/<name>:events的counter,来统计非默认行为的使用情况。

Go1.21.1可以在main包的源文件的package语句之前加入//go:debug指令来覆盖DODEBUG默认值。

GODEBUG详情见Go, Backwards Compatibility, and GODEBUG

在Go1.21之后,当Go产生行为变更的时候,它会根据在workspacego.workmain模块的go.mod文件的go line来判断新旧的行为的选择。

参考