Go 語言創(chuàng)始人:復(fù)制億點點代碼比用別人輪子好!
大家好,我是煎魚。
平時我們經(jīng)常會進行網(wǎng)上沖浪,學(xué)習(xí)經(jīng)驗、知識以及吃瓜。在代碼界,還有同學(xué)調(diào)侃我們就是 c+v (復(fù)制粘貼)工程師。
我的專用快捷鍵:
在 Go 語言中,有一句諺語也指出了 ”復(fù)制“ 的有益之處,叫做:"A little copying is better than a little dependency"(復(fù)制一點總比依賴一點好)。
重點關(guān)鍵字是:復(fù)制,依賴。
復(fù)制一點 vs 引入依賴
復(fù)制,只要核心
如果可以自己寫一些短小精悍的代碼,那就沒有必要直接導(dǎo)入一個庫去做(可以只復(fù)制核心算法)。
例如 UUID 的案例:
func main() {
f, _ := os.Open("/dev/urandom")
b := make([]byte, 16)
f.Read(b)
f.Close()
uuid := fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
fmt.Println(uuid)
}
雖然有很多 UUID 的第三方庫,但普遍會有許多功能堆積在一個庫中,這樣會引入許多不必要的新依賴。
如果只是要一點新功能,可以自己簡單實現(xiàn),封裝為公司內(nèi)部方法導(dǎo)入。
可以有效減少依賴管理的負(fù)擔(dān),縮小二進制文件大小,帶來更大的穩(wěn)定性、安全、測試第三方庫這方面大多都是不清楚的。
引入大依賴,易折騰
指向的副作用是在我們引用依賴了太多的東西時,會導(dǎo)致產(chǎn)生一個應(yīng)用,依賴過多的場景:
比較經(jīng)典的是微服務(wù)的依賴。更貼近我們的場景,那就是 Go modules 中帶來的各第三方組件庫的版本互相制衡了。
最小版本選擇
以下介紹的是 Go Modules 的最小版本選擇的計算規(guī)則,其會帶來版本間的互相制衡。
一個模塊往往依賴著許多其它許許多多的模塊,并且不同的模塊在依賴時很有可能會出現(xiàn)依賴同一個模塊的不同版本,如下圖(來自Russ Cox):
在上述依賴中,模塊 A 依賴了模塊 B 和模塊 C,而模塊 B 依賴了模塊 D,模塊 C 依賴了模塊 D 和 F,模塊 D 又依賴了模塊 E,而且同模塊的不同版本還依賴了對應(yīng)模塊的不同版本。那么這個時候 Go modules 怎么選擇版本,選擇的是哪一個版本呢?
我們根據(jù) proposal 可得知,Go modules 會把每個模塊的依賴版本清單都整理出來,最終得到一個構(gòu)建清單,如下圖(來自Russ Cox):
我們看到 rough list 和 final list,兩者的區(qū)別在于重復(fù)引用的模塊 D(v1.3、v1.4),其最終清單選用了模塊 D 的 v1.4 版本。
真實場景
在 Go RPC 的使用中,gRPC 的應(yīng)用是非常廣泛的。而 gRPC、grpc-gateway、protoc(含對應(yīng)語言的 plugin)、etcd,幾者的版本是會有不兼容的情況的。
例如:gRPC 本身會做一些實驗性的 package,etcd 在 v3.5.0 前沒有 Go modules 的良好版本管理,同時 protoc 的高版本又會對 gRPC 的版本有一定的要求,會形成各第三方庫對各庫版本有要求的情況。
在內(nèi)部框架或應(yīng)用中,我們常常會通過 go.mod 來聲明所使用的版本。但在 ”最小版本選擇“ 的存在下,其遵守版本化,一旦依賴的另外一個庫,要求更高的 gRPC 版本,就會打破這個平衡。
最近一次見到的,就是公司內(nèi)有人使用 TIDB 的庫,只是使用了某一塊東西,但卻導(dǎo)致大量被依賴的版本被動升級。
最終這位同學(xué)就采取了復(fù)制一點的做法,解決了增加大量依賴的副作用。
總結(jié)
實際上 Go 的這句諺語 "A little copying is better than a little dependency",更多的是一種軟件工程里的指導(dǎo)思想。
當(dāng)你只是涉及到一個很簡單的功能,那完全可以自行實現(xiàn)或復(fù)制核心代碼。沒必要直接導(dǎo)入一個大的第三方庫,它有可能帶來許多奇奇怪怪的依賴,使得你的編譯構(gòu)建變得緩慢,依賴管理也復(fù)雜了起來。
這是需要我們都好好思考的。