詳解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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章