我做過兩個自動化生成代碼的項目,scaffold和redis-orm惋嚎。
scaffold 主要是通過數(shù)據(jù)庫表定義來生成基于表的增刪改查的基礎(chǔ)管理工作;
redis-orm 是通過yaml的結(jié)構(gòu)定義文件生成關(guān)系型數(shù)據(jù)庫與redis的常規(guī)操作實現(xiàn)。
公司里還有一套微服務(wù)的自動化生成框架希痴,能夠快速的通過protobuf的定義文件生成項目的框架代碼。
自動化生成代碼有個最大的優(yōu)點:減少程式化的編碼春感。所謂程式化的編碼就是砌创,通常這些編碼的工作量會隨著業(yè)務(wù)量的增長線性增長,同時又是最沒有技術(shù)含量的工作鲫懒。所以通過開發(fā)自動化生成工具非常有必要嫩实,減少無謂的工作量同時大大提升工作效率,把大家解放出來做更有意義的事窥岩。
不論是我寫的自動化生成工具或是公司的微服務(wù)框架生成工具還是其它一些官方工具甲献,都有一個共同原理,所謂自動化生成代碼的秘密颂翼,即
通過結(jié)構(gòu)化的元數(shù)據(jù)生成模式代碼
這句話中有兩個關(guān)鍵詞:
-
結(jié)構(gòu)化的元數(shù)據(jù)
結(jié)構(gòu)化的元數(shù)據(jù)的來源可以是: 模式代碼
模式代碼缘挑,即所有生成的代碼是符合一定規(guī)律的,而這種規(guī)律就是基于元數(shù)據(jù)而言的桶略。
最簡單的例子
官方的工具stringer就是一個自動化生成代碼工具语淘,主要用途是通過枚舉值的變量名生成String函數(shù)接口,常用場景就是在定義程序狀態(tài)碼中使用际歼。其中惶翻,結(jié)構(gòu)化的元數(shù)據(jù)就是枚舉類型的定義。
package codes
type Code uint32
//go:generate stringer -type=Code
const (
OK Code = 0
Canceled Code = 1
Unknown Code = 2
InvalidArgument Code = 3
)
這是一個簡化版的GRPC狀態(tài)碼的例子鹅心,在文件所屬目錄下通過以下stringer命令即可生成代碼文件code_string.go吕粗。
$: stringer -type Code
生成的代碼如下:
// Code generated by "stringer -type Code"; DO NOT EDIT
package codes
import "fmt"
const _Code_name = "OKCanceledUnknownInvalidArgument"
var _Code_index = [...]uint8{0, 2, 10, 17, 32}
func (i Code) String() string {
if i >= Code(len(_Code_index)-1) {
return fmt.Sprintf("Code(%d)", i)
}
return _Code_name[_Code_index[i]:_Code_index[i+1]]
}
原代碼函數(shù)有一句注釋的語句:
//go:generate stringer -type=Code
通過該語句,可以在命令行中執(zhí)行如下命令旭愧,效果相同:
$: go generate
一個小技巧颅筋,在制作自動化生成代碼工具的過程中有時候會很有用。
微服務(wù)框架的自動化
微服務(wù)現(xiàn)在很火输枯,如何開發(fā)一個微服務(wù)框架的自動化生成工具呢议泵?
首先,我們要清楚什么是框架桃熄?
框架是對接口的抽象
這是我個人對框架的總結(jié)先口,通過將項目中變化的部分通過接口抽象出來,提供給開發(fā)者瞳收,將不變的或者配置可變的放入框架中碉京。
其實,grpc 已經(jīng)是一個簡單的微服務(wù)框架了螟深,只是功能比較單一收夸,僅僅通過protobuf的定義生成客戶端與服務(wù)端代碼框架。它是怎么做到的血崭?
管道的概念卧惜,做服務(wù)端的人都非常熟悉〖腥遥可以用管道的概念類比一下grpc框架代碼的生成過程咽瓷。
protoc | protoc-gen-go | plugin:grpc
protoc編譯器通過讀取protobuf協(xié)議與接口配置,輸出結(jié)構(gòu)化元數(shù)據(jù)給 protoc-gen-go舰讹,由它生成 go 代碼茅姜,在protoc-gen-go中會用到 plugin:grpc 的插件實現(xiàn)grpc框架代碼的定制生成。
當(dāng)然, protoc-gen-go 調(diào)用 plugin:grpc 不是通過管道的方式钻洒。
要實現(xiàn)微服務(wù)框架的自動化的關(guān)鍵全在 plugin:grpc 中了奋姿。因為 plugin:grpc 就是一個代碼生成器。你想要的所有內(nèi)心戲全部可以在這里實現(xiàn)素标。包括:
- 服務(wù)發(fā)現(xiàn)
- 上下文定制
- 錯誤處理
- 日志
- 統(tǒng)計
全部可以在框架里實現(xiàn)称诗,僅僅暴露簡單的接口供開發(fā)人員開發(fā)。
為了讓生成代碼更加精煉头遭、可讀性更強寓免,共用的一些函數(shù)都會通過公用包的形式實現(xiàn)。
在安裝GRPC的過程中计维,有這樣一條安裝命令:
$: go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
其中袜香,包proto就是protoc生成go代碼提供的公用包。
** 結(jié)構(gòu)化元數(shù)據(jù) **
有時候閱讀代碼可以幫助我們理解protobuf協(xié)議鲫惶。在公司的微服務(wù)框架里用到了custom option. 在官方文檔說蜈首,這個屬性對于大部分開發(fā)者都是不會用到的。因為這個屬性僅有在需要開發(fā)自己的框架代碼時才會使用到欠母。編寫模式代碼中欢策,可以通過custom option控制框架代碼的生成。
** 模式代碼 **
除了結(jié)構(gòu)化的元數(shù)據(jù)艺蝴,模式代碼的質(zhì)量直接影響了項目本身的質(zhì)量。模式代碼保持精煉鸟废,可讀性強都是一些基本要求猜敢。不貼代碼了,具體代碼參見grpc.go.
如何編寫自己的Plugin盒延,除了參考GRPC本身的Plugin實現(xiàn)缩擂,還可以參考這個項目
micro/protobuf.
自動化生成代碼常見的坑
在開發(fā)自動化生成代碼工具的過程中,關(guān)鍵一步是編寫模式代碼添寺。通常模式代碼一定是通過不斷的迭代才能達到所謂的完美胯盯。所以,在不斷迭代的過程中计露,就會出現(xiàn)博脑,很痛苦的,改變接口票罐。
如果只是生成的代碼改變接口可能影響面還比較小叉趣,只需要相應(yīng)的修改調(diào)用方代碼即可。但是如果生成代碼中調(diào)用的公用包接口發(fā)生改變了该押,可能以前生成的代碼就會發(fā)生故障疗杉。這也是我真實碰到過的一個坑。為了防止類似錯誤蚕礼,可以通過版本控制的辦法解決烟具。
通過對倉庫打tag梢什,利用gopkg.io實現(xiàn)版本控制,是非吵快捷且高效的解決辦法.
如何用好自動化代碼生成工具
用好自動化代碼生成工具的關(guān)鍵嗡午,除了對生成代碼本身要很熟悉外,還需要了解生成工具編寫的模式代碼玖翅。了解自動化代碼生成工具的原理是非常必要的翼馆。
其實框架越強大,對于業(yè)務(wù)而言越有利金度,但對喜歡偷懶的程序員而言是不利的应媚。所以利用偷懶來的時間,閱讀框架代碼非常必要猜极。
歸根結(jié)底中姜,自動化編程是一項泛化編程技術(shù),以前在c++中是件高端而隱秘的事跟伏,將程序執(zhí)行期的代碼移至編譯期生成丢胚。如今,在go語言中受扳,可以通過模板包template光明正大的干這件事了携龟。
以上,就是我在開發(fā)和使用自動化代碼生成工具中學(xué)到的些許經(jīng)驗勘高,全當(dāng)拋磚引玉峡蟋,歡迎指教。