Golang的包管理之道
2016-02-15 18:13:00 來源:肖德時 評論:0 點擊:
對于一門編程語言的開發(fā)者,類庫包管理是一項考核編程語言成熟度的重要指標之一,Golang 也不例外。筆者在日常使用Golang語言開發(fā)系統(tǒng)程序時發(fā)現(xiàn),在 Golang 的世界里,存在著大量的技術實現(xiàn)討論和各種自制的解決方案。因為Golang官方并沒有推薦最佳的包管理方案,開發(fā)者在選擇心目中最優(yōu)的包管理方案時總會耗費精力去選擇合適自己的方案。所以本文的目的就是想和大家一起,針對Golang 包管理的設計問題,一起探討Golang包管理問題出現(xiàn)的原因以及解決辦法,在詳細的對比探討之后,間接地體會出Golang語言的開發(fā)團隊對語言設計的深層設計哲學。
Go包管理的現(xiàn)狀和問題
目前主流的編程語言 Python、Ruby、Java、Php 等已經(jīng)把包管理的流程設計的猶如行云流水般流暢,一般情況下開發(fā)者是不需要操心類庫包依賴管理以及升級、備份、團隊協(xié)作的。在 Golang 的世界里,尤其是在1.5之前,此類庫包管理的流程設計真的是“僅僅”能工作的狀態(tài)。筆者結合日常開發(fā)過程中遇到的問題,整理出 Golang 語言包管理的現(xiàn)狀如下:
- 網(wǎng)絡環(huán)境是一個瓶頸,尤其是遇到大量的依賴包下載時,下載過程就是讓開發(fā)者長時間等待的過程,直至無法忍受。此類問題困擾多了,國內的開發(fā)者做了一個異步下載 Golang 包的鏡像服務來嘗試解決它。但在日常工作中,這種間接的辦法并不能有效的解決此類問題。
- Golang 的第三方包是沒有中央庫統(tǒng)一管理的,所以不存在索引庫的概念。遇到需要的庫,一定要小心的檢查包的可用性。因為包管理并沒有全局的版本控制。當你在本地編譯成功之后分享給同事時并不能保障你的同事就能一次編譯成功。類庫版本不對的情況時常發(fā)生,以至于開發(fā)者不得不把依賴包直接加到應用代碼倉庫中。類庫小的幾十兆,大的上百兆,從開發(fā)者的角度來說,代碼干凈程度是決定一個程序是否優(yōu)雅和品位的,但是加入例如幾百兆的依賴包實在是無奈之舉,此方法并沒解決問題。實際上理想中的包管理設計應該是可以自動應對包的依賴管理的,例如 python 的 pip,ruby 的 Bundler。
- Golang 作為云計算時代最流行的系統(tǒng)級編程語言,目前在全球開發(fā)者社區(qū)都受到熱烈的關注和大量的使用。業(yè)界不乏開發(fā)者推出自己的包管理解決辦法,混亂的包管理治理工作對于開發(fā)者來說,耗費了大量的精力。Golang 的開發(fā)組也是遲遲沒有給出統(tǒng)一的解決辦法。
當然,目前 Golang 到了1.5版本時代,官方開始引入包管理的設計,加了 vendor目錄來支持本地包管理依賴。這個方法目前還不是默認開放的,goimports 并不能直接使用。官方會在1.6版本開始正式啟用這個特性。為了在1.5環(huán)境下啟用這個特性,Golang 啟用了一個環(huán)境變量作為開關:GO15VENDOREXPERIMENT=1 ,1.6之后就會默認啟用不再使用此環(huán)境變量。
原因分析
筆者認為,Golang 語言的設計者都是多年經(jīng)驗的世界級語言開發(fā)者,發(fā)明它也是為了谷歌內部替代 C++/C 的系統(tǒng)級語言,不可能沒有考慮包管理。所以 Golang 對包管理一定有自己的理解。筆者從一開始接觸Golang 時就發(fā)現(xiàn),它真的引入的新的語言概念非常少。對于包的獲取,就是用 go get命令從遠程代碼庫(GitHub, Bitbucket, Google Code, Launchpad)拉取。這樣做的好處是,直接跳過了包管理中央庫的的約束,讓代碼的拉取直接基于版本控制庫,大家的協(xié)作管理都是基于這個版本依賴庫來互動。細體會下,發(fā)現(xiàn)這種設計的好處是去掉冗余,直接復用最基本的代碼基礎設施。Golang 這么干很大程度上減輕了開發(fā)者對包管理的復雜概念的理解負擔,設計的很巧妙。
當然,go tools 引入的 go get 命令,仍然過于簡單。對于現(xiàn)實過程中的開發(fā)者來說,仍然有其痛苦的地方。
- 缺乏明確顯示的版本。團隊開發(fā)容易導入不一樣的版本
- 第三方包沒有內容安全審計,很容易引入代碼 Bug
- 依賴的完整性無法校驗,程序編譯時無法保障百分百成功
Go開發(fā)組對于此類問題的建議是把外部依賴的代碼復制到你的源碼庫中管理。
包管理的問題,并不是一個單點問題。它涉及到程序的工程操作性。開發(fā)者需要的是可以在任何時間,任何地點和環(huán)境,可以反復的編譯出同樣的程序:ReproducibleBuild
- 可以在特定的分支上重現(xiàn)一個 Bug
- 使用 bisect 可以隔離出哪一次提交引入的 Bug
所以,官方推薦把第三方代碼引入自己的代碼庫仍然是一種折中的辦法:
- 對于之前的 go get。我們如何升級依賴庫的版本。仍然需要第三方工具或者腳本來維護類庫,本身就是有點復雜。
- 我們很難直接針對第三方庫的 Bug,貢獻代碼修復 Bug。所以,你復制的那一份代碼已經(jīng)開始工作后,誰還敢動呢?更糟糕的是,如果這個第三方庫的開發(fā)者很活躍,代碼更新更快,如何升級我們的引用代碼呢?
- 官方的辦法對于普通的程序問題不是很大,最多就是編譯時的依賴。但如果你寫的是一個給其他人使用的類庫,引入這個庫就會帶來麻煩了。你這個庫被多人引用,如何管理你這個庫的代碼依賴呢?難道還是一股腦的復制嗎?
幾種解法,利弊
由于官方對于包管理暫時沒有明確的指導意見,所以,作為社區(qū)驅動的一門語言,不缺乏各路優(yōu)秀開發(fā)者推出的自己的最佳實踐工具:
- https://github.com/tools/godep
- https://github.com/gpmgo/gopm
- https://github.com/pote/gpm
- https://github.com/nitrous-io/goop
- https://github.com/alouche/rodent
- https://github.com/jingweno/nut
- https://github.com/niemeyer/gopkg
- https://github.com/mjibson/party
- https://github.com/kardianos/vendor
- https://github.com/kisielk/vendorize
- https://github.com/mattn/gom
- https://github.com/dkulchenko/bunch
- https://github.com/skelterjohn/wgo
- https://github.com/Masterminds/glide
- https://github.com/robfig/glock
- https://bitbucket.org/vegansk/gobs
- https://launchpad.net/godeps
- https://github.com/d2fn/gopack
- https://github.com/laher/gopin
- https://github.com/LyricalSecurity/gigo
- https://github.com/VividCortex/johnny-deps
到了1.5后Golang的Vendor目錄特性出來后,官方 Wiki 推薦了支持此特性的包管理工具如下:
- Godep
- Govendor
- godm
- vexp
- gv
- gvt - Recursively retrieve and vendor packages.
- govend
- Glide
根據(jù)筆者的實踐總結下來,對于國外的開發(fā)者,因為沒有“國家防火墻”的限制,帶寬也會非常充足。我推薦使用的工具是 Glide,推薦原因是設計簡潔,符合 Golang 的一貫風格。
給一個glide 的配置文件例子參考:
package: main import: - package: github.com/coreos/go-etcd ref: cc90c7b091275e606ad0ca7102a23fb2072f3f5e subpackages: - etcd - package: github.com/docker/distribution ref: 9038e48c3b982f8e82281ea486f078a73731ac4e - package: github.com/mailgun/log ref: 44874009257d4d47ba9806f1b7f72a32a015e4d8 - package: github.com/mailgun/oxy ref: 547c334d658398c05b346c0b79d8f47ba2e1473b subpackages: - cbreaker - forward - memmetrics - roundrobin - utils - package: github.com/hashicorp/consul ref: de080672fee9e6104572eeea89eccdca135bb918 subpackages:
對于國內開發(fā)者來說,最好是能一個一個包來管理。遇到網(wǎng)絡問題,可以通過國內鏡像下載。在這樣的情況之下gvt 就是一個不錯的選擇。它可以幫助我們把一個包以及依賴都徹底的拉到本地的代碼庫中,統(tǒng)一了團隊協(xié)作過程中編譯環(huán)境不一致的問題。
給一個例子參考:
$ gvt fetch github.com/fatih/color2015/09/05 02:38:06 fetching recursive dependency github.com/mattn/go-isatty2015/09/05 02:38:07 fetching recursive dependency github.com/shiena/ansicolor$ tree -d.└── vendor └── github.com ├── fatih │ └── color ├── mattn │ └── go-isatty └── shiena └── ansicolor └── ansicolor9 directories$ cat > main.gopackage mainimport "github.com/fatih/color"func main() { color.Red("Hello, world!")}$ export GO15VENDOREXPERIMENT=1$ go build .$ ./helloHello, world!$ git add main.go vendor/ && git commit
未來
Golang 社區(qū)一直遵循“盡量簡單”的原則,從不多加一份可能的設計負擔給用戶,這也是我喜歡它的原因。對于管理依賴的處理,是 Go開發(fā)組 一直重視的技術點,它的重要性遠比“DRY”原則還過之:
“Through the design of the standard library, great effort was spent on controlling dependencies. It can be better to copy a little code than to pull in a big library for one function. Dependency hygiene trumps code reuse.” - Go at Google
Go Team 強調的是代碼的干凈度勝過代碼的重用。這是不一樣的編程哲學,還請大家且行且珍惜。
總結下官方對包管理依賴的建議如下:
- 當你開源類庫時,請盡量的少用第三方庫,學會使用標準庫。發(fā)布的類庫,也請使用版本服務,類如gopkg.in來管理版本。
- 對于程序的包管理,使用官方推薦的工具來管理。如果你有自己的想法,請直接對這些官方推薦的工具做貢獻,讓社區(qū)一起來共同解決這個問題。
作者
肖德時,北京數(shù)人科技有限公司CTO,負責云計算的研發(fā)及架構設計工作。關注領域包括Docker,Mesos集群, 云計算等領域。 肖德時之前為紅帽Engineering Service部門內部工具組Team Leader。
參考
- https://nathany.com/go-packages/
- Manage Dependencies With Godep
- Go 1.5 Vendor Experiment
- https://nathany.com/go-packages/
感謝郭蕾對本文的審校。
給InfoQ中文站投稿或者參與內容翻譯工作,請郵件至editors@cn.infoq.com。也歡迎大家通過新浪微博(@InfoQ,@丁曉昀),微信(微信號:InfoQChina)關注我們。
相關熱詞搜索:golang package management 架構 & 設計 語言 & 開發(fā) 包管理器 Java Golang 語言&開發(fā) 語言設計 語言
