聽說你還在自己做重複勞動?看我一鍵生成錯誤碼對映

阿兵雲原生發表於2022-12-15

大家在工作中定義錯誤碼的時候都是如何處理的? xdm 可以評論區交流交流

我看到過有的是這樣定義錯誤碼的:

m := make(map[int]string)
m[0] = "OK"
m[1] = "連結失敗"
m[2] = "檔案型別錯誤"
...

還看到過這樣定義錯誤碼的:

type myErr struct {
    code   int
    err    error
    extMsg interface{}
}

myOk := myErr{0,errors.New("PROCESS OK"),"xxx"}
myOk := myErr{1,errors.New("檔案型別錯誤"),"xxx"}
myOk := myErr{2,errors.New("連線資料庫失敗"),"xxx"}
...

也有見到過這樣做的:

const (
    ERR_OK          = 0
    ERR_CONN_REFUSE = 1
    ERR_FILE_NOT    = 2
)


var m = map[int]string{
    ERR_OK:          "OK",
    ERR_CONN_REFUSE: "連結被拒絕",
    ERR_FILE_NOT:    "檔案不存在",
}

現在有一個更好的方法來實現我們工作中錯誤碼的對映

引入 go generate

我們們引入 go generate ,可以只用定義錯誤碼和寫註釋,就可以達到,當我們呼叫錯誤碼的時候,能夠正確的輸出我們想要的錯誤資訊

舉個例子:

我們先建立如下目錄,將錯誤碼檔案 errcode.go,放在一個單獨的包裡面

.
├── go.mod
├── main.go
└── mycodes
    └── errcode.go

我們還需要運用 stringer 工具,來輔助我們完成這個目標

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

我們來看看上述檔案的內容:

./mycodes/errcode.go

/*
____  ___   _____ ___________
\   \/  /  /     \\__    ___/
 \     /  /  \ /  \ |    |
 /     \ /    Y    \|    |
/___/\  \\____|__  /|____|
      \_/        \/
createTime:2021/10/10
createUser:Administrator
*/
package mycodes

type ErrCode int64 //錯誤碼
const (
        ERR_CODE_OK             ErrCode = 0 // PROCESS OK
        ERR_CODE_INVALID_PARAMS ErrCode = 1 // 引數無效
        ERR_CODE_TIMEOUT        ErrCode = 2 // 超時
        ERR_CODE_FILE_NOT_EXIST ErrCode = 3 // 檔案不存在
        ERR_CODE_CONN_REFUSE    ErrCode = 4 // 連線被拒絕
        ERR_CODE_NET_ABNORMAL   ErrCode = 5 // 網路異常
)

main.go

/*
____  ___   _____ ___________
\   \/  /  /     \\__    ___/
 \     /  /  \ /  \ |    |
 /     \ /    Y    \|    |
/___/\  \\____|__  /|____|
      \_/        \/
createTime:2021/10/10
createUser:Administrator
*/
package main

import (
        "fmt"
        "myerr/mycodes"
)

func main() {
        fmt.Println(mycodes.ERR_CODE_CONN_REFUSE)
}

我們在 main.go 統計目錄下初始化一下 go 的 模組, go mod init myerr

go.mod

module myerr

go 1.15

開始演示

我們直接在 main.go 的同級目錄下執行 go run main.go,輸出如下:

4

是 ERR_CODE_CONN_REFUSE 對應的列舉值 4 ,可是我們期望的課不是這個,我們是期望能直接輸出錯誤碼對應的錯誤資訊

使用 stringer

手動在 mycodes 目錄下使用剛才安裝的 stringer 工具

stringer -linecomment -type ErrCode 

此處的 ErrCode 是錯誤碼的型別 , 執行上述語句後,mycodes 生成了一個檔案 errcode_string.go ,現在目錄結構是這樣的

.
├── go.mod
├── main.go
└── mycodes
    ├── errcode.go
    └── errcode_string.go

看看 errcode_string.go 內容

// Code generated by "stringer -linecomment -type ErrCode"; DO NOT EDIT.

package mycodes

import "strconv"

const _ErrCode_name = "PROCESS OK引數無效超時檔案不存在連線被拒絕網路異常"

var _ErrCode_index = [...]uint8{0, 10, 22, 28, 43, 58, 70}

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]]
}

我們可以看出 stringer 工具,將我們的錯誤碼資訊對映的字串全部合併起來放在 _ErrCode_name 常量中,且有 _ErrCode_index 來作為每一個錯誤碼對映字串的索引值 ,最終便能實現錯誤碼和字串的對映,這個就很簡單吧

效果展示

此時,我們仍然在 main.go 的同級目錄下執行 go run main.go,輸出如下:

連線被拒絕

顯示的正式我們期望的錯誤資訊

stringer 工具

我們來看看 stringer 工具的幫助,在來詳細學習一波

# stringer -h
Usage of stringer:
        stringer [flags] -type T [directory]
        stringer [flags] -type T files... # Must be a single package
For more information, see:
        http://godoc.org/golang.org/x/tools/cmd/stringer
Flags:
  -linecomment
        use line comment text as printed text when present
  -output string
        output file name; default srcdir/<type>_string.go
  -trimprefix prefix
        trim the prefix from the generated constant names
  -type string
        comma-separated list of type names; must be set

可以看到大的用法有 2 種:

 stringer [flags] -type T [directory]
 stringer [flags] -type T files... # Must be a single package

第一種可以在型別 T 後面加上目錄

第二種可以指定型別 T 後面指定明確的檔案,但是這種方式必須是在一個單獨的包裡面

引數如下:

  • -linecomment

使用行註釋文字作為列印文字

  • -output string

輸出檔名稱;預設源目錄下的 / <型別> _string.go,所以我們可以看到例子中我們的輸出檔案在 mycodes 下的 errcode_string.go

  • -trimprefix prefix

從生成的常量名稱中修剪字首

  • -type string

以逗號分隔的型別名稱列表,這個引數是必須要設定的

go generate

剛才我們是在命令列中,使用 stringer 工具來生成的,那麼我們要把這些東西放入專案程式碼中就需要使用 go generate 工具了

先大致瞭解一下 go generate 是個啥玩意

go generate 是 go 自帶的一個工具,我們可以透過在命令列中檢視到:

# go

我們們檢視一下幫助文件 go help generate

# go help generate
...

Go generate scans the file for directives, which are lines of
the form,

        //go:generate command argument...

...

Go generate sets several variables when it runs the generator:

        $GOARCH
                The execution architecture (arm, amd64, etc.)
        $GOOS
                The execution operating system (linux, windows, etc.)
        $GOFILE
                The base name of the file.
        $GOLINE
                The line number of the directive in the source file.
        $GOPACKAGE
                The name of the package of the file containing the directive.
        $DOLLAR
                A dollar sign.

上面這些是 go generate 使用時候的環境變數

  • $GOARCH

體系架構(arm、amd64 等)

  • $GOOS

當前的 OS 環境(linux、windows 等)

  • $GOFILE

當前處理中的檔名

  • $GOLINE

當前命令在檔案中的行號

  • $GOPACKAGE

當前處理檔案的包名

go generate命令格式

go generate [-run regexp] [-n] [-v] [-x] [command] [build flags] [file.go... | packages]
引數說明如下:
-run 
正規表示式匹配命令列,僅執行匹配的命令;

-v 
輸出被處理的包名和原始檔名;

-n 
顯示不執行命令;

-x 
顯示並執行命令;

command 
可以是在環境變數 PATH 中的任何命令。

generate 用法

上面幫助文件有體現,我們可以使用 //go:generate command argument... 來講 generate 工具用起來

實際案例

我們來簡單的嘗試一下

我們在剛才的 main.go 中加入 generate 的語句,使用 generate 執行,ls -alh

/*
____  ___   _____ ___________
\   \/  /  /     \\__    ___/
 \     /  /  \ /  \ |    |
 /     \ /    Y    \|    |
/___/\  \\____|__  /|____|
      \_/        \/
createTime:2021/10/10
createUser:Administrator
*/
package main

//go:generate ls -alh

import (
        "fmt"
        "myerr/mycodes"
)

func main() {
        fmt.Println(mycodes.ERR_CODE_CONN_REFUSE)
}

在 main.go 同級目錄下執行 go generate 看效果

# go generate
total 20K
drwxr-xr-x  3 root root 4.0K Oct 10 17:30 .
drwxr-xr-x 11 root root 4.0K Oct 10 16:25 ..
-rw-r--r--  1 root root   22 Oct 10 16:02 go.mod
-rw-r--r--  1 root root  346 Oct 10 17:30 main.go
drwxr-xr-x  2 root root 4.0K Oct 10 17:13 mycodes

果然是呼叫 ls -alh 成功了

go generate + stringer 的使用

那麼我們就把剛才我們實踐的 stringer 工具也加進去玩玩

此時目錄是這樣的

.
├── go.mod
├── main.go
└── mycodes
    └── errcode.go

main.go 是這樣的

/*
____  ___   _____ ___________
\   \/  /  /     \\__    ___/
 \     /  /  \ /  \ |    |
 /     \ /    Y    \|    |
/___/\  \\____|__  /|____|
      \_/        \/
createTime:2021/10/10
createUser:Administrator
*/
package main

//go:generate stringer -linecomment -type ErrCode ./mycodes

import (
        "fmt"
        "myerr/mycodes"
)

func main() {
        fmt.Println(mycodes.ERR_CODE_CONN_REFUSE)
}

沒錯我們加入了 //go:generate stringer -linecomment -type ErrCode ./mycodes

直接執行 go generate -x 來看效果吧

# go generate -x
stringer -linecomment -type ErrCode ./mycodes

errcode_string.go 又生成了

.
├── go.mod
├── main.go
└── mycodes
    ├── errcode.go
    └── errcode_string.go

執行 go run main.go 當然必須是我們想要的東西啦

# go run main.go
連線被拒絕

go generate 的使用規範

  • 執行go generate命令時,才會執行特殊註釋後面的命令
  • 特殊註釋必須以//go:generate開頭,雙斜線後面沒有空格
  • 該特殊註釋必須在 .go 原始碼檔案中
  • 每個原始碼檔案可以包含多個 generate 特殊註釋
  • go generate命令執行出錯時,將終止程式的執行

最後說說 go generate 還能幹些啥

go generate 能幹的事情還真不少,只要是能夠在 path 下面能找到的可執行程式,都可以放在 //go:generate 後面玩,一般使用 go generate 會有如下場景:

  • protobuf:從 protocol buffer 定義檔案(.proto)生成 .pb.go 檔案 , 這種情況 grpc 通訊的時候常用
  • yacc:從 .y 檔案生成 .go 檔案
  • HTML:將 HTML 檔案嵌入到 go 原始碼
  • bindata:將形如 JPEG 這樣的檔案轉成 go 程式碼中的位元組陣列
  • Unicode:從 UnicodeData.txt 生成 Unicode 表

工具要用用起來才能體現它的價值

歡迎點贊,關注,收藏

朋友們,你的支援和鼓勵,是我堅持分享,提高質量的動力

好了,本次就到這裡

技術是開放的,我們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。

我是阿兵雲原生,歡迎點贊關注收藏,下次見~

相關文章