我时不时得会在别人的代码中看到"go generate",也大致知道这有什么作用,但是平时写写业务代码,并没有过多关注这方面的知识。今天得闲,稍微研究下。
go generate常用来自动生成代码,属于golang tools的官方工具之一,从go1.4开始支持,为开发者提供了便利。可以看看Rob写的博客了解它的入门:https://blog.golang.org/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 stringer -type=Pill
首先,我们要执行go generate这个命令时,代码中一定要有go generate的注释,注释的格式如下:
go generate [-run regexp] [-n] [-v] [-x] [command] [build flags] [file.go... | packages]
执行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的过程中,我还看到了一篇generate的常用工具的wiki,我并没有全部使用过,在此与大家分享,希望能提升开发效率,https://github.com/golang/go/wiki/GoGenerateTools。
go generate
仅在您有使用它的工具时才有用!这是生成代码的有用工具的不完整列表。
fmt.Stringer
枚举的接口。fmt.GoStringer
为枚举实现接口。json.Marshaler
和json.Unmarshaler
接口。sync.Map
。sync.Pool
。atomic.Value
。database/sql.Scanner
和的通用模板生成Go代码database/sql/driver.Valuer
。fmt.Stringer
| binary
| json
| text
| sql
| yaml
枚举。欢迎关注我的公众号:onepunchgo,给我留言。