柚子快報激活碼778899分享:golang中的循環(huán)依賴
柚子快報激活碼778899分享:golang中的循環(huán)依賴
作為 Golang 開發(fā)人員,您可能遇到過導(dǎo)入周期。Golang 不允許導(dǎo)入循環(huán)。如果 Go 檢測到代碼中的導(dǎo)入循環(huán),則會拋出編譯時錯誤。在這篇文章中,讓我們了解導(dǎo)入周期是如何發(fā)生的以及如何處理它們。
導(dǎo)入周期
假設(shè)我們有兩個包,p1并且p2。當(dāng) packagep1依賴于 packagep2且 package 又p2依賴于 packagep1時,就會形成依賴循環(huán)。或者它可能比這更復(fù)雜,例如。packagep2并不直接依賴于 package?p1,而是p2依賴于 package ,而 packagep3又依賴于p1,這又是一個循環(huán)。
讓我們通過一些示例代碼來理解它。
包 p1?:
package p1
import (
"fmt"
"import-cycle-example/p2"
)
type PP1 struct{}
func New() *PP1 {
return &PP1{}
}
func (p *PP1) HelloFromP1() {
fmt.Println("Hello from package p1")
}
包p2:
package p2
import (
"fmt"
"import-cycle-example/p1"
)
type PP2 struct{}
func New() *PP2 {
return &PP2{}
}
func (p *PP2) HelloFromP2() {
fmt.Println("Hello from package p2")
}
func (p *PP2) HelloFromP1Side() {
pp1 := p1.New()
pp1.HelloFromP1()
}
構(gòu)建時,編譯器返回錯誤:
imports import-cycle-example/p1
imports import-cycle-example/p2
imports import-cycle-example/p1: import cycle not allowed
導(dǎo)入周期是糟糕的設(shè)計
Go 高度關(guān)注更快的編譯時間而不是執(zhí)行速度(甚至愿意犧牲一些運行時性能來加快構(gòu)建速度)。Go 編譯器不會花費大量時間嘗試生成最高效的機器代碼,它更關(guān)心快速編譯大量代碼。
允許循環(huán)/循環(huán)依賴關(guān)系將顯著增加編譯時間,因為每次依賴關(guān)系之一發(fā)生更改時,整個依賴關(guān)系循環(huán)都需要重新編譯。它還增加了鏈接時間成本,并使獨立測試/重用事物變得困難(單元測試更困難,因為它們不能彼此隔離地進行測試)。循環(huán)依賴有時會導(dǎo)致無限遞歸。
循環(huán)依賴還可能導(dǎo)致內(nèi)存泄漏,因為每個對象都保留另一個對象,它們的引用計數(shù)永遠不會達到零,因此永遠不會成為收集和清理的候選者。
Robe Pike 在回復(fù) Golang 中允許導(dǎo)入周期的提案時表示,這是一個值得預(yù)先簡單化的領(lǐng)域。進口周期可能很方便,但其成本可能是災(zāi)難性的。他們應(yīng)該繼續(xù)被禁止。
調(diào)試導(dǎo)入周期
關(guān)于導(dǎo)入循環(huán)錯誤最糟糕的是,Golang 不會告訴您導(dǎo)致錯誤的源文件或部分代碼。因此,很難弄清楚代碼庫何時很大。您可能會想知道不同的文件/包來檢查問題到底出在哪里。為什么golang不顯示導(dǎo)致錯誤的原因?因為循環(huán)中不只有一個罪魁禍首源文件。
但它確實顯示了導(dǎo)致問題的軟件包。因此,您可以查看這些軟件包并解決問題。
要可視化項目中的依賴關(guān)系,您可以使用godepgraph,一個 Go 依賴關(guān)系圖可視化工具??梢酝ㄟ^運行以下命令來安裝:
go get github.com/kisielk/godepgraph
它以Graphviz點格式顯示圖形。如果您安裝了 graphviz 工具(您可以從此處下載),您可以通過將輸出管道傳輸?shù)?dot 來渲染它:
godepgraph -s import-cycle-example | dot -Tpng -o godepgraph.png
您可以在輸出 png 文件中看到導(dǎo)入周期:
除此之外,您還可以使用它go list來獲得一些見解(運行g(shù)o help list以獲取更多信息)。
go list -f '{\{join .DepsErrors "\n"\}}'
您可以提供導(dǎo)入路徑,也可以將當(dāng)前目錄留空。
處理進口周期
當(dāng)你遇到導(dǎo)入周期錯誤時,退后一步,思考一下項目組織。處理導(dǎo)入周期的最明顯且最常見的方法是通過接口實現(xiàn)。但有時你并不需要它。有時您可能不小心將包裹分成了幾個。檢查創(chuàng)建導(dǎo)入周期的包是否緊密耦合并且它們需要彼此才能工作,它們可能應(yīng)該合并到一個包中。在Golang中,包是一個編譯單元。如果兩個文件必須始終一起編譯,則它們必須位于同一個包中。
接口方式:
包通過導(dǎo)入 packagep1來使用包中的函數(shù)/變量。p2p2包p2可以從包中調(diào)用函數(shù)/變量,p1而無需導(dǎo)入包p1。所有需要傳遞的包p1實例都實現(xiàn)了 中定義的接口p2,這些實例將被視為包p2對象。
這就是 packagep2忽略 package 的存在p1并且導(dǎo)入周期被破壞的方式。
應(yīng)用上述步驟后,打包p2代碼:
package p2
import (
"fmt"
)
type pp1 interface {
HelloFromP1()
}
type PP2 struct {
PP1 pp1
}
func New(pp1 pp1) *PP2 {
return &PP2{
PP1: pp1,
}
}
func (p *PP2) HelloFromP2() {
fmt.Println("Hello from package p2")
}
func (p *PP2) HelloFromP1Side() {
p.PP1.HelloFromP1()
}
包p1代碼如下所示:
package p1
import (
"fmt"
"import-cycle-example/p2"
)
type PP1 struct{}
func New() *PP1 {
return &PP1{}
}
func (p *PP1) HelloFromP1() {
fmt.Println("Hello from package p1")
}
func (p *PP1) HelloFromP2Side() {
pp2 := p2.New(p)
pp2.HelloFromP2()
}
您可以使用main包中的此代碼進行測試。
package main
import (
"import-cycle-example/p1"
)
func main() {
pp1 := p1.PP1{}
pp1.HelloFromP2Side() // Prints: "Hello from package p2"
}
您可以在 GitHub 上的jogendra/import-cycle-example-go上找到完整的源代碼
使用接口打破循環(huán)的其他方法可以是將代碼提取到單獨的第三個包中,該包充當(dāng)兩個包之間的橋梁。但很多時候它會增加代碼重復(fù)。您可以采用這種方法,同時牢記您的代碼結(jié)構(gòu)。
“三通”進口鏈:包 p1 -> 包 m1 & 包 p2 -> 包 m1
丑陋的方式:
有趣的是,您可以通過使用go:linkname.?go:linkname是編譯器指令(用作//go:linkname localname [importpath.name])。此特殊指令不適用于其后面的 Go 代碼。相反,//go:linkname指令指示編譯器使用“importpath.name”作為源代碼中聲明為“l(fā)ocalname”的變量或函數(shù)的目標文件符號名稱。(定義來自golang.org,乍一看很難理解,看下面的源代碼鏈接,我嘗試用它解決導(dǎo)入循環(huán)。)
有許多 Go 標準包依賴于使用go:linkname.?有時您還可以使用它解決代碼中的導(dǎo)入周期問題,但您應(yīng)該避免使用它,因為它仍然是一種 hack,并且 Golang 團隊不推薦。
這里需要注意的是,Golang 標準包不是用來go:linkname避免導(dǎo)入周期的,而是用它來避免導(dǎo)出不應(yīng)該公開的 API。
這是我使用以下方法實現(xiàn)的解決方案的源代碼go:linkname:
->?jogendra/import-cycle-example-go -> golinkname
底線
當(dāng)代碼庫很大時,導(dǎo)入周期絕對是一件痛苦的事情。嘗試分層構(gòu)建應(yīng)用程序。較高層應(yīng)該導(dǎo)入較低層,但較低層不應(yīng)該導(dǎo)入較高層(它會產(chǎn)生循環(huán))。記住這一點,有時將緊密耦合的包合并到一個包中是比通過接口解決問題更好的解決方案。但對于更一般的情況,接口實現(xiàn)是打破導(dǎo)入周期的好方法。
柚子快報激活碼778899分享:golang中的循環(huán)依賴
相關(guān)文章
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。