Go Module 教程:最小版本選擇

引言

每個依賴管理解決方案都必須解決選擇依賴版本的問題。目前存在的許多版本選擇算法都試圖識別任何依賴關係的 “最新最大” 版本。如果你相信語義版本控制將被正確應用,社會契約將得到尊重,那麼這是有意義的。在這些情況下,依賴項的 “最新最大” 版本應該是最穩定和安全的版本,並且應該與早期版本具有向後兼容性。至少在相同的主版本依賴關係樹中應該如此。

Go 決定採取一種不同的方法,Russ Cox 花了大量的時間和精力寫作 [1] 和討論 [2] Go 團隊的版本選擇方法,這種方法被稱爲最小版本選擇或 MVS。實質上,Go 團隊相信 MVS 爲 Go 程序提供了持久的、可重複的長期構建的最佳機會。我建議讀一讀這篇文章 [3],理解爲什麼 Go 團隊相信這一點。

在本文中,我將盡力解釋 MVS 語義,並展示一個實際的 Go 示例和 MVS 算法。

MVS 語義

命名 Go 的選擇算法 “最小版本選擇” 有點用詞不當,但是一旦你瞭解了它的工作原理,你就會發現它的名字非常接近。正如我之前所說的,許多選擇算法選擇依賴項的 “最新最大” 版本。我喜歡把 MVS 看作是一種選擇 “最新的非最大” 版本的算法。並不是 MVS 不能選擇“最新的最大”,只是如果項目中的任何依賴項都不需要“最新的最大”,那麼就不需要該版本。

爲了更好地理解這一點,讓我們創建這樣一種情況: 幾個模塊 (A、B 和 C) 依賴於同一個模塊(D) ,但每個模塊需要不同的版本。

圖 1

圖 1 顯示了模塊 A、B 和 C 各自獨立地需要模塊 D,並且每個模塊都需要不同版本的模塊。

如果我啓動一個需要模塊 A 的項目,那麼爲了構建代碼,我還需要模塊 D。模塊 D 有很多版本可供選擇。例如,想象模塊 D 是 sirupsen 中的 logrus 模塊。我可以要求 Go 爲我提供一個所有已經被標記爲模塊 D 的版本的列表。

清單 1

$ go list -m -versions github.com/sirupsen/logrus

github.com/sirupsen/logrus v0.1.0 v0.1.1 v0.2.0
v0.3.0 v0.4.0 v0.4.1 v0.5.0 v0.5.1 v0.6.0 v0.6.1
v0.6.2 v0.6.3 v0.6.4 v0.6.5 v0.6.6 v0.7.0 v0.7.1 
v0.7.2 v0.7.3 v0.8.0 v0.8.1 v0.8.2 v0.8.3 v0.8.4
v0.8.5 v0.8.6 v0.8.7 v0.9.0 v0.10.0 v0.11.0 v0.11.1
v0.11.2 v0.11.3 v0.11.4 v0.11.5 v1.0.0 v1.0.1 v1.0.3
v1.0.4 v1.0.5 v1.0.6 v1.1.0 v1.1.1 v1.2.0 v1.3.0
v1.4.0 v1.4.1 v1.4.2

清單 1 顯示了模塊 D 的所有版本,其中顯示了 “最新最大” 的版本爲 v1.4.2。

應該爲項目選擇哪個版本的模塊 D?實際上有兩種選擇。第一個選擇是選擇 “最新最大” 的版本(在這一行的主要版本 1 版本中) ,它將是版本 1.4.2。第二個選擇是選擇模塊 A 需要的版本,即 v1.0.6 版本。

像 dep 這樣的依賴工具會選擇 v1.4.2 版本,並在語義版本控制和社會契約得到尊重的前提下工作。然而,根據 Russ 在該文 [4] 中定義的原因,Go 將尊重模塊 A 的要求,選擇 v1.0.6 版本。Go 爲項目中需要該模塊的所有依賴項選擇當前在所需版本集中的 “最小” 版本。換句話說,現在只有模塊 A 需要模塊 D,而模塊 A 已經指定它需要版本 v1.0.6,因此這將作爲模塊 D 的版本。

如果我引入新的代碼,要求項目導入模塊 B,會怎麼樣?一旦模塊 B 被導入到項目中,Go 將該項目的模塊 D 的版本從 v1.0.6 升級到 v1.2.0。再次爲項目中需要模塊 D 的所有依賴項 (模塊 A 和模塊 B) 選擇模塊 D 的 “最小” 版本,該版本目前位於所需版本集 (v1.0.6 和 v1.2.0 ) 中。

如果我再次引入需要項目導入模塊 C 的新代碼會怎麼樣?然後 Go 將從所需的版本集 (v1.0.6、 v1.2.0、 v1.3.2) 中選擇最新版本 (v1.3.2)。請注意,v1.3.2 版本仍然是“最小” 版本,而不是模塊 D (v1.4.2)的 “最新最大” 版本。

最後,如果我刪除剛剛爲模塊 C 添加的代碼會怎樣?Go 將把該項目鎖定到模塊 D 的版本 v1.3.2 中,降級回到版本 v1.2.0 將是一個更大的改變,而且 Go 知道版本 v1.3.2 工作正常且穩定,因此版本 v1.3.2 仍然是該項目模塊 D 的 “最新非最大” 或“最小”版本。另外,模塊文件只維護一個快照,而不是日誌。沒有關於歷史撤銷或降級的信息。

這就是爲什麼我喜歡將 MVS 看作是一種選擇模塊的 “最新非最大” 版本的算法。希望您現在明白爲什麼 Russ 在命名算法時選擇 “minimal”這個名稱。

示例項目

有了這個基礎,我將以上放在一個項目裏,這樣你就可以看到 Go 和 MVS 算法起的作用。在這個項目中,模塊 D 將表示 logrus 模塊,該項目直接依賴 rethinkdb-go[5] (模塊 A) 和 golib[6] (模塊 B)模塊。Rethinkdb-go 和 golib 模塊直接依賴 logrus 模塊,並且每個模塊都需要不同的版本,而不是 logrus 的 “最新最大” 版本。

圖 2

圖 2 顯示了這三個模塊之間的獨立關係。首先,我將創建項目,初始化模塊,然後啓動 VSCode。

清單 2

cd $HOME
$ mkdir app
$ mkdir app/cmd
$ mkdir app/cmd/db
$ touch app/cmd/db/main.go
$ cd app
$ go mod init app
$ code .

清單 2 顯示了要運行的所有命令。

圖 3

圖 3 顯示了項目結構和模塊文件應該包含的內容。現在可以添加使用 rethinkdb-go 模塊的代碼了。

清單 3:https://play.studygolang.com/p/bc5I0Afxhvc

package main

import (
 "context"
 "log"

 db "gopkg.in/rethinkdb/rethinkdb-go.v5"
)

func main() {
 c, err := db.NewCluster([]db.Host{{Name: "localhost", Port: 3000}}, nil)
 if err != nil {
  log.Fatalln(err)
 }

 if _, err = c.Query(context.Background(), db.Query{}); err != nil {
  log.Fatalln(err)
 }
}

清單 3 引入了 rethinkdb-go 模塊的主版本 5。添加並保存這段代碼之後,Go 查找、下載並提取模塊,更新 go.mod 和 go.sum 文件。

清單 4

module app

go 1.13

require gopkg.in/rethinkdb/rethinkdb-go.v5 v5.0.1

清單 4 顯示了 go.mod 文件,該文件要求 rethinkdb-go 模塊作爲一個直接依賴項,選擇 v5.0.1 版本,這是該模塊的 “最新最大” 版本。

清單 5

...
github.com/sirupsen/logrus v1.0.6 h1:hcP1GmhGigz/O7h1WVUM5KklBp1JoNS9FggWKdj/j3s=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
...

清單 5 顯示了 go.sum 文件中的兩行代碼,它們是 logrus 模塊的 v1.0.6 版本。此時,你可以看到 MVS 算法已經選擇了 logrus 模塊的 “最小” 版本,以滿足 rethinkdb-go 模塊所指定的需求。記住 logrus 模塊的最新版本是 1.4.2。

注意:go.sum 文件應該被視爲不透明的可靠性物件,不應該用它來理解您的依賴關係。我在上面所做的確定版本是錯誤的,不久我將向您展示確定你的項目使用什麼版本的正確方法。

圖 4

圖 4 顯示了將使用哪個版本的 logrus 模塊 Go 來構建項目中的代碼。

接下來,我將添加引入 golib 模塊依賴項的代碼。

清單 6:https://play.studygolang.com/p/h23opcp5qd0

package main

import (
 "context"
 "log"

 "github.com/Bhinneka/golib"
 db "gopkg.in/rethinkdb/rethinkdb-go.v5"
)

func main() {
 c, err := db.NewCluster([]db.Host{{Name: "localhost", Port: 3000}}, nil)
 if err != nil {
  log.Fatalln(err)
 }

 if _, err = c.Query(context.Background(), db.Query{}); err != nil {
  log.Fatalln(err)
 }
 
 golib.CreateDBConnection("")
}

清單 6 爲程序添加了第 07 行和第 21 行。一旦 Go 查找、下載並提取 golib 模塊,go.mod 文件中將顯示以下更改。

清單 7

module app

go 1.13

require (
  github.com/Bhinneka/golib v0.0.0-20191209103129-1dc569916cba
    gopkg.in/rethinkdb/rethinkdb-go.v5 v5.0.1
)

清單 7 顯示了 go.mod 文件已經被修改,以包含 golib 模塊對該模塊的 “最新最大” 版本的依賴關係,該模塊沒有語義版本標記。

清單 8

...
github.com/sirupsen/logrus v1.0.6 h1:hcP1GmhGigz/O7h1WVUM5KklBp1JoNS9FggWKdj/j3s=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
...

清單 8 顯示了 go.sum 文件中的四行代碼,它們現在包含 logrus 模塊的 v1.0.6 和 v1.2.0 版本。看到 go.sum 文件中列出的兩個版本,就會產生兩個疑問:

  1. 爲什麼兩個版本都在 go.sum 文件中?

  2. 當 Go 執行構建時,會使用哪個版本?

兩個版本都在 go.sum 文件中列出的原因,Go 團隊的 Bryan Mills 回答得更好。

”go.sum 文件仍然包含舊版本 (1.0.6) ,因爲它的傳遞需求可能會影響其他模塊的選定版本。我們實際上只需要 go.mod 文件的校驗和,因爲它聲明瞭那些可傳遞的需求,但我們最終還是保留了源代碼的校驗和,因爲 go mod tidy 並不像它應該的那樣精確。” <https://github.com/golang/go/issues/33008

這仍然留下了在構建項目時將使用哪個版本的 logrus 模塊的問題。要正確識別將要使用的模塊及其版本,不要查看 go.sum 文件,而是使用 go list 命令。

清單 9

$ go list -m all | grep logrus

github.com/sirupsen/logrus v1.2.0

清單 9 顯示了在構建項目時將使用 logrus 模塊的 v1.2.0 版本。M 標誌將 go list 指向列表模塊而不是包。

查看模塊圖可以更深入地瞭解項目對 logrus 模塊的需求。

清單 10

$ go mod graph | grep logrus

github.com/sirupsen/logrus@v1.2.0 github.com/pmezard/go-difflib@v1.0.0
github.com/sirupsen/logrus@v1.2.0 github.com/stretchr/objx@v0.1.1
github.com/sirupsen/logrus@v1.2.0 github.com/stretchr/testify@v1.2.2
github.com/sirupsen/logrus@v1.2.0 golang.org/x/crypto@v0.0.0-20180904163835-0709b304e793
github.com/sirupsen/logrus@v1.2.0 golang.org/x/sys@v0.0.0-20180905080454-ebe1bf3edb33
gopkg.in/rethinkdb/rethinkdb-go.v5@v5.0.1 github.com/sirupsen/logrus@v1.0.6
github.com/sirupsen/logrus@v1.2.0 github.com/konsorten/go-windows-terminal-sequences@v1.0.1
github.com/sirupsen/logrus@v1.2.0 github.com/davecgh/go-spew@v1.1.1
github.com/Bhinneka/golib@v0.0.0-20191209103129-1dc569916cba github.com/sirupsen/logrus@v1.2.0
github.com/prometheus/common@v0.2.0 github.com/sirupsen/logrus@v1.2.0

清單 10 顯示了 logrus 模塊在項目中的關係。我將提取直接顯示 logrus 上的依賴需求的代碼行。

清單 11

gopkg.in/rethinkdb/rethinkdb-go.v5@v5.0.1 github.com/sirupsen/logrus@v1.0.6
github.com/Bhinneka/golib@v0.0.0-20191209103129-1dc569916cba github.com/sirupsen/logrus@v1.2.0
github.com/prometheus/common@v0.2.0 github.com/sirupsen/logrus@v1.2.0

在清單 11 中,這些代碼行顯示三個模塊 (rethinkdb-go、 golib、 common) 都需要 logrus 模塊。多虧了 go list 命令,我知道所需的最小版本是 v1.2.0 版本。

圖 5

圖 5 顯示了 logrus 模塊 Go 現在將使用哪個版本來構建項目中所有需要 logrus 模塊的依賴項的代碼。

Go Mod Tidy

在你將代碼 commit/push 到 repo 之前,運行 go mod tidy 以確保你的模塊文件是當前的和準確的。你在本地構建、運行或測試的代碼將影響 Go 在任何時候決定更新模塊文件中的內容。運行 go mod tidy 將保證項目有一個準確和完整的快照,什麼是需要的,這將有助於你的團隊和你的 CI/CD 環境的其他人。

清單 12

$ go mod tidy

go: finding github.com/Bhinneka/golib latest
go: finding github.com/bitly/go-hostpool latest
go: finding github.com/bmizerany/assert latest

清單 12 顯示了運行 go mod tidy 時的輸出。你可以在輸出中看到兩個新的依賴項。這將更改模塊文件。

清單 13

module app

go 1.13

require (
    github.com/Bhinneka/golib v0.0.0-20191209103129-1dc569916cba
    github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 // indirect
    github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
    gopkg.in/rethinkdb/rethinkdb-go.v5 v5.0.1
)

清單 13 顯示,go-hostpool 和 assert 模塊被列爲構建項目所需的間接模塊。這裏列出它們是因爲這些項目目前不符合模塊標準。換句話說,對於這些項目的任何標記版本或 master 中的 “最新最大” 版本,在 repo 中都不存在 go.mod 文件。

Why were these modules included after running go mod tidy? I can use the go mod why command to find out.

爲什麼這些模塊包括後,運行去模組整理?我可以使用 go mod why 命令來找出答案。

清單 14

$ go mod why github.com/hailocab/go-hostpool

# github.com/hailocab/go-hostpool
app/cmd/db
gopkg.in/rethinkdb/rethinkdb-go.v5
github.com/hailocab/go-hostpool

------------------------------------------------

$ go mod why github.com/bmizerany/assert

# github.com/bmizerany/assert
app/cmd/db
gopkg.in/rethinkdb/rethinkdb-go.v5
github.com/hailocab/go-hostpool
github.com/hailocab/go-hostpool.test
github.com/bmizerany/assert

清單 14 顯示了爲什麼項目間接需要這些模塊。Rethinkdb-go 模塊需要 go-hostpool 模塊,go-hostpool 模塊需要 assert 模塊。

升級依賴

該項目有三個依賴項,每個依賴項都需要 logrus 模塊,其中 logrus 模塊的 v1.2.0 版本當前被選中。在項目生命週期的某個階段,升級直接和間接的依賴關係將變得非常重要,以確保項目所需的代碼是最新的,並且能夠利用新特性、 bug 修復和安全補丁。爲了應用升級,Go 提供了 go get 命令。

在你運行 go get 升級項目的依賴項之前,有幾個選項需要考慮。

使用 MVS 升級只需要直接和間接依賴項。

我建議你從這種類型的升級開始,直到你對項目和模塊有了更多的瞭解。這是 go get 最保守的形式。

清單 15

$ go get -t -d -v ./...

清單 15 展示瞭如何使用 MVS 算法執行只關注所需依賴項的升級。下面是 flag 的定義。

對當前項目運行此命令將不會導致任何更改,因爲項目已經具有構建和測試項目所需的最小版本的最新版本。那是因爲我剛剛運行了 go mod tidy,這個項目是新的。

使用 Latest Greatest 升級所有直接和間接依賴項

這種升級將使整個項目的依賴性從 “最小” 提高到“最大”。所需要做的就是將 -u 標誌添加到命令行中。

清單 16

$ go get -u -t -d -v ./...

go: finding golang.org/x/net latest
go: finding golang.org/x/sys latest
go: finding github.com/hailocab/go-hostpool latest
go: finding golang.org/x/crypto latest
go: finding github.com/google/jsonapi latest
go: finding gopkg.in/bsm/ratelimit.v1 latest
go: finding github.com/Bhinneka/golib latest

清單 16 顯示了使用 -u 標誌運行 go get 命令的輸出。這個輸出沒有告知真實的情況。如果我向 go list 命令詢問哪個版本的 logrus 模塊現在正被用於構建項目,會發生什麼情況?

清單 17

$ go list -m all | grep logrus

github.com/sirupsen/logrus v1.4.2

清單 17 顯示瞭如何選擇 logrus 的 “最新最大” 版本。爲了將這個選項保存下來,對 go.mod 文件進行了更改。

module app

go 1.13

require (
    github.com/Bhinneka/golib v0.0.0-20191209103129-1dc569916cba
    github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 // indirect
    github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
    github.com/cenkalti/backoff v2.2.1+incompatible // indirect
    github.com/golang/protobuf v1.3.2 // indirect
    github.com/jinzhu/gorm v1.9.11 // indirect
    github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
    github.com/sirupsen/logrus v1.4.2 // indirect
    golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 // indirect
    golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
    golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 // indirect
    gopkg.in/rethinkdb/rethinkdb-go.v5 v5.0.1
)

清單 18 在第 13 行顯示,v1.4.2 版本現在是項目中 logrus 模塊的選定版本。模塊文件中的這一行是 Go 在構建項目時所遵循的。即使刪除了改變 logrus 模塊依賴性的代碼,v1.4.2 版本對於這個項目來說仍然是一成不變的。請記住,降級將是一個比升級到 v. 1.4.2 版本更大的變化。

在 go.sum 文件中可以看到哪些更改?

清單 19

github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=

清單 19 顯示了 logrus 的所有三個版本現在是如何在 go.sum 文件中表示的。正如上面 Bryan 所解釋的,這是因爲傳遞性需求可能會影響其他模塊的選擇版本。

圖 6

圖 6 顯示了 Go 現在將使用 logrus 模塊哪個版本來構建項目中所有需要 logrus 模塊的依賴項的代碼。

使用 Latest Greatest 升級所有直接和間接依賴項

你可以用 ./...  所有升級選項,包括所有直接和間接依賴項,包括那些不需要構建項目的依賴項。

清單 20

$ go get -u -t -d -v all

go: downloading github.com/mattn/go-sqlite3 v1.11.0
go: extracting github.com/mattn/go-sqlite3 v1.11.0
go: finding github.com/bitly/go-hostpool latest
go: finding github.com/denisenkom/go-mssqldb latest
go: finding github.com/hailocab/go-hostpool latest
go: finding gopkg.in/bsm/ratelimit.v1 latest
go: finding github.com/google/jsonapi latest
go: finding golang.org/x/net latest
go: finding github.com/Bhinneka/golib latest
go: finding golang.org/x/crypto latest
go: finding gopkg.in/tomb.v1 latest
go: finding github.com/bmizerany/assert latest
go: finding github.com/erikstmartin/go-testdb latest
go: finding gopkg.in/check.v1 latest
go: finding golang.org/x/sys latest
go: finding github.com/golang-sql/civil latest

清單 20 顯示了現在爲項目找到、下載和提取了多少依賴項。

清單 21

Added to Module File
   cloud.google.com/go v0.49.0 // indirect
   github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 // indirect
   github.com/google/go-cmp v0.3.1 // indirect
   github.com/jinzhu/now v1.1.1 // indirect
   github.com/lib/pq v1.2.0 // indirect
   github.com/mattn/go-sqlite3 v2.0.1+incompatible // indirect
   github.com/onsi/ginkgo v1.10.3 // indirect
   github.com/onsi/gomega v1.7.1 // indirect
   github.com/stretchr/objx v0.2.0 // indirect
   google.golang.org/appengine v1.6.5 // indirect
   gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
   gopkg.in/yaml.v2 v2.2.7 // indirect

Removed from Module File
   github.com/golang/protobuf v1.3.2 // indirect

清單 21 顯示了 go.mod 文件的更改,添加了更多的模塊,刪除了一個模塊。

注意:如果你想 vendoring,go mod vendor 命令可以從 vendor 文件夾中取出測試文件。

作爲一般準則,在使用 go get for your projects 升級依賴項時,不要使用 all 選項或 -u 標誌。只使用你需要的模塊,並使用 MVS 算法來選擇這些模塊及其版本。必要時手動覆蓋特定的模塊版本。可以通過手動編輯 go.mod 文件來完成手動重寫,我將在以後的文章中向你展示。

重置依賴關係

如果在任何時候你對模塊和被選中的版本感到不舒服,你總是可以通過刪除模塊文件和運行 go mod tidy 來重置選擇。當項目還在開始階段,而且情況不穩定時,這是一個更好的選擇。一旦項目穩定併發布,我會猶豫是否重置依賴關係。正如我前面提到的,隨着時間的推移,模塊版本可能會被設置,並且您希望在長時間內使用持久的和可重複的構建。

清單 22

$ rm go.*
$ go mod init <module name>
$ go mod tidy

清單 22 顯示了可以運行的命令,以允許 MVS 從頭再次執行所有選擇。在寫這篇文章的整個過程中,我一直在這樣做,以便重新設置項目並提供文章的列表。

總結

在這篇文章中,我解釋了 MVS 語義,並展示了 Go 和 MVS 算法的實際應用示例。我還展示了一些 Go 命令,它們可以在你遇到困難或遇到未知問題時爲你提供信息。在向項目添加越來越多的依賴項時,可能會遇到一些邊緣情況。這是因爲 Go 生態系統已經有 10 年的歷史了,所有現有的項目都需要更多的時間才能達到模塊兼容。

在以後的文章中,我將討論在同一個項目中使用不同主要版本的依賴關係,以及如何手動檢索和鎖定依賴關係的特定版本。現在,我希望您能夠更多地信任模塊和 Go 工具,並且你能夠更清楚地瞭解 MVS 如何隨着時間的推移選擇版本。


via: https://www.ardanlabs.com/blog/2019/12/modules-03-minimal-version-selection.html

作者:William Kennedy[7] 譯者:polaris1119[8] 校對:polaris1119[9]

本文由 GCTT[10] 原創編譯,Go 中文網 [11] 榮譽推出,發佈在 Go 語言中文網公衆號,轉載請聯繫我們授權。

參考資料

[1]

寫作: https://research.swtch.com/vgo

[2]

討論: https://www.youtube.com/watch?v=F8nrpe0XWRg

[3]

這篇文章: https://research.swtch.com/vgo-principles

[4]

該文: https://research.swtch.com/vgo-principles

[5]

rethinkdb-go: https://github.com/rethinkdb/rethinkdb-go

[6]

golib: https://github.com/Bhinneka/golib

[7]

William Kennedy: https://www.ardanlabs.com/

[8]

polaris1119: https://github.com/polaris1119

[9]

polaris1119: https://github.com/polaris1119

[10]

GCTT: https://github.com/studygolang/GCTT

[11]

Go 中文網: https://studygolang.com/

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