绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
深入理解Go之generate
2020-04-26 09:35:33

概述

开发中经常有定义错误码这样的需求,错误码标识具体的错误信息。另外还需要设置每个错误的具体描述。在 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工具包中。

传统方式

定义错误码:

  1. package errcode


  2. import "fmt"


  3. // 定义错误码

  4. const (

  5. ERR_CODE_OK = // OK

  6. ERR_CODE_INVALID_PARAMS = 1 // 参数

  7. ERR_CODE_TIMEOUT = 2 // 超时

  8. // ...

  9. )


  10. // 定义错误码与描述信息的映射

  11. var mapErrDesc = map[int]string {

  12. ERR_CODE_OK: "OK",

  13. ERR_CODE_INVALID_PARAMS: "参数",

  14. ERR_CODE_TIMEOUT: "超时",

  15. // ...

  16. }


  17. // 根据错误码返回描述信息

  18. func GetDescription(errCode int) string {

  19. if desc, exist := mapErrDesc[errCode]; exist {

  20. return desc

  21. }


  22. return fmt.Sprintf("error code: %d", errCode)

  23. }

使用错误码:

  1. package main


  2. import (

  3. "github.com/darjun/errcode"

  4. )


  5. func main() {

  6. code := errcode.ERR_CODE_INVALID_PARAMS

  7. fmt.Println(code, errcode.GetDescription(errCode))


  8. // 输出: 1 参数

  9. }

为了使用方便,我们可以为错误码定义一个新的类型,然后为该类型定义 String()方法,这样就不用手动调用 GetDescription函数了。修改如下:

  1. type ErrCode int


  2. const (

  3. ERR_CODE_OK ErrCode = // OK

  4. ERR_CODE_INVALID_PARAMS ErrCode = 1 // 参数

  5. ERR_CODE_TIMEOUT ErrCode = 2 // 超时

  6. )


  7. func (e ErrCode) String() string {

  8. return GetDescription(e)

  9. }

现在已经不需要在外部调用 GetDescription函数了,从而不需要导出。将函数名改为 getDescription即不导出。

这种方式有什么问题呢?

每次增加错误码时,都需要修改 mapErrDesc,有时候可能会忘。另外,错误描述在注释和 mapErrDesc都出现了。那么能不能只写注释,然后使用工具自动生成我们想要的代码呢?

接下我们看看如何通过 go generate + stringer解决这个问题。

go generate

go generate是 Go 自带的工具。使用命令 go generate执行。 go generate是利用源代码中的注释工作的。格式如下:

  1. //go:generate command arg1 arg2

这样在同一个目录下执行命令 go generate就会自动运行命令 command arg1 arg2。 command可以是在 PATH中的任何命令,应用非常广泛。官网提供了几种示例,见文档。

stringer命令可以为给定类型生成 String方法。

安装 stringer

stringer并不是 Go 自带的工具,需要手动安装。可以执行下面的命令安装:

  1. $ go get golang.org/x/tools/cmd/stringer

上面的命令需要翻墙。也可以通过 Github 上的镜像来安装。假设你已经配置好 Go 的开发环境了,安装方法如下:

  1. $ git clone https://github.com/golang/tools/ $GOPATH/src/golang.org/x/tools

  2. $ go install golang.org/x/tools/cmd/stringer

安装好的 stringer命令位于 $GOPATH/bin目录下,强烈建议将这个目录加入系统 PATH

使用

stringer有两种模式,默认是根据变量/常量名来生成字符串描述。我们在常量定义上增加注释:

  1. //go:generate stringer -type ErrCode

选项 -type指定 stringer命令作用的类型名。

然后在同一个目录下执行:

  1. $ go generate

会在同一个目录下生成一个文件 errcode_string.go。文件名格式是 类型名小写_string.go。也可以通过 -output选项指定输出文件名,例如下面就是指定输出文件名为 code_string.go

  1. //go:generate stringer -type ErrCode -output code_string.go

我们来看看这个文件的内容:

  1. // Code generated by "stringer -type ErrCode -output errcode_string.go"; DO NOT EDIT.


  2. package errcode


  3. import "strconv"


  4. const _ErrCode_name = "ERR_CODE_OKERR_CODE_INVALID_PARAMSERR_CODE_TIMEOUT"


  5. var _ErrCode_index = [...]uint8{, 11, 34, 50}


  6. func (i ErrCode) String() string {

  7. if i < || i >= ErrCode(len(_ErrCode_index)-1) {

  8. return "ErrCode(" + strconv.FormatInt(int64(i), 10) + ")"

  9. }

  10. return _ErrCode_name[_ErrCode_index[i]:_ErrCode_index[i+1]]

  11. }

生成的代码做了一些优化,减少了字符串对象的数量。

这时 ERR_CODE_INVALID_PARAMS.String()返回的描述信息是 ERR_CODE_INVALID_PARAMS。在一些上下文中甚至不需要自己调用 String()方法,如 fmt.Println。因为 ErrCode实现了 fmt.Stringer,一些上下文中会自动调用。

这样 errcode.go文件中 mapErrDesc全局变量和 getDescription函数都可以去掉了。

但是我们更希望的是能返回后面的注释作为错误描述。这就需要使用 stringer的 -linecomment选项。修改 go:generate如下:

  1. //go:generate stringer -type ErrCode -linecomment -output code_string.go

然后,执行 go generate命令。生成的 code_string.go与之前的有所不同,如下:

  1. const _ErrCode_name = "OK参数超时"


  2. var _ErrCode_index = [...]uint8{, 2, 14, 20}

可以看到确实通过注释生成了错误消息。

注意点:

  1. go:generate前面只能使用 //注释,注释必须在行首,前面不能有空格且 //与 go:generate之间不能有空格!!!

  2. go:generate可以在任何 Go 源文件中,好在类型定义的地方。

自动化

有人会说,这样还是很麻烦啊:每次都要执行一次 go generate命令,我忘了怎么办?我很懒,也不想手动敲这个命令。没问题,不会偷懒不是一个好程序员。我们可以在构建项目的脚本中执行这个命令,例如在 makefile 中:

  1. all:

  2. go generate && go build .

这样一个 make命令就可以执行 go generate同时编译项目了。完美!

当然上面命令比较简单,实际项目中可能需要处理一下目录。

代码在Github上。

参考链接

  1. go blog - generate

  2. go generate文档


分享好友

分享这个小栈给你的朋友们,一起进步吧。

Go 每日一库
创建时间:2020-04-24 14:01:14
每天学习一个 Go 语言库~
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

栈主、嘉宾

查看更多
  • darjun
    栈主

小栈成员

查看更多
  • ?
  • 小雨滴
  • 飘絮絮絮丶
  • 栈栈
戳我,来吐槽~