聲明:本文為InfoQ中文站特供稿件味抖,首發(fā)地址為:Go語(yǔ)言編程模式
在2016年倫敦舉辦的QCon大會(huì)上,Peter Bourgon做了《六年Go語(yǔ)言設(shè)計(jì)經(jīng)驗(yàn)》的報(bào)告活尊,重點(diǎn)探討了在使用Go進(jìn)行開發(fā)時(shí)的編程模式和反模式隶校。在這里,我們將他給Go開發(fā)者的建議進(jìn)行了簡(jiǎn)單的總結(jié)蛹锰。
GOPATH:將GOPATH/bin添加到“PATH”這個(gè)環(huán)境變量中深胳,以便Go應(yīng)用可以訪問所需要的二進(jìn)制文件。在絕大多數(shù)場(chǎng)景下铜犬,Bourgon建議使用全局唯一的GOPATH稠屠。有些開發(fā)者希望嚴(yán)格區(qū)分自己的代碼和外部依賴代碼,這些人更傾向于創(chuàng)建兩個(gè)GOPATH條目翎苫。開發(fā)者也可以選擇不設(shè)置環(huán)境變量,并針對(duì)每個(gè)工程都使用gb構(gòu)建榨了。
代碼倉(cāng)庫(kù)的結(jié)構(gòu): 代碼倉(cāng)庫(kù)的結(jié)構(gòu)依賴于項(xiàng)目結(jié)構(gòu)煎谍。如果是私人項(xiàng)目,開發(fā)者可以選擇自己喜歡的任何結(jié)構(gòu)龙屉。如果是開源項(xiàng)目呐粘,開發(fā)者最好遵循Remote Packages的建議,以便go get命令引用該項(xiàng)目的包转捕。Bourgon建議創(chuàng)建一個(gè)基礎(chǔ)目錄作岖,其中要包含程序的主要構(gòu)件,以及放置幫助包的子目錄五芝,具體如下圖所示:
代碼格式化: Bourgon強(qiáng)調(diào)開發(fā)者需要重視Go的權(quán)威的代碼格式化風(fēng)格痘儡,一旦開發(fā)者習(xí)慣這種風(fēng)格,他的代碼的可讀性將大大提高枢步。按照Bougron的觀點(diǎn)沉删,Go開發(fā)者社區(qū)會(huì)認(rèn)為非格式化的代碼出自計(jì)算機(jī)新手渐尿。每次保存之前,可以使用gofmt工具格式化代碼矾瑰。他認(rèn)為Go代碼審核指南為開發(fā)者和代碼審核者提供了一套通用的實(shí)踐規(guī)則砖茸。他還支持Andrew Gerrand關(guān)于Go開發(fā)的建議,包括如何為變量殴穴、函數(shù)和exports等元素命名凉夯,如果你能夠遵循這些建議,閱讀你代碼的人將會(huì)非常感激你采幌。
配置: Bourgon建議配置管理應(yīng)該有“清晰的定義和良好的文檔支持劲够。”他仍舊在使用來(lái)自標(biāo)準(zhǔn)庫(kù)的flag包植榕,不過也希望這個(gè)包能夠更簡(jiǎn)單易懂再沧。他強(qiáng)調(diào)了明確定義配置項(xiàng)的重要性。通過環(huán)境變量傳遞配置項(xiàng)并沒有為應(yīng)用的使用者提供足夠的信息去理解應(yīng)用的參數(shù)使用尊残,他建議在help中提供必要的配置信息炒瘸。
包名: 應(yīng)該根據(jù)某個(gè)模塊提供的服務(wù)而不是它的內(nèi)容來(lái)定義包名。如果一個(gè)包含有HelloWorld消息寝衫,那么它不應(yīng)該被稱為common或consts顷扩,而是greetings。包名應(yīng)該表明它所做什么慰毅,而不是它有什么隘截。
點(diǎn)導(dǎo)入: Bourgon建議不要使用“點(diǎn)導(dǎo)入”龙誊,這個(gè)特性通過設(shè)置點(diǎn)號(hào)來(lái)代替包名栅葡,使得開發(fā)者不需要明確的包名就可以訪問相應(yīng)包中的變量赤嚼。這個(gè)特性降低了項(xiàng)目的可讀性深员,尤其對(duì)于新手追他,新來(lái)的開發(fā)人員容易弄錯(cuò)哪個(gè)變量屬于哪個(gè)包诅愚。Go——顯式聲明優(yōu)于隱式聲明摇天。
Flags: Bourgon并不認(rèn)為在init()方法而不在main()方法中初始化flags是一個(gè)好主意硫眯,因?yàn)檫@使得這些flags無(wú)法在全局領(lǐng)域使用宰掉,而某些測(cè)試用例要用到這些flags呵哨。
構(gòu)造函數(shù): 在談到構(gòu)造函數(shù)時(shí),他建議將初始化的struct以內(nèi)聯(lián)方式直接作為參數(shù)傳入轨奄,從而避免傳入無(wú)效或者未完成的狀態(tài)孟害,例如:
foo := newFoo(*fooKey, fooConfig{
Bar: bar,
Baz: baz,
Period: 100 * time.Millisecond,
})
有意義的默認(rèn)值: 不要使用nil初始化某個(gè)變量,這使得每次在使用該變量的時(shí)候都需要進(jìn)行空值檢查挪拟,最好使用一個(gè)無(wú)操作值(no-operation value)進(jìn)行變量初始化挨务。例如,使用ioutil.Discard初始化一個(gè)output變量。
模塊的交叉引用: 有些情況下會(huì)出現(xiàn)兩個(gè)互相引用的模塊耘子。在構(gòu)建其中的一個(gè)時(shí)果漾,同時(shí)需要構(gòu)建另一個(gè)模塊,在構(gòu)建后一個(gè)時(shí)又需要第一個(gè)先構(gòu)建谷誓,下列兩個(gè)structs的定義就屬于這種情況:
type bar struct {
baz *baz
}
type baz struct {
bar *bar
}
Bourgon提供了三種方法處理這種情況:
- 整合:兩個(gè)關(guān)系如此密切的對(duì)象應(yīng)該整合成一個(gè)绒障,在這種情況下應(yīng)該整合成一個(gè)barbaz結(jié)構(gòu)體。
- 分割:如果這兩個(gè)模塊必須保持分割捍歪,那么可以應(yīng)用下列代碼中采取的策略:
type bar struct {
a *atom
monad
}
type baz struct {
atom
m *monad
}
a := &atom{...}
m := newMonad(...)
bar := newBar(a, m, ...)
baz := newBaz(a, m, ...)
- 通信:當(dāng)上述兩種方法都不適用時(shí)户辱,可以考慮在兩個(gè)模塊之間發(fā)送消息。
type bar struct {
toBaz chan<- event
}
type baz struct {
fromBar <-chan event
}
c := make(chan event)
bar := newBar(c, ...)
baz := newBaz(c, ...)
依賴: Bourgon還提出了”明確依賴關(guān)系“的建議糙臼,例如:
unc (f *foo) process() {
log.Printf("bar: %v", result) // ...
}
應(yīng)該寫成下面這樣:
func (f *foo) process() {
f.Logger.Printf("bar: %v", result) // ...
}
log.Printf實(shí)際上調(diào)用了Logger模塊庐镐,這么寫的話就隱去了這層依賴關(guān)系。為了明確這層依賴關(guān)系变逃,開發(fā)者應(yīng)該在構(gòu)造過程中創(chuàng)建一個(gè)Logger對(duì)象必逆,并使用ioutil.Discard代替空值nil。
通道(Channel): Bourgon建議揽乱,當(dāng)多個(gè)協(xié)程(goroutine)之間共享內(nèi)存時(shí)應(yīng)使用mutex名眉,并通過通道對(duì)協(xié)程進(jìn)行協(xié)調(diào)。
日志打踊嗣蕖: 日志記錄的代價(jià)很高损拢,有可能成為應(yīng)用的性能瓶頸。因此撒犀,建議只在絕對(duì)必要的地方記錄日志福压,包括給開發(fā)者閱讀或者供機(jī)器調(diào)用的信息。僅僅需要記錄info和debug級(jí)別的日志或舞。
監(jiān)控工具: Bourgon認(rèn)為Go應(yīng)用的監(jiān)控代價(jià)很小荆姆,推薦開發(fā)者使用Prometheus監(jiān)控自己應(yīng)用使用的各種資源。
全局狀態(tài): 消除隱式的全局依賴和全局狀態(tài)映凳。
測(cè)試: 執(zhí)行包級(jí)別的測(cè)試胆筒。為了測(cè)試而設(shè)計(jì):使用函數(shù)式編程風(fēng)格——使用參數(shù)表明依賴關(guān)系、使用接口以及避免依賴全局狀態(tài)魏宽。
依賴管理: 將所有依賴項(xiàng)都拷貝到項(xiàng)目的倉(cāng)庫(kù)中用于構(gòu)建二進(jìn)制代碼。Bourgon建議開發(fā)者根據(jù)自己的需要從gvt决乎、vendetta队询、glide或gb這幾個(gè)工具中選擇。
構(gòu)建: 不要使用go build构诚,要使用go install蚌斩,因?yàn)楹笳呖梢跃彺嬉蕾囮P(guān)系,并把這些依賴關(guān)系放在GOPATH/bin下以便于調(diào)用范嘱。
這些建議已經(jīng)被應(yīng)用于開發(fā)Go Kit送膳,一款用于構(gòu)建微服務(wù)的分布式編程工具员魏。
2009年以來(lái),Bourgon在SoundCloud和Weaveworks兩家公司都使用Go語(yǔ)言開發(fā)叠聋,開發(fā)了幾款產(chǎn)品撕阎,包括:Roshi——一款基于時(shí)間序列的事件數(shù)據(jù)庫(kù),以及Go Kit。
2016年QCon大會(huì)上的《六年Go語(yǔ)言設(shè)計(jì)經(jīng)驗(yàn)》視頻將會(huì)在今年晚些時(shí)候?qū)ν夤_碌补。
查看英文原文:Programming Patterns in Go
本號(hào)專注于后端技術(shù)虏束、JVM問題排查和優(yōu)化、Java面試題厦章、個(gè)人成長(zhǎng)和自我管理等主題镇匀,為讀者提供一線開發(fā)者的工作和成長(zhǎng)經(jīng)驗(yàn),期待你能在這里有所收獲袜啃。