我時不時得會在別人的代碼中看到"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.Marshaler
和json.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.Stringer
|binary
|json
|text
|sql
|yaml
枚舉。 - 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,給我留言。