Go 1.26 一键修复旧代码:go fix modernizers + Green Tea GC + synctest
来源:微信公众号
Go 1.25/1.26 不是小版本升级,而是工具链历史的一次转折——你的代码不再需要你手动维护了。gofix modernizers、Green Tea GC、testing/synctest,三个变化让你少写一半重构工单。
真正的主角:go fix
你有没有这种代码——
for i := 0; i < n; i++ {
...
}
Go 1.22 之后可以写成 for range n。你知道,但三年了,你还没改。
或者这种——
eq := strings.IndexByte(pair, '=')
result[pair[:eq]] = pair[1+eq:]
Go 1.18 就有 strings.Cut 了。你知道怎么用,但一直没动手。
这不是你的问题。这是大多数 Go 项目的真实状态:知道该改,但没人专门花时间去改。
从 Go 1.25 开始,情况变了。不是因为你变勤快了,而是工具链开始替你动手了。
go fix 重写:20+ 现代化分析器
go fix 不是什么新东西。它从 Go 1 就有了,以前只是偶尔用来修一下 build tag 格式之类的小补丁。但在 Go 1.26 里,它被彻底重写了——底层和 go vet 共用同一个分析框架。
现在有一套标准化的、可扩展的分析器体系。每个分析器专门处理一类代码现代化转换:
$ go fix ./...
| 分析器 | 功能 |
|---|---|
| any | 把 interface{} 替换成 any |
| minmax | 把 if x < 0 { x = 0 } 替换成 min(max(...)) |
| rangeint | 把 for i := 0; i < n; i++ 替换成 for range n |
| stringscut | 把 strings.Index(s, ":") 替换成 strings.Cut |
| mapsloop | 把手写 map 循环替换成 maps.Keys 等泛型函数 |
| forvar | 删除 Go 1.22 之后多余的 x := x 重声明 |
| fmtappendf | 把 []byte(fmt.Sprintf) 替换成 fmt.Appendf |
| inline | 基于 //go:fix inline 注解的源级内联 |
跑一遍 go fix ./...,相当于一次批量完成两年来你「知道该改但一直没改」的所有代码现代化。
联动修复:跑一遍不够就跑两遍
这是 Go 开发组自己都没想到会这么好用的效果。
有一段代码:
x := f()
if x < 0 {
x = 0
}
if x > 100 {
x = 100
}
第一次跑 go fix,minmax 分析器发现第一个 if x < 0 可以用 max 替换。改完之后,再看第二个 if x > 100,minmax 又发现可以进一步用 min 缩到一行。
最终结果:
x := min(max(f(), 0), 100)
同一个分析器按先后顺序发现两次机会。Go 团队的建议是:跑两次 go fix,就能达到一个不错的固定点。
//go:fix inline:让包作者定义迁移规则
Go 1.26 还做了一个更骚的操作:你可以在自己的函数上加一条 //go:fix inline 注解,让 go fix 自动化你的 API 迁移。
// Deprecated: 用 os.ReadFile 代替
//go:fix inline
func ReadFile(filename string) ([]byte, error) {
return os.ReadFile(filename)
}
用户跑 go fix 时,所有对 ioutil.ReadFile 的调用会被自动替换成 os.ReadFile,连 import 都会自动切换。
这个能力的想象空间很大。你在公司内部维护了一套公共库,某天要重构 API 签名。以前可能要写迁移脚本、群发邮件、挨个项目提 MR。现在?一边加注解,一边在 release notes 里加一行「跑 go fix ./...」。剩下的交给机器。
Google 内部数据:通过这个机制已经自动完成超过 18,000 个代码变更。
testing/synctest:并发测试不再 flaky
如果你写过 Go 并发测试,你一定遇到过这个选择:要慢,还是要 flaky?
testing/synctest 从 Go 1.24 开始作为实验特性引入,Go 1.25 正式 GA,彻底解决了这个问题。它的设计叫 bubble(气泡):
气泡内的 goroutine 使用合成时间。你在气泡里把时间往前拨 1 秒,这个操作会同步触发所有等待时间的 goroutine,不会有真实的睡眠。
效果是什么?测试可以压缩到几毫秒执行,并且完全确定。
一句话总结:如果你手上有 flaky 的并发测试,Go 1.25 的 synctest 比你任何一个 mock 方案都干净。
Green Tea GC:零代码改动的 10-50% GC 下降
升到 Go 1.25 设置 GOEXPERIMENT=greenteagc,或者直接升到 Go 1.26(默认启用),你的 GC 开销就降了。
Green Tea GC 的核心创意:按页扫描,而不是按对象扫描。
传统标记-清扫:对象之间随机跳转,路径像在城市里穿街走巷。
Green Tea:按页批量处理,路径像在高速公路上直行。
效果:Google 实际工作负载中,GC CPU 开销降低 10% 到 50%。堆较大的服务效果更明显。
而且注意:零代码改动。改一行 go.mod 的版本号,你的 GC 就变快了。
要不要升?
如果你的 Go 版本在 1.21 以上(含),应该升到 1.26。
理由三条:
- Green Tea GC 没有成本 — 零代码改动,默认启用
- go fix 降低维护成本 — 一次性清掉「知道该改但一直没改」的代码
- synctest 解决痛点 — 并发测试 flaky 变成一行 go test
唯一需要犹豫的场景:你的项目还在用 Go 1.18 或更早,升级语言版本本身需要做兼容性评估。
你可以带走的三句话
- 升级到 Go 1.26 后第一件事:跑
go fix ./...;如果一次不够,跑两次。 - Green Tea GC 是零成本的性能收益,不值得为兼容性犹豫超过两天。
- 有并发测试 flaky?testing/synctest 在 Go 1.25 已经 GA,它在标准库里,不需要第三方 mock 框架。