---
title: Go module 的主版本号路径
date: '2019-12-06 07:58:52'
draft: false
summary: 把主版本号写进 import path，看起来麻烦，但它把兼容性断点直接变成了可见的命名变化。
slug: go-module-major-version-in-import-path
syndication:
- platform: Weibo
  url: https://weibo.com/1648815335/IjtlCcojz
tags:
- golang
- go-module
- dependency-management
- versioning
topics:
- software-engineering
type: post
---

前些天修 `go-commons-pool` 的一个 bug，顺手把它发布成了 `Go module`。结果很快就收到反馈：用 module 方式依赖时有问题。

查了一下才发现，问题不在功能，而在版本规则。

因为 `pool` 的版本已经是 `2.x` 了，按 `Go module` 的规范，主版本号大于 `1` 时，`import path` 里必须带上版本号。也就是说：

- 以前是 `import github.com/jolestar/go-commons-pool`
- 现在要写成 `import github.com/jolestar/go-commons-pool/v2`

第一次碰到这个规则时，直觉上会觉得很别扭：升级依赖，为什么还要改代码里的路径？

但后来再回头看 `Russ Cox` 对 `vgo` 原则的解释，会发现这个规则虽然不优雅，却有它非常明确的目标。

核心其实是三件事：

- `Compatibility`
- `Repeatability`
- `Cooperation`

其中最关键的是兼容性。

软件一旦进入长期维护阶段，最怕的不是版本多，而是不小心升级到了一个不兼容版本。其他很多语言会把这件事留给库作者和用户自己约定，比如：

- 继续沿用原路径，用户自己承担升级修复成本
- 改个库名，等于重新发一个项目
- 或者靠工具链和 lock file 做更多外部约束

而 `Go module` 相当于把其中一种做法强制标准化了：

只要你升级了主版本号，并且这个升级是不兼容的，那你就必须在路径上把它显式标出来。

效果上，这几乎等于说：不兼容升级就相当于给库改了名字。

这件事最大的好处，是多版本可以明确共存，用户也不会在无意中被“平滑升级”到一个行为已经不兼容的版本。

从工程角度看，这确实挺合理。

所以我现在对这个设计的理解是：它不是为了让语法更好看，而是在用路径层面的显式性，换取更稳定的依赖管理边界。

丑是丑了点，但它把很多原来靠约定和运气维持的事情，变成了工具链能强制执行的规则。

<!-- WEIBO_MEDIA_START -->
## 原微博中的媒体

![](./weibo-4446410482953405-1.jpg)
<!-- WEIBO_MEDIA_END -->
