Go 1.26 的 go fix 重写:与 go vet 同框架,//go:fix inline 能做源码级 API 迁移
Go 1.26 发布说明:
发布博客:
这次工具链的主要变化是 go fix 被彻底重写,下面按使用顺序记录。
旧 go fix 的历史 fixer 全部删除
老 go fix 是附带一批历史 fixer 的独立命令,重写后这些已废弃的 fixer 全部删除。
新命令基于与 go vet 完全相同的 Go 分析框架(analysis framework),因此 go fix 现在是 modernizer 的统一入口,工作方式与 vet 的诊断机制一致:同一套 analyzer 既负责发现问题,也负责生成改写。
首批 modernizer:用上现代写法,但不改语义
首批包含几十个 fixer,覆盖语言与标准库的近代写法,把代码迁移到更当前的表达方式。
官方明确说明:这些 fixer 不应改变程序行为。若某个 fix 导致程序出问题,应上报,而不是绕过去。后续会有两篇 blog 专门讲 modernizers 与 inliner 的细节。
//go:fix inline:源码级内联,用于 API 迁移
除标准 fixer 外,还带一个源码级 inliner。给自己的函数加上 //go:fix inline 指令后,工具会把调用点直接替换成函数体,属于纯源码级改写。
这条最实用的场景是 API 迁移:把旧函数标注为可内联,跑一遍 fix,所有调用点就被改写成新实现,再删掉旧函数,不会留下中间层转发的 shim。
//go:fix inline
func OldOption() Option {
return NewOption(DefaultConfig)
}
内联并非对所有函数都成立:被标记的函数需要满足内联条件;不满足时不会发生改写,所以标注后要看实际 diff,不能假设全部生效。
用法与 CI 集成
批量执行,与 vet 一样接受包模式:
go fix ./...
预览时不要依赖“只打印不写入”的模式;比较稳妥的做法是在单独分支上执行,用 git diff 查看改写结果,确认后再提交。
CI 里的顺序是:先 vet 看诊断,再让 fix 应用。vet 阶段用来确认哪些包、哪些位置存在问题;fix 阶段只负责落地改写。
go vet ./... # 先看诊断,确认范围
go fix ./... # 再应用改写
常见问题
- diff 过大。批量改写会把整个仓库的写法翻新一遍,一次全量跑很难 review。实际做法是按包或按目录分批执行,并把每一步拆成独立提交。
- 必须有单测兜底。虽然官方声明行为不变,但改写量一大,靠人工比对不现实,跑测试比读 diff 更可靠。
- inliner 有适用条件。见上文,未满足条件的函数不会被内联。
顺带几处工具链变化
go mod init 默认写低一档的 go 版本。 用 1.N.X 工具链执行会写入 go 1.(N-1).0;1.N 的预发布版本写 go 1.(N-2).0。具体到 Go 1.26 RC 生成 go 1.24.0,Go 1.26 及小版本生成 go 1.25.0。目的是让新模块兼容当前受支持的 Go 版本。需要精确控制版本时,随后用 go get go@version 指定。
cmd/doc 与 go tool doc 被删除,统一用 go doc 代替,flag、参数与行为都一致。
pprof web UI 默认火焰图。 -http 打开的界面现在默认火焰图视图,旧的 graph 视图在 View -> Graph 或路径 /ui/graph。
与写代码直接相关的两条语言变化
内置 new 支持表达式形式 new(int64(300)),写 encoding/json、protobuf 里 *int 这类可选字段时省掉中间变量。
泛型类型可以在自己的类型参数列表里引用自身:
type Adder[A Adder[A]] interface {
Add(A) A
}
这两类新写法正是 modernizer 指向的目标形态,跑完 go fix 后大概率会在 diff 里看到。