Golang的包管理之道

1928038 8年前發布 | 39K 次閱讀 Go語言 Google Go/Golang開發

來自: http://www.infoq.com/cn/articles/golang-package-management

對于一門編程語言的開發者,類庫包管理是一項考核編程語言成熟度的重要指標之一,Golang 也不例外。筆者在日常使用Golang語言開發系統程序時發現,在 Golang 的世界里,存在著大量的技術實現討論和各種自制的解決方案。因為Golang官方并沒有推薦最佳的包管理方案,開發者在選擇心目中最優的包管理方案時總會耗費精力去選擇合適自己的方案。所以本文的目的就是想和大家一起,針對Golang 包管理的設計問題,一起探討Golang包管理問題出現的原因以及解決辦法,在詳細的對比探討之后,間接地體會出Golang語言的開發團隊對語言設計的深層設計哲學。

Go包管理的現狀和問題

目前主流的編程語言 Python、Ruby、Java、Php 等已經把包管理的流程設計的猶如行云流水般流暢,一般情況下開發者是不需要操心類庫包依賴管理以及升級、備份、團隊協作的。在 Golang 的世界里,尤其是在1.5之前,此類庫包管理的流程設計真的是“僅僅”能工作的狀態。筆者結合日常開發過程中遇到的問題,整理出 Golang 語言包管理的現狀如下:

  1. 網絡環境是一個瓶頸,尤其是遇到大量的依賴包下載時,下載過程就是讓開發者長時間等待的過程,直至無法忍受。此類問題困擾多了,國內的開發者做了一個異步 下載 Golang 包的鏡像服務 來嘗試解決它。但在日常工作中,這種間接的辦法并不能有效的解決此類問題。
  2. Golang 的第三方包是沒有中央庫統一管理的,所以不存在索引庫的概念。遇到需要的庫,一定要小心的檢查包的可用性。因為包管理并沒有全局的版本控制。當你在本地編譯成功之后分享給同事時并不能保障你的同事就能一次編譯成功。類庫版本不對的情況時常發生,以至于開發者不得不把依賴包直接加到應用代碼倉庫中。類庫小的幾十兆,大的上百兆,從開發者的角度來說,代碼干凈程度是決定一個程序是否優雅和品位的,但是加入例如幾百兆的依賴包實在是無奈之舉,此方法并沒解決問題。實際上理想中的包管理設計應該是可以自動應對包的依賴管理的,例如 python 的 pip,ruby 的 Bundler。
  3. Golang 作為云計算時代最流行的系統級編程語言,目前在全球開發者社區都受到熱烈的關注和大量的使用。業界不乏開發者推出自己的包管理解決辦法,混亂的包管理治理工作對于開發者來說,耗費了大量的精力。Golang 的開發組也是遲遲沒有給出統一的解決辦法。

當然,目前 Golang 到了1.5版本時代,官方開始引入包管理的設計,加了 vendor目錄來支持本地包管理依賴。這個方法目前還不是默認開放的,goimports 并不能直接使用。官方會在1.6版本開始正式啟用這個特性。為了在1.5環境下啟用這個特性,Golang 啟用了一個環境變量作為開關: GO15VENDOREXPERIMENT=1 ,1.6之后就會默認啟用不再使用此環境變量。

原因分析

筆者認為,Golang 語言的設計者都是多年經驗的世界級語言開發者,發明它也是為了谷歌內部替代 C++/C 的系統級語言,不可能沒有考慮包管理。所以 Golang 對包管理一定有自己的理解。筆者從一開始接觸Golang 時就發現,它真的引入的新的語言概念非常少。對于包的獲取,就是用 go get命令從遠程代碼庫(GitHub, Bitbucket, Google Code, Launchpad)拉取。這樣做的好處是,直接跳過了包管理中央庫的的約束,讓代碼的拉取直接基于版本控制庫,大家的協作管理都是基于這個版本依賴庫來互動。細體會下,發現這種設計的好處是去掉冗余,直接復用最基本的代碼基礎設施。Golang 這么干很大程度上減輕了開發者對包管理的復雜概念的理解負擔,設計的很巧妙。

當然,go tools 引入的 go get 命令,仍然過于簡單。對于現實過程中的開發者來說,仍然有其痛苦的地方。

  1. 缺乏明確顯示的版本。團隊開發容易導入不一樣的版本
  2. 第三方包沒有內容安全審計,很容易引入代碼 Bug
  3. 依賴的完整性無法校驗,程序編譯時無法保障百分百成功

Go開發組對于此類問題的建議是把外部依賴的代碼復制到你的 源碼庫中管理

包管理的問題,并不是一個單點問題。它涉及到程序的工程操作性。開發者需要的是可以在任何時間,任何地點和環境,可以反復的編譯出同樣的程序: ReproducibleBuild

  • 可以在特定的分支上重現一個 Bug
  • 使用 bisect 可以隔離出哪一次提交引入的 Bug

所以,官方推薦把第三方代碼引入自己的代碼庫仍然是一種折中的辦法:

  • 對于之前的 go get。我們如何升級依賴庫的版本。仍然需要第三方工具或者腳本來維護類庫,本身就是有點復雜。
  • 我們很難直接針對第三方庫的 Bug,貢獻代碼修復 Bug。所以,你復制的那一份代碼已經開始工作后,誰還敢動呢?更糟糕的是,如果這個第三方庫的開發者很活躍,代碼更新更快,如何升級我們的引用代碼呢?
  • 官方的辦法對于普通的程序問題不是很大,最多就是編譯時的依賴。但如果你寫的是一個給其他人使用的類庫,引入這個庫就會帶來麻煩了。你這個庫被多人引用,如何管理你這個庫的代碼依賴呢?難道還是一股腦的復制嗎?

幾種解法,利弊

由于官方對于包管理暫時沒有明確的指導意見,所以,作為社區驅動的一門語言,不缺乏各路優秀開發者推出的自己的最佳實踐工具:

到了1.5后Golang的Vendor目錄特性出來后,官方 Wiki 推薦了支持此特性的包管理工具如下:

  • Godep
  • Govendor
  • godm
  • vexp
  • gv
  • gvt - Recursively retrieve and vendor packages.
  • govend
  • Glide

根據筆者的實踐總結下來,對于國外的開發者,因為沒有“國家防火墻”的限制,帶寬也會非常充足。我推薦使用的工具是 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:</pre> 

對于國內開發者來說,最好是能一個一個包來管理。遇到網絡問題,可以通過國內鏡像下載。在這樣的情況之下gvt 就是一個不錯的選擇。它可以幫助我們把一個包以及依賴都徹底的拉到本地的代碼庫中,統一了團隊協作過程中編譯環境不一致的問題。

給一個例子參考:

$ gvt fetch github.com/fatih/color
2015/09/05 02:38:06 fetching recursive dependency github.com/mattn/go-isatty
2015/09/05 02:38:07 fetching recursive dependency github.com/shiena/ansicolor

$ tree -d . └── vendor └── github.com ├── fatih │ └── color ├── mattn │ └── go-isatty └── shiena └── ansicolor └── ansicolor

9 directories

$ cat > main.go package main import "github.com/fatih/color" func main() { color.Red("Hello, world!") }

$ export GO15VENDOREXPERIMENT=1 $ go build . $ ./hello Hello, world!

$ git add main.go vendor/ && git commit</pre>

未來

Golang 社區一直遵循“盡量簡單”的原則,從不多加一份可能的設計負擔給用戶,這也是我喜歡它的原因。對于管理依賴的處理,是 Go開發組 一直重視的技術點,它的重要性遠比“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 強調的是代碼的干凈度勝過代碼的重用。這是不一樣的編程哲學,還請大家且行且珍惜。

總結下官方對包管理依賴的建議如下:

  • 當你開源類庫時,請盡量的少用第三方庫,學會使用標準庫。發布的類庫,也請使用版本服務,類如 gopkg.in 來管理版本。
  • 對于程序的包管理,使用官方 推薦的工具 來管理。如果你有自己的想法,請直接對這些官方推薦的工具做貢獻,讓社區一起來共同解決這個問題。

作者

肖德時,北京數人科技有限公司CTO,負責云計算的研發及架構設計工作。關注領域包括Docker,Mesos集群, 云計算等領域。 肖德時之前為紅帽Engineering Service部門內部工具組Team Leader。

參考

感謝郭蕾對本文的審校。

給InfoQ中文站投稿或者參與內容翻譯工作,請郵件至editors@cn.infoq.com。也歡迎大家通過新浪微博(@InfoQ,@丁曉昀),微信(微信號: InfoQChina )關注我們。

</div>

 本文由用戶 1928038 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!