用text/template写生成器比拼接字符串更安全可靠,因其支持嵌套/条件/循环、自动转义、解耦模板与逻辑、导出字段要求、存在性检查、go/format格式化、AST语法验证、indent缩进控制、参数化配置、import自动管理及分组插入。

用 text/template 写生成器比拼接字符串更安全可靠
硬拼接 Go 代码字符串(比如 "func " + name + "() { ... }")极易出错:括号不匹配、引号转义漏掉、关键字被当变量名。Go 标准库的 text/template 是专为这类场景设计的,支持嵌套、条件、循环,且自动转义危险字符(比如模板里写 {{.Name}},值是 "user's id",输出就是安全的字面量)。
实操建议:
- 把模板文件单独存为
api_handler.tmpl,和业务逻辑解耦 - 用
template.ParseFiles()加载,别用Parse()硬编码模板字符串 - 结构体字段必须导出(首字母大写),否则
text/template读不到 - 生成前先用
os.Stat()检查目标文件是否存在,避免静默覆盖
模板中调用 go/format 保证生成代码格式合规
即使模板写得再规范,手动换行缩进也难保符合 gofmt 风格。直接在生成流程里调用 go/format.Node() 是最省心的做法——它会按官方规范重排版,还能提前暴露语法错误(比如少了个 })。
常见错误现象:生成的代码能编译但 go vet 报 composite literal uses unkeyed fields,其实是模板里漏写了结构体字段名。
实操建议:
- 生成完先用
parser.ParseFile()解析 AST,验证语法正确性,再走format.Node() - 不要对整个文件字符串跑
gofmt -w命令——跨平台路径和环境变量不稳定,优先用标准库 API - 如果模板里要插入多行代码块(如 handler 函数体),用
{{.Body | indent 4}}配合自定义indent函数,避免缩进错乱
通过 flag 或配置文件传参,避免硬编码业务规则
生成器一旦写死表名或字段类型(比如所有 id 都生成 int64),后续改起来就得全局搜替换。应该把可变部分抽成参数,让使用者决定行为。
使用场景举例:给 MySQL 表生成 CRUD,字段类型映射规则可能因项目而异(TINYINT(1) 映射成 bool 还是 int8?)。
实操建议:
- 用
flag.String("type-map", "tinyint=bool,int8", "")支持运行时覆盖默认映射 - 配置文件推荐
yaml格式,用gopkg.in/yaml.v3解析,比 JSON 更适合描述层级规则 - 参数校验必须前置:比如
--output路径不存在时,立刻log.Fatal("output dir not exist: ", dir),别等到写文件才失败
生成代码后必须做 import 自动管理
模板里写 json.Marshal() 很自然,但不会自动加 "encoding/json" 到 import 列表。手写 import 不可持续,容易漏、容易重复、容易冲突(比如两个模板都引入了 fmt)。
性能影响:每次生成都全量解析 import 并去重,开销极小(AST 遍历毫秒级),但能彻底规避编译失败。
实操建议:
- 用
ast.Inspect()扫描生成后的 AST,收集所有未声明的包引用(如json.Marshal→"encoding/json") - 已有 import 分组处理:标准库在上、第三方在下、本地包在最下,用
astutil.AddImport()插入时指定位置 - 别依赖
goimportsCLI 工具——它需要 GOPATH/GOPROXY 环境,CI 中易出问题;标准库方案零依赖
真正麻烦的不是生成代码,而是让生成结果能直接 go run 通过。模板语法、AST 操作、import 管理这三块只要漏一个细节,就会卡在 CI 的第一道 go build 检查上。











