在使用 go:generate 配合 Go 模板生成 Go 代码时,不应尝试用 gofmt 直接格式化含 {{...}} 的模板文件;而应在模板执行后、写入目标文件前,对生成的纯 Go 代码字节流调用 go/format.Source 进行格式化。
在使用 `go:generate` 配合 go 模板生成 go 代码时,不应尝试用 `gofmt` 直接格式化含 `{{...}}` 的模板文件;而应在模板执行后、写入目标文件前,对**生成的纯 go 代码字节流**调用 `go/format.source` 进行格式化。
Go 模板(.tmpl 或内联模板)本质是混合了 Go 语法与模板指令(如 {{.Field}}、{{range .Items}})的文本,并非合法的 Go 源文件——因此 gofmt 解析失败是预期行为,而非疏漏。官方工具链并未提供“模板级 gofmt”支持,原因很明确:格式化目标应是最终生成的 Go 代码,而非中间模板。
正确的实践是在代码生成逻辑中嵌入格式化步骤。以下是一个典型、健壮的生成器片段:
package main
import (
"bytes"
"fmt"
"go/format"
"os"
"text/template"
)
func generateModel(w io.Writer, data interface{}) error {
t := template.Must(template.New("model").Parse(`
// Code generated by go:generate; DO NOT EDIT.
package model
type {{.Name}} struct {
{{- range .Fields}}
{{.Name}} {{.Type}} ` + "`json:\"{{.JSONTag}}\"`" + `
{{- end}}
}
`))
var buf bytes.Buffer
if err := t.Execute(&buf, data); err != nil {
return fmt.Errorf("executing template: %w", err)
}
// 对生成的 Go 代码字节流进行 gofmt 格式化
formatted, err := format.Source(buf.Bytes())
if err != nil {
return fmt.Errorf("formatting generated code: %w", err)
}
_, err = w.Write(formatted)
return err
}
✅ 关键要点:
- 使用 bytes.Buffer 捕获模板执行结果(即纯 Go 源码);
- 调用 go/format.Source() 处理 []byte,它等价于命令行 gofmt -w 的核心逻辑;
- 错误需显式处理——格式化失败通常意味着生成逻辑产出非法 Go 语法(如括号不匹配、未闭合字符串),应立即修复模板或数据;
- 无需额外依赖,go/format 是标准库的一部分(go 命令本身即基于此包)。
⚠️ 注意事项:
- 切勿在模板中插入 gofmt 可读性 hack(如手动缩进 {{if}} 块),这会增加维护负担且易出错;
- 若生成内容包含多文件,建议为每个文件单独执行 format.Source,避免跨文件格式污染;
- 在 CI 中可添加校验步骤:生成后运行 go fmt ./... 并比对是否变更,确保格式化逻辑生效。
总结来说,优雅的代码生成 = 正确的模板设计 + 执行后即时格式化。把 gofmt 留给真正的 Go 源码,既符合工具链设计哲学,也保障了产出质量与可维护性。











