我时不时得会在别人的代码中看到"go generate",也大致知道这有什么作用,但是平时写写业务代码,并没有过多关注这方面的知识。今天得闲,稍微研究下。
go generate的用途
go generate常用来自动生成代码,属于golang tools的官方工具之一,从go1.4开始支持,为开发者提供了便利。可以看看Rob写的博客了解它的入门:https://blog.golang.org/generate。
go generate的常见用法
我们根据Rob的博客的例子来讲解一下generate的用法,比如有以下这段代码:
package pill
type Pill int
const (
Placebo Pill = iota
Aspirin
Ibuprofen
Paracetamol
Acetaminophen = Paracetamol
)
func GetPill(p Pill) string {
switch p {
case Placebo:
return "Placebo"
case Aspirin:
return "Aspirin"
case Ibuprofen:
return "Ibuprofen"
case Paracetamol:
return "Paracetamol"
}
return ""
}
我们再写个test看看:
func TestGetPill(t *testing.T) {
fmt.Println(GetPill(Pill(1))) //output: Aspirin
}
有一些枚举类型的常量,我们想要让这些常量返回其相对应的名字,我们需要写一个类似于这样的GetPill方法。但这样有个问题,一旦pill越来越多,那我们的case就会越来越多,不论是增删改都是件麻烦事,这时候就可以利用generate结合另外一个工具stringer来完成代码自动生成。
由于stringer并不在官方发行版的工具集里,我们需要 自行安装,执行:
$ go get golang.org/x/tools/cmd/stringer
接着,我们修改代码,增加一行注释:
//go:generate stringer -type=Pill
package pill
type Pill int
const (
Placebo Pill = iota
Aspirin
Ibuprofen
Paracetamol
Acetaminophen = Paracetamol
)
GetPill()可以去掉了,接着我们执行:
$ go generate
在同级目录下,生成了pill_string.go的文件,文件内容如下:
// Code generated by "stringer -type=Pill"; DO NOT EDIT.
package pill
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[Placebo-0]
_ = x[Aspirin-1]
_ = x[Ibuprofen-2]
_ = x[Paracetamol-3]
}
const _Pill_name = "PlaceboAspirinIbuprofenParacetamol"
var _Pill_index = [...]uint8{0, 7, 14, 23, 34}
func (i Pill) String() string {
if i < 0 || i >= Pill(len(_Pill_index)-1) {
return "Pill(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Pill_name[_Pill_index[i]:_Pill_index[i+1]]
}
生成的代码看起来有些奇怪,但是实际使用时我们并不用关注它,这些代码看起来奇怪是为了减少内存占用。回过头来,我们再看看我们的需求是什么,我们想要得到每个枚举值的返回信息,再写个test试试:
func TestGetPill2(t *testing.T) {
fmt.Println(Pill(1).String()) //output: Aspirin
}
可以看到,我们实现了我们的需求,并且我们不需要去维护一个GetPill()方法了。
go generate 解析
我们实现了一个小例子,现在就来理解一下为什么是这么实现的。着重看下那句注释:
//go:generate stringer -type=Pill
首先,我们要执行go generate这个命令时,代码中一定要有go generate的注释,注释的格式如下:
go generate [-run regexp] [-n] [-v] [-x] [command] [build flags] [file.go... | packages]
- -run 正则表达式匹配命令行,仅执行匹配的命令;
- -v 输出被处理的包名和源文件名;
- -n 显示不执行命令;
- -x 显示并执行命令;
- command 可以是在环境变量 PATH 中的任何命令。
执行go generate命令时,也可以使用一些环境变量,如下所示:
- $GOARCH 体系架构(arm、amd64 等);
- $GOOS 当前的 OS 环境(linux、windows 等);
- $GOFILE 当前处理中的文件名;
- $GOLINE 当前命令在文件中的行号;
- $GOPACKAGE 当前处理文件的包名。
有以下几点要注意:
- 该特殊注释必须在 .go 源码文件中;
- 每个源码文件可以包含多个 generate 特殊注释;
- 运行go generate命令时,才会执行特殊注释后面的命令;
- 当go generate命令执行出错时,将终止程序的运行;
- 特殊注释必须以//go:generate开头,双斜线后面没有空格。
那么再看看,我们这个例子中的用法,go generate 后面真正生成代码,执行的是"stringer -type=Pill",前面我们已经go get将stringer安装到我们的\$GOPATH/bin中,而\$GOPATH/bin已经被我添加到我的环境变量PATH中,因此代码生成了。那么同理,go generate后面还可以加上其他命令,来自动生成代码,比较常见的场景有比如根据.proto文件生成代码等,类似如下:
//go generate protoc --go_out=plugins=grpc:. server.proto
go generate在文件中可以有多个定义,可以写多个generate的注释,还可以给命令写别名,比如:
//go:generate -command yacc go tool yacc
//go:generate yacc -o gopher.go gopher.y
这里在-command后面的yacc是个"go tool yacc"的别名,实际执行go generate命令就等同于执行:
$ go tool yacc -o gopher.go gopher.y
go generate在代码中的使用方法大致如上所述,不过在实际的编译中,执行go build并不会执行go generate,需要另外执行,这可以通过写Makefile等自动化编译的方式去解决。
go generate常使用的一些工具
在学习go generate的过程中,我还看到了一篇generate的常用工具的wiki,我并没有全部使用过,在此与大家分享,希望能提升开发效率,https://github.com/golang/go/wiki/GoGenerateTools。
go generate
仅在您有使用它的工具时才有用!这是生成代码的有用工具的不完整列表。
- goyacc – Go的Yacc。
- stringer – 实现
fmt.Stringer
枚举的接口。 - gostringer –
fmt.GoStringer
为枚举实现接口。 - jsonenums – 枚举的实现
json.Marshaler
和json.Unmarshaler
接口。 - go-syncmap – 使用软件包作为的通用模板生成Go代码
sync.Map
。 - go-syncpool – 使用软件包作为的通用模板生成Go代码
sync.Pool
。 - go-atomicvalue – 使用软件包作为的通用模板生成Go代码
atomic.Value
。 - go-nulljson – 使用包作为实现
database/sql.Scanner
和的通用模板生成Go代码database/sql/driver.Valuer
。 - go-enum – 使用包作为实现接口的通用模板生成Go代码
fmt.Stringer
|binary
|json
|text
|sql
|yaml
枚举。 - go-import – 执行非go文件的自动导入。
- gojson – 从示例json文档生成go结构定义。
- vfsgen – 生成静态实现给定虚拟文件系统的vfsdata.go文件。
- goreuse – 使用包作为通用模板通过替换定义来生成Go代码。
- embedfiles – 将文件嵌入Go代码。
- ragel – 状态机编译器
- peachpy – 嵌入在Python中的x86-64汇编器,生成Go汇编
- bundle – Bundle创建适用于包含在特定目标软件包中的源软件包的单一源文件版本。
- msgp – MessagePack的Go代码生成器
- protobuf – protobuf
- thriftrw – thrift
- gogen-avro – avro
- swagger-gen-types – 从swagger定义中去生成代码
- avo – 使用Go生成汇编代码
- Wire – Go的编译时依赖注入
- sumgen – 从sum-type声明生成接口方法实现
- interface-extractor – 生成所需类型的接口,仅在包内使用方法。
- deep-copy – 为给定类型创建深度复制方法。
欢迎关注我的公众号:onepunchgo,给我留言。