gofmt和goimports是重构前的强制门槛,能解决90%格式问题但无法替代设计层面重构;函数职责、错误处理和结构体访问模式需手动干预,工具仅辅助统一缩进、空格与导入,确保diff干净、CR聚焦语义。

gofmt 能解决 90% 的格式一致性问题,但不能替代设计层面的重构;真正影响代码可维护性的,是函数职责、错误处理方式和结构体字段访问模式 —— 这些必须手动干预,工具只辅助。
gofmt 和 goimports 是重构前的强制门槛
不先跑 gofmt -w ./ 和 goimports -w ./,后续任何重构都容易被格式差异干扰。它们不做逻辑变更,但统一缩进、空格、导入顺序后,diff 更干净,CR 更聚焦语义。
-
gofmt -s启用简化模式(比如合并连续空行),建议加到编辑器保存钩子中 -
goimports会自动增删 import,但不会识别未使用的包别名(如json "encoding/json"却没用到json)—— 这类残留需人工清理 - CI 中应校验
gofmt -l ./输出为空,否则阻断提交
gopls 提供的 extract function 不适合复杂逻辑
VS Code 或 Goland 调用 gopls 的“提取函数”功能,在简单表达式或单层 if 分支下可用;一旦涉及变量捕获、defer、错误链或多个返回路径,生成的代码大概率编译失败或行为错位。
- 它默认把局部变量全作为参数传入,不考虑指针/值语义差异(比如原作用域修改了
err,提取后却传值副本) - 不会自动补
errors.Is或errors.As,仍用==比较错误,违反 Go 1.13+ 最佳实践 - 若被提取代码含
return,工具可能错误地将整个函数体包裹进新函数,破坏原有控制流
用 ast 包做安全替换时,导入语句最容易被忽略
写脚本批量把 log.Print 替成 zerolog.Ctx(ctx).Info() 时,AST 修改只动调用节点,import "log" 不会自动换成 import "github.com/rs/zerolog",更不会删旧 import。
- 必须显式调用
astutil.AddImport(fset, file, "github.com/rs/zerolog") - 要检查目标包是否已存在:重复 import 不报错但污染可读性;缺失 import 则直接编译失败
- 若原文件用点导入(
import . "log"),AST 解析后CallExpr.Fun是*ast.Ident而非*ast.SelectorExpr,需单独分支处理
结构体字段赋值重复逻辑,别用反射或 map[string]interface{}
看到十几个 AddXXX 方法都做长度校验 + 赋值,第一反应不是写个 SetField(name, value),而是用指针参数提取共用函数 —— 因为反射在 Go 里代价高、类型不安全,且 IDE 无法跳转、静态分析工具无法覆盖。
立即学习“go语言免费学习笔记(深入)”;
- 正确做法:
func (s *Struct) setString(dst *string, src string) error,调用方传&s.Name - 错误做法:
func (s *Struct) SetField(field string, value interface{}),内部用reflect.ValueOf(s).Elem().FieldByName(field) - 后者会让
go vet失效,go test -race无法检测字段竞争,且 panic 错误信息指向反射调用栈而非业务位置
gofmt 可以交出去,但 if err != nil 后该 return 还是 log.Fatal,得看上下文 —— 工具不理解业务语义。


















