這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Go語言有很多工具, goimports用於package的自動匯入或者刪除, golint用於檢查源碼中不符合Go coding style的地方, 比如全名,注釋等. 還有其它工具如gorename, guru等工具. 作為工具它們都是使用go語言(查看)開發的, 這些工具都有一個共同點就是: 讀取原始碼, 分析原始碼, 修改或產生新代碼.
簡述
很多程式設計語言/庫/架構等都能產生代碼, 比如使用rails, 可以輕鬆地new一個project出來, 產生項目基本代碼, 我們稱其為boilerplate, 或者template, 這已經習以為常了. 像ruby的動態語言通常能在運行時產生代碼, 我們稱之為meta programming(元編程), 比如rails的resources可以產生restful的router出來.因為是運行時動態產生, 因此可能會遇到exception, 以及效能方面有所損失.
像elixir這種程式設計語言的macro則比ruby的元編程方面向"前"一步, 它在編譯期產生代碼, 而不在運行時產生, 好處是可以產生大量的代碼而對效能幾乎沒有太大影響. 像phoenix架構的router查看部分, 則通過macro產生大量的函數, 利用BEAM的pattern matching機制高效路由.elixir的macro是寫在原始碼裡的, 而Go則可以分離.
Go語言可以通過reflect包同樣做到ruby的運行時產生代碼(比如建立對象), 但更強大的一點是, 它通過讀取源碼, 再修改源碼, 產生新的代碼.我們可以將這個過程單獨寫作一個工具, 這個工具可以適用於不同的項目.
例子
stringer
package game//go:generate stringer -type=GameStatus// 注意//與go:generate字元之間不能有空格// GameStatus 表示比賽的狀態type GameStatus intconst (Unvalid GameStatus = iotaValidFailedValidRegisterStartRunningEnd)
運行 go generate 會產生gamestatus_stirng.go檔案, 並且實現了Stringer介面.
同樣的例子在gRPC中也出現過code, 產生的string.正如Rob Pike所說:
let the mechine do the work.source
gen_columns
很多項目在使用資料庫時, 通過tag指定資料庫裡的欄位名字, 在寫SQL時, 又只能通過字串來表示欄位名, 因此如果某一個欄位名修改時, 則意味著涉及到此欄位的SQL都面臨著修改, 而我們希望只需要修改一個地方.
有一個結構作為資料庫表結構如下:
type User struct { ID int `json:"id" bson:"id"` Name string `json:"name" bson:"name"`}
當使用這個model裡的欄位進行sql查詢時, 通常使用:
map[string]interface{}{ "id":123456,}
作為查詢條件, 如果當欄位名更改時, 不得不修改這個map裡的key值
如果能夠自動產生一個結構體, 用於表示這些column name值, 那麼只需修改一處:
map[string]interface{}{ UserColumns.ID: 123456}
使用方法
gen_columns -tag="bson" -path="./models/user.go"
會產生一個獨立的檔案, 裡面的內容為:
package modelstype _UserColumn struct {ID stringName string}var UserColumns _UserColumnfunc init() {UserColumns.ID = "id"UserColumns.Name = "name"}
總結
gen_columns是自己在項目中遇到問題所給出的解決辦法, 第一版本是通過reflect做的, 總共需要好幾個步驟; 使用ast做就只需在編譯時間多加一個go generate, 而這命令基本上可以整合在build的指令碼裡, 因此不需要再額外擔心代碼產生的問題.
讓我們用Go創造更多產生代碼的工具吧.
其它例子
- impl 指定一個介面, 產生介面所需方法
- goa 一套用於寫web service的DSL