AICon全球人工智能与机器学习技术大会周四开幕,点击查看完整日程>> 了解详情
写点什么

Golang 的包管理之道

  • 2016 年 2 月 14 日
  • 本文字数:4449 字

    阅读完需:约 15 分钟

对于一门编程语言的开发者,类库包管理是一项考核编程语言成熟度的重要指标之一,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:

对于国内开发者来说,最好是能一个一个包来管理。遇到网络问题,可以通过国内镜像下载。在这样的情况之下 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

未来

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 )关注我们。

2016 年 2 月 14 日 00:5819805

评论

发布
暂无评论
  • 了解移动 App 的持续交付生命周期

    今天我主要和你分享了移动App的持续交付生命周期的几个主要部分,包括代码及依赖管理、项目信息管理、静态代码检查、构建管理、发布管理、运营管理,以及热修复。

    2018 年 9 月 13 日

  • Go 1.5 编译器与运行时环境由 Go 语言自身编写

    Go语言1.5版本拥有一个用Go语言自身编写的完整工具链,一个更快的垃圾回收器和在每一个可用的CPU上运行的goroutine。

  • 知乎 iOS 客户端工程化工具 Venom

    本文介绍Mac App开发工具Venom。

  • Go 包管理的前世今生

    说实话,Golang对一个新人真的挺不友善的,因为一上手要了解的概念。你看人家Java,上来一个项目mvn install一下就完事了,赶紧利落。但是Golang就麻烦了,你得先了解什么是GOPATH。我当年刚接触Golang真正开始做项目的时候,只知道按要求配置环境变量,对GOPATH真正理解可能都是好几个月以后的事情了。说白了,还是因为懒。真正做项目的人,有多少有耐心砍柴磨刀,出现一个东西就研究半天啊,我们只是想要Copy-Paste而已。

  • 用 Go 写一个轻量级的 ssh 批量操作工具

    用 Go 写一个轻量级的 ssh 批量操作工具

    2021 年 3 月 4 日

  • 快速构建持续交付系统(四):Ansible 解决自动部署问题

    在今天这篇文章中,我主要基于Ansible系统的能力,和你分享了搭建一套部署系统的过程。

    2018 年 9 月 27 日

  • 小议 Java 语言

    很多新人入门会要求我推荐编程语言,Java 属于我推荐的语言之一,因为 Java 标准、规范,是面向对象编程的代表,在学习其他编程语言的时候还可以参考互通。

    2018 年 1 月 12 日

  • 一个 PHPer 的 Golang 之路

    我是一个外表谦让,内心狂热,外表斯文,内心贪玩的一个普通人。我的职业是程序员,是一个golang语言爱好者,一半是因为golang好用,一半是因为其他语言学不好。我是从phper转为gopher的,写php的时候我认识了互联网软件,写go的时候感觉自己终于在编程。

  • 手把手教你依赖管理

    当项目越来越庞大,你会依赖越来越多的第三方库,那怎么管理这些依赖呢?

    2018 年 7 月 14 日

  • Rust 1.5 发布:引入 cargo install,提升了编译性能

    Rust是一门系统编程语言,它的三个主要目标是:安全、速度和并发性。最近,该语言发布了1.5.0稳定版,该版本有700多个变化,大部分是bug修复,其中最主要的变动就是引入了cargo install——一个新的在本地系统上安装Cargo应用程序包的子命令,进一步简化了Rust应用程序的分发。

  • Golang 中依赖管理的灾难

    依赖管理在软件工程中一直是一个比较核心的话题。一个人的力量是有限的,在较大规模的软件开发中,你总是需要与别人协作编写代码,或是充分借助开源世界的力量。软件在它的有效生命周期内是一直在迭代的,那么在你依赖的这些"外部"代码库演进的过程中,如何保证自己的代码或工程是可以在任何时间任何地点重复编译重复部署的,而不会出现满屏的红色编译错误告警,甚至是莫名其妙的运行时崩溃? 在初步了解了Golang中是如何管理依赖后,我确实有些失望。自然的,Golang官方团队也早已意识到语言內建的依赖管理机制不够理想,需要有一个优雅而统一的解决方案。因此,如果你打算建立一个包含外部依赖的Golang工程,不妨先参考下官方建议。 那么,让我们来看一看Golang的依赖管理问题到底是怎么回事吧。 tag: 开发、依赖管理 node-id:golang-dependencyManagement-disaster

  • Go 2 提上日程,官方团队呼吁社区给新特性提案提交反馈

    昨天,Go语言作者之一Robert Griesemer在官方博客发文称,Go 2已经选择出备选新特性提案,进入提案反馈阶段,他呼吁社区积极参与进来,和官方团队一起改进Go语言设计。

  • Grumpy:Google 用 Go 开发的 Python 运行时

    Google 运行着大量的 Python 代码,youtube.com 的前端服务器是 Python 写的,YouTube 的 API 主要也是 Python 写的。到了 YouTube 的量级,性能非常重要。YouTube 的前端主要依赖 CPython 2.7,所以 Google 花费了大量精力来优化运行时,并调整应用,以获得最好的性能。 备注:末尾的广告还麻烦去掉哈

  • 解读 2016 之 Golang 篇:极速提升,逐步超越

    Go语言已经7岁了!今年8月,Go 1.7如期发布。撰写本稿时,Go 1.8的测试版也出来了。我们正在热切盼望着明年2月的Go 1.8正式版。

  • 推荐几本 Go 相关书籍

    首先推荐《Go 程序设计语言》这本书:

    2021 年 3 月 23 日

  • Go 1.5 正式发布:实现自举、引入并发垃圾收集器

    在经历了几个Beta版本和一个rc版本之后,Go 1.5终于正式发布了。这是一个很重要的版本,实现方面有很多变化。因为这个版本仍然遵循Go 1兼容性承诺,开发团队预计,所有的Go程序应该都可以和以前一样编译和运行。

  • App 如何通过注入动态库的方式实现极速编译调试?

    今天这篇文章,我和你详细分享了动态库链接器的一个非常实用的应用场景:如何使用动态库加载方式进行极速调试。由此我们可以看出,类似链接器这样的底层知识是非常重要的。

    2019 年 3 月 23 日

  • 环境准备:如何安装和配置一个基本的 Go 开发环境?

    今天,我来手把手带你配置好一个 Go 的开发环境,供你以后开发、编译用。

    2021 年 5 月 26 日

  • 用 Go 语言进行编程的利与弊

    Go 语言有多火爆?国外如 Google、AWS、Cloudflare、CoreOS 等,国内如七牛、阿里等都已经开始大规模使用 Go 语言开发其云计算相关产品。跟着世界级巨人的脚步应该不至于走错方向。今天我们翻译并分享 Samuel Jones 大佬分享的用 Go 语言进行编程的利与弊,以飨读者。

  • Go Mysql Driver 集成 Seata-Golang 解决分布式事务问题

    2020年4月,我们开始尝试实现go语言的分布式事务框架Seata-Golang。众所周知,Seata AT模式以无业务代码侵入的特点被广大开发者推崇。

    2021 年 3 月 25 日

发现更多内容

Rust从0到1-代码组织-模块

rust modules 模块

合约跟单系统搭建,合约一键跟单app

13823153121

Golang Test

escray

Go 学习 极客时间 4月日更

这份阿里P8技术专家整理的《一面到底》Java岗,GitHub已标星79k

Java架构之路

Java 程序员 架构 面试 编程语言

4种语义分割数据集Cityscapes上SOTA方法总结

华为云开发者社区

语义分割 OCR 数据集Cityscapes HRNet SegFix

阿里巴巴的“双11”高并发秒杀终极版教程!(Java语言设计)

Java 编程 程序员 架构

理性看待区块链+大宗商品

Geek_987812

区块链

真的香!Github一夜爆火被各大厂要求直接下架的面试题库也太全了

Java架构之路

Java 程序员 架构 面试 编程语言

GitHub开源:4行代码实现《黑客帝国》数字雨特效

不脱发的程序猿

GitHub 开源 程序人生 4月日更 黑客帝国

加密原理详解:对称式加密VS非对称式加密

Java架构师迁哥

饿了么EMonitor演进史

阿里巴巴中间件

可观测性 饿了么 emonitor etrace

全网最全 ECMAScript 攻略

清秋

JavaScript ecmascript 前端 ES6 Ecma

暴涨暴跌的牛市,普通人怎么和平发育?

Geek_987812

区块链

开发环境上云,打造五星级开发体验

CODING DevOps

Kubernetes 云原生 CODING Nocalhost

开发知识 | 即时通讯是怎么做到的?

APICloud

前端 即时通讯 APP开发 小程序制作 开发技巧

万字精华:好好巩固你的Nginx知识体系

学Java关注我

Java 编程 架构 程序人生 计算机

破解class文件的第一步:深入理解JAVA Class文件

华为云开发者社区

Java JVM 索引 class文件

推荐5个4K视频下载网站 (百万优质资源)

科技猫

网站 分享 视频 经验 资源分享

一周信创舆情观察(4.12~4.18)

统小信uos

悲观锁与乐观锁的实现(详情图解)

Java架构师迁哥

Android组件化和插件化开发

寻找生命中的美好

android 组件化 插件化

阿里“秘密团队”整理出来的一份Java面试复盘手册!全面复盘在望

Java架构之路

Java 程序员 架构 面试 编程语言

跳出CRUD!阿里大牛熬夜整理71W字性能优化全解究竟有什么魅力?

程序员小毕

Java 程序员 架构 面试 性能优化

走完线上 BUG 定位最后一公里

阿里巴巴中间件

可观测性 bug bug修复

2021年处置非法集资部际联席会议:密切关注打着区块链、虚拟货币等旗号的新型风险

Geek_987812

线程的故事:我的3位母亲成就了优秀的我!

王磊

Java 线程 多线程

腾讯二面:MySQL的半同步是什么?

程序员小毕

Java MySQL 程序员 腾讯 面试

插件化库VirtualAPK详解

寻找生命中的美好

android 插件化 VirtualAPK

总是记不住java的IO流用法?用N个问题教你掌握java IO流

华为云开发者社区

Java 字符串 IO流 字节输入流 字符流

redis常见应用场景

Sakura

4月日更

架构实战营模块二作业

刁寿钧

架构实战营

数据cool谈(第2期)寻找下一代企业级数据库

数据cool谈(第2期)寻找下一代企业级数据库

Golang的包管理之道-InfoQ