关于Go Module的争吵

即将发布的Go 1.11将加入对go mod的支持,解决了go一直存在的依赖管理的短板。不过这篇文章并不是要介绍go module本身,而是打算说一下go团队和社区之间的冲突——你可以从中看到go team的做事风格,也可以当做是单纯的八卦。

先说一下背景。Go作为Google内部孵化的项目,其对的设计非常符合Google内部的使用场景:所有的源代码都在一个巨大的repo,依赖就是VCS里面的路径,构建的时候全部从最新源代码编译,最终只产出一个单一的可执行文件。然而并不是所有的公司都是像Google这样使用Go,Google之外的使用者们需要更好的依赖管理,主要有:

  1. 依赖版本的控制,以不受依赖模块代码修改的影响,产生稳定的可预测的构建结果
  2. 稳定的依赖路径,以在项目vcs地址改变或者fork vcs之后能够兼容

最早有gopkg.in/这样的方案,通过一次跳转指向真正的VCS地址,URL里不同的版本跳转到不同的分支。后来有了各种第三方的依赖管理工具,在vendor机制出现后,这些工具实现上把拉下来的依赖固化到vendor里。之后,社区人员在Go团队的认可下成立了包管理工具的委员会和工作组,组织开发了dep,作为——或者说他们以为作为——官方的依赖管理实现。

然而到了18年Russ Cox又开发了vgo的实验项目,这也引起了迷惑,Go出现了两个官方的依赖管理工具? vgo和dep是什么关系?随着vgo最终标准化并进入Go 1.11称为go module的实现,尘埃落定,dep所谓的官方只是社区开发者的一厢情愿,根正苗红的只有vgo。

故事是这样开始的。

vgo进入Go 1.11,Russ Cox 发了一个Twitter:

Russ Cox: 对于在Go 1.11里的Go Module的实现方式我感到非常兴奋。我上次瞅了一下最流行的大约1000个项目,93%的能够直接转换为Go Module。

Twitter发出来之后,Sam Boyer(dep的主要开发者),转推了一条(后面不放英文原文截图了,可以到文章后面的连接去看):

Sam Boyer: 最初的(旧项目到Go Module的)转换从来不是问题,问题是从不拿工资的贡献者们那里偷走工作成果。
我上周GoSF的演讲是关于这个的。现在视频找不到了,不过我截了个图。

Russ Cox回复了这条推:

Russ Cox: 我听了你的演讲。虽然我们谈了很长时间,但是明显你现在对于这一切,对于我还是感觉很受挫败,很沮丧。对此我真的很抱歉,我知道那是什么感受。


Sam Boyer: 你能这样说我很感激。你肯定也很不容易,对于这件事造成的过分的压力我也很抱歉。
但是这并不是你我之间的事情,我之所以在演讲中提到你,是因为你是后来社区文化和机制争论的源头。

另一个Go社区的重要贡献者,Peter Bourgon说:

Peter Bourgon: 因为私下认识事件中的一些人,我承认我是有偏向的。不过作为一个外人看到这个事故现场,我可能以后对Rust兴趣更多一些了。为什么Go团队领导层总是拒绝从其他现代语言(的做法)中学习呢?


Russ Cox: Go并不是要满足所有人的所有需求。如果你喜欢Rust的方式,就去用Rust吧。我也欣赏Rust,事实上我花了很多时间研究Rust,Swift中的好的地方,但是适合一个语言的未必适合其他语言。


Peter Bourgon: 是的。但是我想不到原因为什么go的依赖管理没有从其他的依赖管理中学习到任何东西。我从局外人的角度来看,Go歇斯底里的尝试不去达成任何其他人已经达成的共识,我不明白为什么要这样做。
并不仅仅是你的问题。我参与了dep开始之前的讨论,在之前还一直在关注glide。我不知道为什么go的依赖管理社区就是不从其他(包管理)已经遇到的问题中学习。
作为一个已经在提供开箱即用的工具方面证明了自己的语言和社区,我非常困惑为什么这么关键的一个部分(指包管理)几乎是刻意的设计得这么挫。

好吧,Peter Bourgon其实有点歪楼了。

Russ Cox:我们是一个小团队,还有其他优先的事情要做。确实我们在这个领域晚了几年,但是事情正在往好的方面发展,几年之后再来看看我们做成什么样子。

Matt Farina(Glide作者)跳出来了

你说你们是一个小团队,是因为你们把Go当做Google下面团队的一个产品,Google这个团队是小的。如果你们创建一个社区,借助其他人的力量,那就是一个很大的团队。比如Rust的依赖管理团队就有不是Mozilla的人。

围攻之下Russ Cox开启了疯狂发推模式,连发N条Twitter阐述前因后果。

Russ Cox:
非常有道理的批评。我们并没有处理好依赖管理的社区进程。Go的核心团队没有今早和足够的参与这个进程,没有能够引导平滑的落地。作为Go的技术负责人,这是我的责任,我为此道歉。

自从一开始写下"go get",Go核心团队就希望社区能够接手依赖管理方面的事情。Keith Rarick的goven,之后是godep, 还有Matt Butcher'的glide, 已经Dave Cheney的gb,都是达到最终方案中的一步。

2015年春天Jason Buberel调研了这些方案,试图寻找统一各种方案的方法。他收集的信息说明了这些方案的主要问题缺乏在go工具链中的支持,也就是我们现在的vendor机制。
Jason收集的证据让我和Rob Pike相信我们需要增加vendor机制,我们在1.5中作为实验特性添加,在1.6中正式发布。
Daniel Theophanes领导编写了vendor.json的格式标准,以便不同的依赖管理工具能够互相交互。我把vendor作为社区的成功案例写在了go blog中。但是我们没有统一的语言来描述go的依赖关系。

2016年夏天,Gophercon上有有个go包管理的讨论,最终创建了工作小组和顾问委员会。我那时候正在休陪产假,Andrew Gerrand代表Go团队参与。

到11月份的时候,Andrew要去做其他的事情了,我开始参与。工作组一开始就要马上开发一个可使用的单独工具,我想要确保总体的设计能够是的最终良好的集成到go命令中。

2016年12月,我开始给Sam写邮件讨论设计。我希望把版本管理作为go build的一部分,以免用户需要额外的流程。我问他是否这样代价太高。我还写了一个Version SAT问题的文章。
我们还讨论了依赖同一个package多个版本造成的冲突问题,Sam高度评价了cargo(Rust的包管理工具)中的实践。
我们还讨论了和go工具链的集成需要go compile和build的支持,包括改变GOPATH和vendor的语义。
我说虽然最终集成到go中的会和现在的不一样,dep依然有必要继续开发,提供更多的经验。Sam看起来是同意的。

2017年1月,dep要发布时名字改成了hoard,在讨论中我再次提到dep不会简单的集成到go变成go dep或者go hoard。

2017年三月,Sam发邮件给golang-nuts邮件组,带了一个merging into go toolchain的链接。这个roadmap看起来就是要直接把dep 放到go的软件包里。
我在回复中重申了需要修改才能把dep集成到go toolchain,以及对Sam把dep当成了其本应的角色更官方的解决方案的顾虑。
Sam的回复中使用了"official experiment"这个词,我现在依然觉得这个词很合适。一个experiment是用来验证一个假说,获得关于某些东西更多的知识。

2017年6月,我和包管理的工作组分享了一个文档,关于对go工具集成的设想。Sam和其他人帮助我发现了其中的一些缺陷。我现在把它发布在这里
几天后我和工作组的人见面讨论这个设计。因为Andrew缺席所以我记了笔记给他。我现在把它发布在这里

我们在GopherCon contributor summit讨论了dep。Matt Farina说我当时说如果我自己去搞能做的更好,我确认我没有(说这句话)。印象里我说的是dep需要修改才能集成到go命令。
讨论的一个主题是关于dep的未来。我再次强调(包管理工作组的)进程是为了更好的理解我们面临的问题,以及最终在go命令中提供平滑的集成。

我认识到集成到go命令需要相当的工作量来修改go命令内部的一些东西,在2017年中期我花了一些时间来实现build cache,在go1.10中发布。build cache让GOPATH/pkg过时了,在版本化的依赖中也不需要这个。
GopherCon之后我休假了,然后又去另外一个项目帮忙,之后回来完成了build cache。build cache能用之后,我就能验证多版本的依赖问题了。这时已经是2017年11月。

11月晚些时候我给工作组发了一份草稿,然后和他们见面了解他们的想法。我那时候还没有写代码,我仍在试图搞明白和go集成应该怎么做。
草稿结尾建议dep支持semantic import versioning。Sam and Peter 争论说修改已经太晚了,dep不做修改,应该集成为go dep。
我想要发表import versioning 的草稿,公开讨论。Sam让我等等,别破坏了他的假期。我同意了。12月开始,Sam和我每周碰两次,讨论go集成的问题。

既然dep不愿意支持import versioning,我直接修改了go命令来验证这个想法。我发现了import versioning和minimal version selection最佳结合的方式,并且实现了它。

2018年1月初我有了一个想要公开讨论的设计,但还没有可以验证好坏的实现。但是Sam 2月3号在FOSDEM有一个关于dep的演讲,我不想事情复杂化,所以我和Sam持续沟通但是没有发布。
2月中旬我完成了可以工作的demo,并且确信设计没有什么大的问题。Sam的FOSDEM演讲也结束了,所以我发布了我的设计和原型实现。炸了。

剩下的事情大家都知道了。设计修正了几个月之后,成为正式的 proposal,被接受,然后实现,然后merge进主干,在1.11中preview。

最初的go get已经有10年了,我们替换了它。我们的目标是创建一个能够为go用户继续服务十年的工具,尤其是在代码规模和开发团队都越来越大的情况下。

Dep是个成功的实验。它验证了在Bundler 和 Cargo中使用的"tag versions, record Boolean constraints, solve with SAT"方式,我也从dep中学到了很多:
(列了很多条从dep学到的东西,这里省略了,感兴趣的自己去看Twitter上的原文)

为什么Go团队说go要支持版本(指sementic import versionging)呢?首先,我们创建了Go,我设计了Go命令。我们要保证的最重要的一件事是一致的愿景,让Go成为Go。愿景的一部分是让开发large-scale软件更容易,包括internet-scale的开源项目。虽然现在说什么方式是最好的还太早,但是我们可以从其他的系统中学习他们不scale的原因,并且避免掉。

无论或大或小,我们有个一修改go的流程:提Proposal。Go核心团队帮助支持重要的点,以符合Go的设计和愿景。我们引导社区的讨论去达成一致。
并不是任何时候社区都能达成一致,这时候Go核心团队来做决定。原则上来说我是最终决定者,实际上Go团队内部会长时间来讨论达成内部的一致。
而Dep从来没有一个Proposal。而Go Module Proposal,受到了社区广泛的欢迎,虽然Sam和一小撮其他人不是这样。我把决定的权利留给其他Proposal的reviewer,他们考虑其中的问题,最终接受了我的Proposal(好吧,谁敢不接受呢)。

Go Module的设计不是完美的,但是他解决了上述的问题,为下一个时代打下了良好基础。我们非常愿意和任何愿意帮助我们的人去改进它。

回顾过去,就像我一开始所说的,我们Go团队没能处理好社区进程,我为此道歉。我们应该持续参与和修正路线,以能够集成到go工具链。

无论如何,我确实钦佩Rust团队的社区参与。为了成功,他们明白关键点并且和社区贡献者全力投入。而在这个进程中,太长的时间我没有做到。

我们让人难以接受的放弃了dep,让Go module看起来是一个巨大的路线修正。整个社区知道结果后非常的震惊,因为我们之前只和包管理工作组的人沟通到了。
我以为我只需要专注在技术细节上面,让工作组的人和社区交流,结果导致了整个社区都认为dep是官方的实现,虽然在我和工作组的沟通中明确表明了不是这样。
回想起来我犯的最大的错误,是没有更广泛和公开的讨论dep的问题,我想让工作组对外用一个声音说话。
看起来我只是和Sam以及工作组的其他人讨论dep和dep集成到go中需要做的事情,但这些都被Sam忽略了,结果是dep无法作为合适的实现集成到go中。
通过各种途径,dep以go包管理的最终方案的名义出现在了大家面前,即使我们早在2016年12月就告诉Sam dep只是过程中的一步,并且是最终会被取代的。
如果公开的讨论,大家的预期可能会不一样。也许还会让dep的设计变得更容易被go吸收和集成。

再次,我为在此期间所犯的所有错误道歉。我从中学到了很多,成功的社区合作需要做什么,不需要做什么。希望后面的进程能够更好的运作。

太长了,简单的总结一下:我有管理责任,我道歉。dep一开始就不是作为官方实现,是个实验,你们想多了。我想要sementic import versioning,dep不支持,所以没法把dep集成到go中。现在我搞了go module Proposal和原型实现(vgo),大家都说好。

下面围观的吃瓜群众纷纷表示你不需要道歉,你干的很好,项目干的比这个差的多的去了。

Matt Farina又发了几条Twitter回应:

首先感谢你公开的分享了这么多的想法。
我想你没有抓到我非常重要的一个点。在Gophercon的包管理工作组会议你说dep代码行太多了,go团队维护不了。这说明了一个问题,Go的问题最终都是Go团队决定的,其他人没有话语权。在你的回复中你说的都是你做的工作,不管过去的还是最近的。关注点都是你和Go团队的工作。导师-徒弟的关系怎么变成让社区参与主导,怎么考虑社区意见?

社区是作为主导者的一份子,还是单纯的产品使用者?依赖管理为什么就不能是一个独立的团队?一个在这个问题上富有经验并且聚焦这个问题的团队。工具和复杂度在膨胀,为什么不能交出一部分主导权给社区?其他的语言都是这么做的。

还有vgo,或者说go module有各种各样的问题,有些你对我承认过的。你快速构思出来的解决方案让事情变得更加复杂,并且对go用户不透明。
vgo和dep都有各自的问题。vgo的问题我和你认可的一些人谈过,他们对此也有顾虑。这些顾虑主要是风格方面的。

Go的问题主要有两个:
1. Go是一个Google提供的产品,其他人可以使用,但是没有话语权。小的高度控制的团队和社区是分离的,嫌隙正在越来越大。
2. 一个新的依赖管理模型(大概值得是import versioning),有不熟悉和没有验证过的问题

Peter Bourgon 在其自己的博客上发表文章"A response about dep and vgo"回应。只摘关键部分

Russ Cox狂发Twitter从他的视角描述了dep/vgo的故事。再去争论历史不是愉快的事情,但是叙述中有一些微妙和重要的事情,这对于我很重要,因为我经历过这些事情,我的记忆和这些叙述不一致。我认为它重要是因为Go团队花了很多时间和精力去推广社区文化,我认为这次事件,虽然有点乱有点复杂,是一个完美的证据证明他们还没有搞清楚正确的方式。有一个精确的历史记录非常重要,这样将来才能够尽量避免再出现类似的问题。

Russ一开始讲了一些历史,我跳过去直接到有争论的地方。

(引用了2017年7月其分享设计文档给工作组,以及在 GopherCon上讨论的Twitter)
当时我也在场,在contributor summit,主持了包管理工作组的多次会谈。我不记得Russ精确的字词了,但是我记得最终的结果。当会议进程过半的时候,Russ来了,他说他现在明白他需要自己去解决问题,他可能刚刚开始搞了或者已经搞了一半了,他希望能够给一些反馈。
明显的印象是,Russ并不打算——至少还没打算——参与到委员会中来,包括我们提供给他的研究成果,以及研究的原型实现dep。Russ有他自己的关于最终方案的理论,他更想自己验证这些理论。这是在第一次委员会和Russ有意义的沟通,当时我们正在为我们最终得到了核心团队的关注而兴奋,保持着谨慎的乐观,结果却是噩耗的开始。
Matt写到:
"我看到 Jess, Sam和其他人发出了一声叹息,低下了头。看起来就好像空气被从房间里抽干了。我非常的沮丧,以至于直接告诉你,这不是正确的对待把时间和努力投入这个事情上的人们的方式。"
我也清楚的记得这个时刻,就像Matt所写的一样。

(引用2017年11月draft提议dep支持semantic import versioning的Twitter)
这绝对是不正确的:委员会没有人想要把dep简单的搞成go dep。我们是提议增加一些子命令的集成,作为初稿以让讨论继续下去,但是我们从来没有坚持在最终产品上加上一个类似于ensure这样的子命令,从第一天开始所有人都清楚这个事情。
确实,我们从一开始就知道深度集成到go中是必要的。我们还知道要需要go命令的作者-Russ的同意才能提出Proposal,所以我们谨慎的把做决定推迟到了Russ参与会谈之后。事实上,我们避免在我们认为是go工具领域做决定,以让将来的集成变得容易。dep提出了很多的初稿,比如ensure,以在Russ缺席的时候工作能够继续下去。
当时关于semantic import versioning我们是有几次讨论,Russ没有能够说服委员会它是必要的,我们也没有一致的同意修改dep支持它。可能这就是他记得的东西。

(文章好长中间关于细节的撕逼跳过去了。。想了解的看原文吧)

(引用关于不可接受的放弃了dep那段Twitter)
这么说是不诚实的。路线的修正也震惊到了所有的包管理工作组人员,因为直到vgo的文章出来之前,我们每个人依然有希望和期望核心团队继续和我们一起工作,以让dep达到可以被接受的状态,而不是提议一个全新的东西。
我们有意谨慎的避免在和核心团队建立更正式的沟通渠道之前,提出一个Proposal。很多dep的开发工作实在(和核心团队)完全没有沟通的情况下进行的。我们——至少我自己——在vgo的文章出现之后,仍然希望Russ和Sam的技术冲突能够解决,并提出一个dep的Proposal。请记住:到那时位置,Russ只有两三次出现在我们的会议中,而这个会议已经进行两年了。
Russ的技术主张我们肯定没有忽视,我们广泛的争论了这些主张。他们只是没有说服Sam和委员会。与其优化那些主张让它们更有说服力,或是与委员会一起找到一个妥协点,Russ决定自己去实现自己的想法,抛开我们自己提出Proposal,并且直到完成之后才告诉我们。必须说明:作为Go的技术负责人,Russ当然有权利做所有的这些事情。但是这样一个项目反映了什么?

(这里再省略一段。。)

当dep委员会成立的时候,我竭尽所能的,让它以及它推进的工作,尽可能的可靠和无懈可击,以保护工作组的信用,工作组成员的声誉,以及产出的产品的有效性。为了代替自核心团队的指导,甚至基本的交流,我们决定:
- 从社区的领袖和专家中征集成员
- 成立一个次级的顾问组支持所有相关的项目
- 花费了半年时间收集用户反馈和进行领域内的研究
- 详细Review了所有其他有意义的包管理工具
- 步调一致的进行设计,目标在所有的主要决策上达成一致
- 所有的一切,都煞费苦心的,公开的记录文档

我们做了所有的这一切,因为我们想要成为社区能够更近一步,解决一直被核心团队忽视的问题的典范。我无法找到比我们比我们所做过的事情更好的事情了。但是结果,这些努力就像我们从来没有做过任何事一样:核心团队最终不参与进我们所贡献的成果上面,而是坚持自己完成,本质上一个全新的重头开始的项目。

我想dep/vgo的结局很好的说明了,虽然Go领导层很乐意接受对issue和无争议的Proposal这样的贡献,但仍然在与大规模的自主自治的贡献者斗争。权利掌握在Google的核心开发团队里。

如果有一天我做关于这个最终失败的项目的演讲,我要在最后放一页幻灯片,上面有一个图,一艘船在岛上失事了,船长说: "你生命的意义就在于警告其他人"。我希望这个故事给别人一个警告:"如果你有兴趣对Go做出大的实质性的贡献,独立的勤奋工作不能补偿你那不是来自核心团队的设计"。

也许一些读者读到这里会说:“Perter,这不是很显然的事情吗”。可能是吧。(没能认识到是)我的错。

Addendum(作者看完在Reddit上的讨论添加的):
这次引发的讨论启发了我。我想我对于发生的事情,有了更好的全景的视角。我认为dep团队和核心团队各自有完全不同的对对话的理解,直到vgo文章的出现让量子态塌缩了。
Dep团队相信dep和之前出现的工具是不一样的,是一个经过研究和详细考虑,社区驱动的最终go依赖管理解决方案。核心团队认为dep和之前的工具本质上是同一类,是他们设计的最终的方案的前奏的一部分。
我相信dep团队很清楚他们的地位,能够从核心团队的表述中能够看出来是处于什么样的地位的。但是明显(dep团队成员)没有能够普遍的理解这个地位,直到为时已晚,直到已经太晚。唉,事后来看是很显然的事情,但是当时谁又能够看得透呢?


rsc的Twitter Storm

https://twitter.com/_rsc/status/1022588240501661696twitter.com

A response about dep and vgo

A response about dep and vgopeter.bourgon.org

Reddit上的讨论

r/golang - Peter Bourgon · A response about dep and vgowww.reddit.com图标

Hacker News上的讨论

https://news.ycombinator.com/item?id=17628311news.ycombinator.com

编辑于 2018-08-10

文章被以下专栏收录