Go 1.17 新特性:Module 有哪些變化?
閱讀本文大概需要 5 分鐘。
大家好,我是 polarisxu。
自從 Go1.11 增加 Go Module 以來,每個版本都在不斷改進 Module。Go1.17 也不例外。這次最主要的變化有兩點:
Module graph pruning:Module 依賴圖修剪 Lazy Loading:Module 延遲加載
此外還有 Deprecated 注釋等。本文就一起探究下這些新變化,因為有人沒看懂,不知道這些變化是什么意思。
01 Module 依賴圖修剪
要搞懂這個知識點,需要對比 1.17 之前的情況。
注意,這個變化并不會影響 go mod 的任何使用
為了方便演示,我們構(gòu)建一個這樣的例子。
有四個模塊:主模塊(studymod)和 a、b、c 三個模塊,如下圖:

module studymod 是我們的項目,它依賴模塊 a 中的 x 包,而 x 包依賴模塊 b,同時 a 包中的 y 包依賴模塊 c。
很顯然,對我們的項目 studymod 來說,模塊 c 的代碼根本沒用上。Go 1.17 對 module 的改進主要就是在這種沒用上的模塊上。
基于 Go1.16
假定在 $HOME 下創(chuàng)建一個新目錄 gomod116,構(gòu)建如下目錄結(jié)構(gòu):
[xuxinhua@/Users/xuxinhua/gomod116]
$ tree -L 3
.
├── a
│ ├── x
│ │ └── x.go // 包含正確的包定義和 import
│ └── y
│ └── y.go
├── b
│ └── b.go
├── c
│ └── c.go
└── studymod.go
在 Go 文件中只是簡單的定義包和 import。幾個 go 文件的內(nèi)容分別如下:
// x.go
package x
import _ "b"
// y.go
package y
import _ "c"
// b.go
package b
// c.go
package c
// studymod.go
package studymod
import _ "a/x"
進入 gomod116 目錄,將 Go 版本切換到 Go1.16.x(多版本發(fā)揮作用了,多版本問題,可以看看這篇文章:終于找到了一款我喜歡的安裝和管理 Go 版本的工具),然后執(zhí)行下列命令:
$ go mod init studymod
$ cd a
$ go mod init a
$ cd ../b
$ go mod init b
$ cd ../c
$ go mod init c
因為模塊 a 依賴模塊 b 和 c,往 a/go.mod 增加如下代碼:
module a
go 1.16
require (
b v0.1.0
c v0.1.0
)
在 gomod116 根目錄的 go.mod 增加如下代碼:
module studymod
go 1.16
require a v0.1.0
replace (
a v0.1.0 => ./a
b v0.1.0 => ./b
c v0.1.0 => ./c
)
因為 studymod 只直接依賴模塊 a。replace 部分可以忽略,只是為了本地能夠正常引入模塊。
此時,在 gomod116 目錄下執(zhí)行 go build,如果不報錯,表示一切正常,我們可以執(zhí)行如下命令看到依賴關系:
$ go mod graph
studymod [email protected]
[email protected] [email protected]
[email protected] [email protected]
studymod 依賴 a,a 依賴 b 和 c。
但我們知道,studymod 模塊實際根本不需要模塊 c,因此,我們嘗試在 studymod 模塊中刪除模塊 c 的引用,即刪除 go.mod 中 replace 部分的 c v0.1.0 => ./c,再次執(zhí)行 go build:
$ go build
go: [email protected] requires
[email protected]: missing go.sum entry; to add it:
go mod download c
可見模塊 c 不能少。(驗證后,記得將 go.mod 恢復原樣)
基于 Go1.17
現(xiàn)在我們基于 Go1.17 做類似的驗證,將 Go 切到 1.17,執(zhí)行如下命令,將 gomod116 拷貝一份:
$ cp -rf gomod116 gomod117
然后進入 gomod117 目錄,將 go.mod 的 版本由 1.16 改為 1.17:
module studymodgo 1.17require a v0.1.0replace ( a v0.1.0 => ./a b v0.1.0 => ./b c v0.1.0 => ./c)
接著執(zhí)行 go mod tidy,發(fā)現(xiàn) go.mod 變成這樣:
module studymodgo 1.17require a v0.1.0require b v0.1.0 // indirectreplace ( a v0.1.0 => ./a b v0.1.0 => ./b c v0.1.0 => ./c)
多了一行 require,記錄了 module studymod 的間接依賴:module [email protected]。執(zhí)行 go build 一切正常。
跟 Go1.16 一樣,刪除掉 c v0.1.0 => ./c 這行,再次執(zhí)行 go build,依然正常。這就是依賴圖裁剪,再看依賴關系,跟 Go1.16 是不一樣的。
$ go mod graphstudymod [email protected] [email protected]@v0.1.0 [email protected]@v0.1.0 [email protected]
這么做的優(yōu)劣
這么做是基于社區(qū)的反饋,有興趣的可以看看這個提案:Proposal: Lazy Module Loading,這也是合理的,畢竟沒有用到的代碼為什么一定需要呢?
此外,裁剪后,go.mod 中會包含入更多的依賴項(完整的依賴列表),新包含的依賴項單獨放在一個 require 下??梢酝ㄟ^ https://github.com/studygolang/studygolang 試驗下。下載該代碼后,執(zhí)行如下命令:
$ go mod tidy -go=1.17
diff 后,在原來的基礎上多出了如下的 require:

有點類似其他語言中 xxx.lock 文件的感覺了。
當然,這種方式后,go.mod 的文件會更大,這也是該方式的一個相對劣勢。
02 延遲加載(lazy Loading)
理解了上面依賴圖的裁剪,延遲加載就一句話:那些根本沒有用上的模塊(比如上面例子中的模塊 c),Go 1.17 后,Go 命令不會去讀取其 go.mod 文件。如果之后需要了,再去加載。
03 Deprecated 注釋
Go1.17 go.mod 中支持 Deprecated 注釋,用來標明該模塊廢棄了。
// Deprecated: use example.com/mod/v2 instead.module example.com/mod
對于那些使用了被廢棄的 module 的 go 項目,go list、go get 命令都會給出 warning。
看過我這篇文章:《從 go-chi 框架撤回所有主版本聊 Go1.16 的新特性》 的朋友,可能會有疑問,撤回和廢棄有何不同,如何分別使用?這里稍微總結(jié)下:
撤回的使用場景:
發(fā)現(xiàn)了一個嚴重的安全漏洞; 發(fā)現(xiàn)了嚴重的不兼容性或 bug; 這個版本是偶然發(fā)布的,或是過早發(fā)布了;
而 Deprecated 是用來作廢整個 module 的,也就是說,不能廢棄某個 minor 或 patch 版本。Deprecated 更多用來提示使用者升級到更新的 major 版本,比如要廢棄 v1,希望大家升級到 v2,就應該使用 Deprecated。
04 總結(jié)
還有其他一些小的變動,這里不一一列舉。提醒下大家,從 Go1.16 版本開始,下載安裝 Go 二進制程序,不再使用 go get,而是 go install,go get 只用來下載普通包。
因為 module 越來越完善,官方針對 module 也有更完整的文檔:https://docs.studygolang.com/ref/mod。
我是 polarisxu,北大碩士畢業(yè),曾在 360 等知名互聯(lián)網(wǎng)公司工作,10多年技術研發(fā)與架構(gòu)經(jīng)驗!2012 年接觸 Go 語言并創(chuàng)建了 Go 語言中文網(wǎng)!著有《Go語言編程之旅》、開源圖書《Go語言標準庫》等。
堅持輸出技術(包括 Go、Rust 等技術)、職場心得和創(chuàng)業(yè)感悟!歡迎關注「polarisxu」一起成長!也歡迎加我微信好友交流:gopherstudio
