Go1-17 新特性:對 Go 依賴管理的一把大剪刀

大家好,我是煎魚。

不得不說。我可是個經歷過 Go 依賴管理羣魔亂舞,Go modules 遷移一堆 BUG 的人兒,難頂... 爲此當年我寫了不少技術文章,希望給大家避坑。

如下:

在近期 Go1.17 發佈後,Go modules 帶來了兩大更新,煎魚摩拳擦掌,他們分別是:

今天帶大家一起來了解這兩塊內容,爭取了解其爲何物,背景又是什麼。

背景

在日常的 Go 工程開發中,不知道你有沒有遇到過 Go modules 的一個奇怪的點。大家沒說,就以爲是正確的,默認就接受了。

引用官方的 mod_lazy_new_import.txt[1] 的案例來說,就是假設我們在代碼中:

關聯如下圖所示:

module 關聯圖示

這個 Go 程序如果運行起來,會發生什麼情況呢?在 Go1.17 以前,如果你不存在 module C 的 package z,程序在編譯構建的時候就會報錯,提示找不到。

實際上 module C 的 package z 並沒有對你主程序有任何建設意義,俗話來講就是 “佔着茅坑不拉屎”。

他只是因爲 main module 在導入 module A 時,也被 “間接” 導入了 package y 的依賴,也就是我們常看到的 go.mod 文件中的 “indirect” 標識,他們會導致構建失敗,讓人直呼無奈。

Go1.17 module 改進

顯然,社區反饋希望避免看到 “不相關” 的傳遞依賴等,也因此有了 Go1.17 的 module 改造。

接下來的 module 例子我們將會結合提案 《Proposal: Lazy Module Loading》[2] 、《cmd/go: module graph pruning and lazy module loading》[3] 以及 《Module graph pruning[4]》的內容、案例來進行說明和介紹。

module graph pruning

第一個改進就是模塊依賴圖裁剪(module graph pruning),這是這個版本 module 優化的基礎。

在 Go1.17 以前,只要該項目的 go.mod 文件分析出來你存在間接的依賴,如果你沒有安裝過該依賴,就會出現報錯。

錯誤提示如下:

$ go build
go: example.com/a@v0.1.0 requires
 example.com/c@v0.1.0: missing go.sum entry; to add it:
 go mod download example.com/c

這個時候我們都會默默地去安裝一遍... 沒想過這是間接依賴,和我們的程序沒一點直接的代碼關係。

在 Go1.17 及之後就變了,go.mod 文件如下,會存在 2 塊 require 代碼塊:

module example.com/lazy

go 1.17

require example.com/a v0.1.0

require example.com/b v0.1.0 // indirect
...

這就是區別,第一塊的 require 我們眼熟,那分拆出來的第二塊 require 的是什麼呢?

這就是那些模塊的間接依賴(常見到的 indirect 標識依賴)。可以理解爲像是其他語言的 xxx.lock 文件一樣的存在。

此處分析出來的間接依賴,將會不會像以前一樣阻礙編譯構建,只會真正有使用到的纔會進行識別。

lazy module loading

第二個改進是延時模塊加載(lazy module loading),是基於模塊依賴圖裁剪(module graph pruning)的場景上的進一步優化。

也就是以往那些沒被使用到的,但又間接依賴的模塊。在 Go1.17 及以後不會被 Go 命令讀取和加載,只有真正需要的時候纔會加載。

副作用

Go module 依賴圖裁剪也帶來了一個副作用,那就是 go.mod 文件 size 會變大。

在 Go 1.17 版本之後,每次 go mod tidy(當 go.mod 中的 go 版本爲 1.17 時),Go 命令都會對 main module 的依賴做一次深度掃描(deepening scan)。

該操作將 main module 的所有直接和間接依賴都記錄在 go.mod 中,考慮到內容較多,Go 1.17 將直接依賴和間接依賴分別放在兩個不同的 require 代碼塊中。

也就是上文所見到的內容。

總結

自從 Go 語言推出 Go modules 依賴,module 一直不斷地在優化和改進。雖然看上去已經越來越好用了,但依然似乎存在不少問題。

就拿本次變更來講,我也是在好朋友的 Go 微信羣中看到提問,才思考了起來。因爲大家看到第二塊 require 時,雖然知道是間接依賴的包,但更明確,爲什麼要單獨出來?

大家其實是不大理解的,本次變更也可能存在語義不清,不夠明確的情況。但無論如何,後續我們可以繼續觀察。

參考資料

[1]

mod_lazy_new_import.txt: https://github.com/golang/go/blob/4012fea822763ef3aa66dd949fa95b9f8d89450a/src/cmd/go/testdata/script/mod_lazy_new_import.txt

[2]

Proposal: Lazy Module Loading: https://go.googlesource.com/proposal/+/master/design/36460-lazy-module-loading.md

[3]

cmd/go: module graph pruning and lazy module loading: https://github.com/golang/go/issues/36460

[4]

Module graph pruning: https://golang.google.cn/ref/mod#graph-pruning

關注煎魚,吸取他的知識 👆

你好,我是煎魚。高一折騰過前端,參加過國賽拿了獎,大學搞過 PHP。現在整 Go,在公司負責微服務架構等相關工作推進和研發。

從大學開始靠自己賺生活費和學費,到出版 Go 暢銷書《Go 語言編程之旅》,再到獲得 GOP(Go 領域最有觀點專家)榮譽,點擊藍字查看我的出書之路

日常分享高質量文章,輸出 Go 面試、工作經驗、架構設計,加微信拉讀者交流羣,記得點贊!

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/2vVGVd_QJSrCeenuvwGS3g