详解go generate

我时不时得会在别人的代码中看到"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.Marshalerjson.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.Stringerbinaryjsontextsqlyaml枚举。
  • 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,给我留言。

image

你可能感兴趣的:(golang学习)