大家好,我是煎魚。

平時我們經常會進行網上衝浪,學習經驗、知識以及吃瓜。在程式碼界,還有同學調侃我們就是 c+v (複製貼上)工程師。

我的專用快捷鍵:

在 Go 語言中,有一句諺語也指出了 ”複製“ 的有益之處,叫做:"A little copying is better than a little dependency"(複製一點總比依賴一點好)。

重點關鍵字是:複製,依賴。

複製一點 vs 引入依賴

複製,只要核心

如果可以自己寫一些短小精悍的程式碼,那就沒有必要直接匯入一個庫去做(可以只複製核心演算法)。

例如 UUID 的案例:

func main() {
 f, _ := os.Open("/dev/urandom")
 b := make([]byte16)
 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 的第三方庫,但普遍會有許多功能堆積在一個庫中,這樣會引入許多不必要的新依賴。

如果只是要一點新功能,可以自己簡單實現,封裝為公司內部方法匯入。

可以有效減少依賴管理的負擔,縮小二進位制檔案大小,帶來更大的穩定性、安全、測試第三方庫這方面大多都是不清楚的。

引入大依賴,易折騰

指向的副作用是在我們引用依賴了太多的東西時,會導致產生一個應用,依賴過多的場景:

比較經典的是微服務的依賴。更貼近我們的場景,那就是 Go modules 中帶來的各第三方元件庫的版本互相制衡了。

最小版本選擇

以下介紹的是 Go Modules 的最小版本選擇的計算規則,其會帶來版本間的互相制衡。

一個模組往往依賴著許多其它許許多多的模組,並且不同的模組在依賴時很有可能會出現依賴同一個模組的不同版本,如下圖(來自Russ Cox):

在上述依賴中,模組 A 依賴了模組 B 和模組 C,而模組 B 依賴了模組 D,模組 C 依賴了模組 D 和 F,模組 D 又依賴了模組 E,而且同模組的不同版本還依賴了對應模組的不同版本。那麼這個時候 Go modules 怎麼選擇版本,選擇的是哪一個版本呢?

我們根據 proposal 可得知,Go modules 會把每個模組的依賴版本清單都整理出來,最終得到一個構建清單,如下圖(來自Russ Cox):

我們看到 rough list 和 final list,兩者的區別在於重複引用的模組 D(v1.3、v1.4),其最終清單選用了模組 D 的 v1.4 版本。

真實場景

在 Go RPC 的使用中,gRPC 的應用是非常廣泛的。而 gRPC、grpc-gateway、protoc(含對應語言的 plugin)、etcd,幾者的版本是會有不相容的情況的。

例如:gRPC 本身會做一些實驗性的 package,etcd 在 v3.5.0 前沒有 Go modules 的良好版本管理,同時 protoc 的高版本又會對 gRPC 的版本有一定的要求,會形成各第三方庫對各庫版本有要求的情況。

在內部框架或應用中,我們常常會透過 go.mod 來宣告所使用的版本。但在 ”最小版本選擇“ 的存在下,其遵守版本化,一旦依賴的另外一個庫,要求更高的 gRPC 版本,就會打破這個平衡。

最近一次見到的,就是公司內有人使用 TIDB 的庫,只是使用了某一塊東西,但卻導致大量被依賴的版本被動升級。

最終這位同學就採取了複製一點的做法,解決了增加大量依賴的副作用。

總結

實際上 Go 的這句諺語 "A little copying is better than a little dependency",更多的是一種軟體工程裡的指導思想。

當你只是涉及到一個很簡單的功能,那完全可以自行實現或複製核心程式碼。沒必要直接匯入一個大的第三方庫,它有可能帶來許多奇奇怪怪的依賴,使得你的編譯構建變得緩慢,依賴管理也複雜了起來。

這是需要我們都好好思考的。

推薦閱讀


關注和加煎魚微信,

獲取一手業內訊息和知識,拉你進交流群👇



你好,我是煎魚,出版過 Go 暢銷書《Go 語言程式設計之旅》,再到獲得 GOP(Go 領域最有觀點專家)榮譽,點選藍字檢視我的出書之路

日常分享高質量文章,輸出 Go 面試、工作經驗、架構設計,加微信拉讀者交流群,和大家交流!

阅读原文
在看