Go工具之generate( 二 )

要实现它 , 非常简单 。
func (p Pill) String() string {    switch p {    case Placebo:        return "Placebo"    case Aspirin:        return "Aspirin"    case Ibuprofen:        return "Ibuprofen"    case Paracetamol: // == Acetaminophen        return "Paracetamol"    }    return fmt.Sprintf("Pill(%d)", p)}试想 , 如果我们的Pill名单里新增了一批药品名 , 每次增加或修改药品名 , 在相应的签名函数里 , 也都需要进行更改 。这样岂不是很麻烦且很可能遗漏或出错?这时 , 我们可以通过 go generate + stringer的方案解决该问题 。很简单 , 只需在定义Pill的代码中 , 增加一句注释语句即可 。
//go:generate stringer -type=Pill上面的命令 , 代表运行stringer工具来为Pill类型生成String方法 , 默认输出到pill_string.go文件中 , 执行如下 。
$ go generate$ cat pill_string.go// Code generated by stringer -type Pill pill.go; DO NOT EDIT.package painkillerimport "fmt"const _Pill_name = "PlaceboAspirinIbuprofenParacetamol"var _Pill_index = [...]uint8{0, 7, 14, 23, 34}func (i Pill) String() string {    if i < 0 || i+1 >= Pill(len(_Pill_index)) {        return fmt.Sprintf("Pill(%d)", i)    }    return _Pill_name[_Pill_index[i]:_Pill_index[i+1]]}这样 , 每次我们对Pill类型有修改时 , 我们所需要做的就是运行以下语句即可 。
$ go generate当然 , 你要是觉得这样麻烦 , 或者担心忘记执行generate语句 。那么 , 可以将go generate语句写入Makefile之中 , 置于go build命令之前 , 实现代码生成与编译的自动化 。
值得一提的是 , 在Go源码文档中 , 大量采用了go generate+stringer的方案实现对枚举常量的String方法 。在小菜刀本机Go 1.14.1的源码下 , 一共有23处使用 , 具体如下 。

Go工具之generate

文章插图
 
总结
本文主要介绍generate是什么 , 能做什么 , 如果想深入理解其内在实现逻辑 , 可以去看Go源码中生成代码的详细过程 , 例如sort包下通过genzfunc.go实现zfuncversion.go的生成 。在Go源码宝库中 , 可以找到很多相似的实现逻辑 , 参照如下 。
Go工具之generate

文章插图
 
它们利用Go编译器提供的库 , 包括定义抽象语法树的 go/ast、解析抽象语法树的go/parser、解析用于格式化代码的 go/format、用于Go词法标记的go/token等 。解析源文件并按照已有的模板生成新的代码 , 这一过程和Web 服务中利用模板生成 html 文件类似 。
总结:减少代码的重复编写 , 保护头发!!
参考
https://golang.org/cmd/go/
https://blog.golang.org/generate
https://godoc.org/golang.org/x/tools/cmd/stringer
https://docs.google.com/document/d/1V03LUfjSADDooDMhe-_K59EgpTEm3V8uvQRuNMAEnjg/edit#




推荐阅读