深入理解Go之generate
概述
开发中经常有定义错误码这样的需求,错误码唯一标识具体的错误信息。另外还需要设置每个错误的具体描述。在 HTTP 协议中,200 表示 "OK",404 表示"Not Found"。在 Linux 系统中,ENOENT
的值为 2,表示"No such file or directory"。syscall
包中定义了Errno
类型表示系统错误码,非常易用使用,建议去看看。
每次定义错误码的时候,同时需要添加描述信息。而且描述信息经常会忘。本文介绍go generate
+ stringer
工具链优雅地解决这个问题。
这里顺带提一句,golang tools 是官方提供的工具集,是 Gophers 的工具宝库,值得好好探索一番,参见Github 地址,文档地址。里面有丰富的开发辅助工具,所有的 Go 开发插件都离不开这些工具的支持。例如goimports
工具自动导入使用的包,去掉未使用的包;gorename
用来重命名标识符。
今天要使用的stringer
工具也在tools
工具包中。
传统方式
定义错误码:
package errcodeimport "fmt"// 定义错误码
const (
ERR_CODE_OK = 0 // OK
ERR_CODE_INVALID_PARAMS = 1 // 无效参数
ERR_CODE_TIMEOUT = 2 // 超时
// ...
)// 定义错误码与描述信息的映射
var mapErrDesc = map[int]string {
ERR_CODE_OK: "OK",
ERR_CODE_INVALID_PARAMS: "无效参数",
ERR_CODE_TIMEOUT: "超时",
// ...
}// 根据错误码返回描述信息
func GetDescription(errCode int) string {
if desc, exist := mapErrDesc[errCode];
exist {
return desc
}return fmt.Sprintf("error code: %d", errCode)
}
使用错误码:
package mainimport (
"github.com/darjun/errcode"
)func main() {
code := errcode.ERR_CODE_INVALID_PARAMS
fmt.Println(code, errcode.GetDescription(errCode))// 输出: 1 无效参数
}
为了使用方便,我们可以为错误码定义一个新的类型,然后为该类型定义
String()
方法,这样就不用手动调用GetDescription
函数了。修改如下:type ErrCode intconst (
ERR_CODE_OK ErrCode = 0 // OK
ERR_CODE_INVALID_PARAMS ErrCode = 1 // 无效参数
ERR_CODE_TIMEOUT ErrCode = 2 // 超时
)func (e ErrCode) String() string {
return GetDescription(e)
}
现在已经不需要在外部调用
GetDescription
函数了,从而不需要导出。将函数名改为getDescription
即不导出。这种方式有什么问题呢?
每次增加错误码时,都需要修改
mapErrDesc
,有时候可能会忘。另外,错误描述在注释和mapErrDesc
都出现了。那么能不能只写注释,然后使用工具自动生成我们想要的代码呢?接下我们看看如何通过
go generate
+ stringer
解决这个问题。go generate
go generate
是 Go 自带的工具。使用命令go generate
执行。go generate
是利用源代码中的注释工作的。格式如下://go:generate command arg1 arg2
这样在同一个目录下执行命令
go generate
就会自动运行命令command arg1 arg2
。command
可以是在PATH
中的任何命令,应用非常广泛。官网提供了几种示例,见文档。stringer
命令可以为给定类型生成String
方法。安装
stringer
stringer
并不是 Go 自带的工具,需要手动安装。可以执行下面的命令安装:$ go get golang.org/x/tools/cmd/stringer
上面的命令需要翻墙。也可以通过 Github 上的镜像来安装。假设你已经配置好 Go 的开发环境了,安装方法如下:
$ git clone https://github.com/golang/tools/ $GOPATH/src/golang.org/x/tools
$ go install golang.org/x/tools/cmd/stringer
安装好的
stringer
命令位于$GOPATH/bin
目录下,强烈建议将这个目录加入系统PATH
中。使用
stringer
有两种模式,默认是根据变量/常量名来生成字符串描述。我们在常量定义上增加注释://go:generate stringer -type ErrCode
选项
-type
指定stringer
命令作用的类型名。然后在同一个目录下执行:
$ go generate
会在同一个目录下生成一个文件
errcode_string.go
。文件名格式是类型名小写_string.go
。也可以通过-output
选项指定输出文件名,例如下面就是指定输出文件名为code_string.go
://go:generate stringer -type ErrCode -output code_string.go
我们来看看这个文件的内容:
// Code generated by "stringer -type ErrCode -output errcode_string.go";
DO NOT EDIT.package errcodeimport "strconv"const _ErrCode_name = "ERR_CODE_OKERR_CODE_INVALID_PARAMSERR_CODE_TIMEOUT"var _ErrCode_index = [...]uint8{0, 11, 34, 50}func (i ErrCode) String() string {
if i < 0 || i >= ErrCode(len(_ErrCode_index)-1) {
return "ErrCode(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _ErrCode_name[_ErrCode_index[i]:_ErrCode_index[i+1]]
}
生成的代码做了一些优化,减少了字符串对象的数量。
这时
ERR_CODE_INVALID_PARAMS.String()
返回的描述信息是ERR_CODE_INVALID_PARAMS
。在一些上下文中甚至不需要自己调用String()
方法,如fmt.Println
。因为ErrCode
实现了fmt.Stringer
,一些上下文中会自动调用。这样
errcode.go
文件中mapErrDesc
全局变量和getDescription
函数都可以去掉了。但是我们更希望的是能返回后面的注释作为错误描述。这就需要使用
stringer
的-linecomment
选项。修改go:generate
如下://go:generate stringer -type ErrCode -linecomment -output code_string.go
然后,执行
go generate
命令。生成的code_string.go
与之前的有所不同,如下:const _ErrCode_name = "OK无效参数超时"var _ErrCode_index = [...]uint8{0, 2, 14, 20}
可以看到确实通过注释生成了错误消息。
注意点:
-
go:generate
前面只能使用//
注释,注释必须在行首,前面不能有空格且//
与go:generate
之间不能有空格!!! -
go:generate
可以在任何 Go 源文件中,最好在类型定义的地方。
有人会说,这样还是很麻烦啊:每次都要执行一次
go generate
命令,我忘了怎么办?我很懒,也不想手动敲这个命令。没问题,不会偷懒不是一个好程序员。我们可以在构建项目的脚本中执行这个命令,例如在 makefile 中:all:
go generate && go build .
这样一个
make
命令就可以执行go generate
同时编译项目了。完美!当然上面命令比较简单,实际项目中可能需要处理一下目录。
代码在Github上。
参考链接
- go blog - generate
- go generate文档
推荐阅读
- PMSJ寻平面设计师之现代(Hyundai)
- 太平之莲
- 闲杂“细雨”
- 七年之痒之后
- 考研英语阅读终极解决方案——阅读理解如何巧拿高分
- 由浅入深理解AOP
- 期刊|期刊 | 国内核心期刊之(北大核心)
- 生活随笔|好天气下的意外之喜
- 感恩之旅第75天
- python学习之|python学习之 实现QQ自动发送消息