【译文】Go语言设计:我们做对了什么,做错了什么

这是我在悉尼 GopherConAU 会议上的闭幕演讲(视频),演讲时间是 2023 年 11 月 10 日,即 Go 作为开源项目发布 14 周年纪念日。文中穿插了演讲中使用的幻灯片。

我们做对了什么,我们做错了什么

演讲

你好

首先,请允许我感谢凯蒂和切威,让我有幸为大会致闭幕词。很抱歉我照本宣科,但我想把话说清楚。

2009 年 11 月 10 日

今天是 2023 年 11 月 10 日,是 Go 作为开源项目发布 14 周年纪念日。

那天,加利福尼亚时间下午 3 点(如果没记错的话),Ken Thompson、Robert Griesemer、Russ Cox、Ian Taylor、Adam Langley、Jini Kim 和我满怀期待地看着网站上线,全世界都知道了我们一直在做什么。

十四年后的今天,有很多事情值得我们回顾。今天,我想借此机会谈谈从那天起学到的一些更重要的经验教训。即使是最成功的项目,经过反思,也会有一些本可以做得更好的地方。当然,也有事后看来是成功关键的地方。

首先我必须声明,我只是代表我自己,而不是 Go 团队,也不是 Google。 无论过去还是现在,Go 都是一个专注的团队和一个庞大的社区付出的巨大努力,所以如果你同意我说的任何话,请感谢他们。如果你不同意,可以责怪我,但请不要说出来😃。

鉴于本讲座的题目,许多人可能会认为我会分析语言中的好坏。我当然会分析一些,但除此之外,我还会分析更多,原因有以下几点。

首先,编程语言的好坏在很大程度上是一个见仁见智的问题,而不是一个事实,尽管很多人对 Go 或其他语言最微不足道的特性都争论不休。

此外,关于换行的位置、nil 如何工作、导出使用大写字母、垃圾回收、错误处理等问题,已经有很多讨论。 当然,我们还有很多话要说,但已经说过的就不多了。

但我要谈的不仅仅是语言,真正的原因是这并不是整个项目的目的。我们最初的目标不是创造一种新的编程语言,而是创造一种更好的软件编写方式。我们使用的语言存在问题–无论使用哪种语言,每个人都会遇到这样的问题,但我们遇到的根本问题并不集中在这些语言的功能上,而是谷歌为使用这些语言构建软件而创建的流程。

一种新语言的诞生为探索其他想法提供了一条新路,但它只是一个助推器,而不是真正的关键。如果不是花了 45 分钟来构建我当时正在研究的二进制文件,Go 就不会出现,但这 45 分钟并不是因为编译器太慢,因为它并不慢;也不是因为它所使用的语言太差,因为它并不差。速度慢是由其他因素造成的。

而这些因素正是我们想要解决的:构建现代服务器软件的复杂性:控制依赖关系、与人员不断变化的大型团队一起编程、易维护性、高效测试、有效使用多核 CPU 和网络等等。

简而言之,Go 不仅仅是一种编程语言。当然,它是一种编程语言,这是它的定义,但它的目的是帮助提供一种开发高质量软件的更好方法,至少与 14 多年前的环境相比是这样。

如今,这仍然是它的目标。Go 是一个让开发生产软件变得更简单、更高效的项目。

几周前,当我开始准备这篇演讲时,我有了一个标题,但几乎没有其他东西。 为了激发我的灵感,我在 Mastodon 上征求大家的意见。有不少人回复了我,而且我注意到了回复中的一个趋势:人们认为我们做错的地方都在语言上,而我们做对的地方都在更大的故事中,也就是语言周围的东西,比如 gofmt、部署和测试。事实上,我觉得这很鼓舞人心。我们的努力似乎产生了效果。

但值得承认的是,我们并没有在早期明确真正的目标是什么。也许我们觉得它们是不言而喻的。 为了弥补这一不足,2013 年,我在 SPLASH 会议上发表了题为《 “Google 的 Go “的演讲:为软件工程服务的语言设计》的演讲。


Go at Google

这次演讲和相关博文也许是对Go发生的事情的最好解释。

今天的演讲是 SPLASH 演讲的后续,回顾了我们在构建 Go 语言之后所汲取的经验教训,并将我们自己应用到更广阔的领域。

那么……一些经验教训。

首先,当然是

地鼠(The Gopher)

从地鼠开始说起似乎有些奇怪,但它却是 Go 成功的最早因素之一。 早在发布之前,我们就知道需要一个吉祥物来装饰宣传品–每个项目都需要宣传品–蕾妮-弗伦奇(Renee French)主动提出为我们设计一个吉祥物。我们的想法完全正确。

这是第一款地鼠毛绒玩具的照片。


地鼠

这是地鼠与不太成功的第一原型的合影。


地鼠和它进化程度较低的祖先

Gopher 是一个吉祥物,它是世界各地 Go 程序员的荣誉徽章,甚至是标识。 此时此刻,你正在参加一个名为 GopherCon 的会议,它是众多会议中的一个。 从第一天起,就有一个可识别的、有趣的动物随时准备分享信息,这对 Go 的发展至关重要。 它憨态可掬,却又充满智慧–它可以构建任何东西!


地鼠在搭建机器人(由 Renee French 绘制)

-为社区参与项目定下了基调,即卓越的技术与真正的乐趣相结合。最重要的是,地鼠是社区的一面旗帜,一面可以团结起来的旗帜,尤其是在早期,Go 还是编程界的后起之秀。

这是一张地鼠参加几年前巴黎会议的照片。看看他们多兴奋!


巴黎的地鼠观众(摄影:Brad Fitzpatrick)

综上所述,在知识共享署名许可下发布 Gopher 设计也许不是最好的选择。 一方面,它鼓励人们以有趣的方式对它进行再创作,这反过来又有助于培养社区精神。


地鼠模型表

蕾妮制作了一张 “模型表”,帮助艺术家们在使用地鼠的同时保持它的原貌。

一些艺术家利用这些特征制作了自己的版本;我最喜欢日本设计师 @tottie 设计的 Renee 版本:

@tottie’s gophers

和游戏程序员 @tenntenn:


@tenntenn’s gopher

但是,许可证中的 “署名 “部分经常导致令人沮丧的争论,或者把不属于蕾妮也不符合原作精神的创作错误地归功于蕾妮。而且,老实说,”署名 “往往只是在不情愿的情况下被遵守,或者根本就没有被遵守。例如,我怀疑 @tenntenn 在使用他的地鼠插图时是否得到了补偿,甚至是否得到了承认。


gophervans.com:Boo!

因此,如果重来一次,我们会认真思考确保吉祥物忠于其理想的最佳方法。维护吉祥物是个棘手的问题,解决的办法仍然难以捉摸。

接下来说说更技术性的问题。

正确的事情

这里列出了我认为我们客观上做对的事情,尤其是回想起来。 并不是每个语言项目都做对了这些事情,但每件事情都对 Go 的最终成功至关重要。我会尽量简短,因为这些都是大家耳熟能详的话题。

1.规范。我们从正式的规范开始。这不仅能在编写编译器时锁定行为,还能让多种实现方式共存并就该行为达成一致。编译器本身并不是规范。如何测试编译器?

对了,说明书的初稿就是在悉尼达令港一栋大楼的 18 层写成的。我们在Go的家乡庆祝Go的生日。

2.多种实现。有多个编译器,它们都在执行相同的规范。有了规范,实现起来就容易多了。

伊恩-泰勒(Ian Taylor)有一天发来邮件,告诉我们他读了我们的规范草案后,自己也写了一个编译器,这让我们大吃一惊。

Subject: A gcc frontend for Go

From: Ian Lance Taylor

Date: Sat, Jun 7, 2008 at 7:06 PM

To: Robert Griesemer, Rob Pike, Ken Thompson

One of my office-mates pointed me at https://…/go_lang.html .It

seems like an interesting language, and I threw together a gcc

frontend for it.It’s missing a lot of features, of course, but it

does compile the prime sieve code on the web page.

这让人大开眼界,但随后又出现了更多,所有这一切都得益于正式规范的存在。

大量编译器

拥有多个编译器有助于我们完善语言和改进规范,同时也为那些对我们类似 Plan-9 的工作方式不太感冒的人提供了一个替代环境。

(稍后再详述)。

如今有很多兼容的实现方式,这很好。

3.可移植性。我们简化了交叉编译,这使得

程序员可以在他们喜欢的任何平台上工作,并将其移植到所需的任何平台上。这一点在 Go 语言中可能比其他任何语言都要容易。 我们很容易将编译器视为编译器是它所运行的机器的原生编译器,但这是没有道理的。

打破这种假设是非常伟大的,对许多开发人员来说也是个好消息。


可移植性

4.兼容性。我们一直在努力使语言符合1.0 版本,然后用兼容性保证将其锁定。鉴于这对 Go 的普及产生了巨大的、有据可查的影响,我对大多数其他项目都拒绝这样做感到不解。 是的,保持强大的兼容性是有代价的,但它阻挡了功能的发展,而且在一个几乎没有其他东西是稳定的世界里,不用担心新发布的 Go 会破坏你的项目是一件令人高兴的事。

Go 兼容性承诺

5.库。虽然它的发展有些偶然,因为一开始并没有其他地方可以安装 Go 代码,但一个可靠、制作精良、包含编写 21 世纪服务器代码所需的大部分内容的库的存在是一笔重要的财富。在我们有足够的经验来了解还应该提供什么之前,它让社区成员都在使用同一个工具包。这样做的效果非常好,有助于防止出现变种库,有助于统一社区。

6.工具。我们确保 Go 语言易于解析,这样就可以构建工具。起初,我们认为 Go 需要一个集成开发环境,但工具的简便性意味着,集成开发环境会及时出现在 Go 中。随着 gopls 的出现,它们已经出现了,而且非常棒。


工具

我们还为编译器提供了一系列辅助工具,如自动测试、覆盖率和代码审查。当然还有 go 命令,它集成了整个构建过程,是许多项目构建和维护 Go 代码所需的全部工具。

快速构建

此外,Go在快速构建方面的声誉也很好。

7.Gofmt。我把 gofmt 作为工具中的一个单独项目,是因为它不仅在 Go 上,而且在整个编程社区都留下了浓墨重彩的一笔。在罗伯特编写 gofmt 之前(顺便说一句,他从一开始就坚持要这么做),自动格式化工具的质量并不高,因此大多无人问津。

谚语

Gofmt 证明了这一点,如今几乎所有值得使用的语言都有了标准格式器。不用再为空格和换行争论不休,节省下来的时间值得我们花时间定义标准格式,并编写这段相当困难的代码来实现自动化。

此外,gofmt 还使无数其他工具成为可能,如简化工具、分析器甚至代码覆盖工具。因为 gofmt 的内核成为了一个任何人都可以使用的库,你可以解析一个程序,编辑 AST,然后打印出完美的字节输出,供人类和机器使用。

谢谢你,罗伯特。

祝贺就到此为止吧。说点更有争议的话题吧。

并发

并发很有争议?2002年,也就是我加入谷歌的那一年,并发确实很有争议。约翰-奥斯特豪特(John Ousterhout)曾写过一篇著名的文章,认为线程不好,很多人都同意他的观点,因为线程似乎很难使用。

John Ousterhout 不喜欢线程

谷歌软件几乎总是避开它们,几乎是直接禁止它们,而禁止它们的工程师引用了奥斯特豪特的话。这让我很困扰。自 20 世纪 70 年代以来,我一直在做类似并发症的事情,有时甚至没有意识到这一点,对我来说,这似乎很强大。但经过反思,我发现奥斯特豪特犯了两个错误。首先,他的概括超出了他有兴趣使用线程的领域;其次,他主要是在抱怨通过笨拙的低级包(如 pthread)来使用线程,而不是抱怨线程的基本思想。

像这样混淆解决方案和问题,是各地工程师都会犯的错误。有时,提出的解决方案比要解决的问题更难,因此很难发现还有更简单的途径。但我想说的是。

根据经验,我知道有更好的方法来使用线程,或者不管我们怎么称呼它们,我甚至还在出发前做了关于线程的演讲。


Newsqueak 中的并发性

但知道这一点的并不只有我一个人;许多其他语言、论文甚至书籍都写过关于并发编程的内容,这些都表明并发编程可以做得很好。只是它还没有成为主流思想,而 Go 的诞生正是为了解决这个问题。在那次长达 45 分钟的传奇构建中,我试图为一个无线程的二进制程序添加一个线程,但由于我们使用了错误的工具,所以过程非常艰难,令人沮丧。

回顾过去,我认为可以说 Go 在让编程界相信并发是一种强大的工具(尤其是在多核网络世界中),以及它可以比 pthreads 做得更好方面发挥了重要作用。如今,大多数主流语言都对并发性提供了良好的支持。


谷歌 3.0

此外,Go 语言的并发版本也很新颖,至少在后来的语言中是这样,它让 goroutines 不带任何味道。 没有 coroutines,没有 tasks,没有 threads,没有名字,只有 goroutines。我们发明了 “goroutine “这个词,因为现有的术语都不合适。直到今天,我还希望 Unix 的拼写命令能学会这个词。

另外,因为我经常被问到这个问题,让我来谈谈 async/await。 async/await模型及其相关风格是许多语言选择的支持并发性的方式,这让我有点难过,但它绝对是对pthreads的巨大改进。

与 goroutines、channel 和 select 相比,async/await 对于语言实现者来说更容易构建或改造到现有平台中,而且体积更小。但是,它将部分复杂性推回给了程序员,往往会产生鲍勃-尼斯特罗姆(Bob Nystrom)著名的 “有色函数”。


你的函数是什么颜色的?

我认为Go表明,CSP(一种不同但更古老的模型)完全可以融入过程式语言,而无需如此复杂。我甚至多次看到它作为一个库被使用。但是,要很好地实现它,需要很大的运行时复杂性,我可以理解为什么有些人不愿意在他们的系统中构建这种复杂性。但重要的是,无论你提供什么样的并发模型,都要只做一次,因为提供多种并发实现的环境可能会有问题。当然,Go 解决这个问题的方法是将其放在语言中,而不是库中。

关于这些问题,我们可能有一整篇演讲要讲,但现在已经足够了。

[旁白结束]

并发的另一个价值在于,它让 Go 看起来像是一种新事物。 正如我所说,其他一些语言之前也支持并发,但它们从来都不是主流,而 Go 对并发的支持是一个主要的吸引因素,它帮助增加了早期的采用率,吸引了之前没有使用过并发但对其可能性感兴趣的程序员。

而这正是我们犯了两个重大错误的地方。


吹号地鼠(合作序列进程)

首先,并发很有趣,我们很高兴能拥有它,但我们心目中的用例是

主要是服务器方面的事情,需要在关键库(如 net/http)中完成,而不是在每个程序中都能用到。 因此,当许多程序员使用它时,他们很难理解它到底能给他们带来什么帮助。 我们应该事先解释清楚,语言中的并发支持真正带来的是更简单的服务器软件。 这个问题空间对很多人来说都很重要,但并不是每个尝试 Go 的人都能理解,而这正是我们缺乏指导的原因。

与此相关的第二点是,我们花了太长时间来澄清并行性(在多核机器上支持并行多重计算)与并发性之间的区别。


并发不是并行

无数程序员试图通过使用 “oroutines “并行化来加快代码速度,但结果往往令他们大失所望。只有在底层问题本质上是并行的情况下,并发代码才会在并行化后变得更快,比如服务 HTTP 请求。我们在解释这一点上做得很糟糕,结果让很多程序员感到困惑,可能还赶走了一些人。

为了解决这个问题,2012 年我在 Heroku 的开发者大会 Waza 上发表了一篇名为《并发不是并行》的演讲。这是一场有趣的演讲,但它本应更早举行。

对此表示歉意。但好的观点仍然成立:Go 帮助普及了并发作为服务器软件结构的一种方式。

接口

很明显,接口与并发性一样,是 Go 的一个突出思想。它们是 Go 对面向对象设计的回应,采用的是原始的、以行为为中心的风格,尽管新来者一直在推动让结构体承担这一重任。

让接口动态化,无需提前公布哪些类型实现了接口,这让一些早期的批评者感到困扰,现在仍有一些人对此感到恼火,但这对 Go 所倡导的编程风格来说非常重要。 大部分标准库都建立在接口的基础之上,而测试和管理依赖关系等更广泛的主题也在很大程度上依赖于接口的慷慨和 “欢迎所有人 “的性质。

我觉得接口是 Go 中设计得最好的东西之一。

除了早期关于数据是否应包含在接口定义中的几次讨论之外,接口在讨论的第一天就已经完全成型了。


GIF 解码器:go接口(罗布-派克和奈杰尔-陶,2011 年)

这里有一个故事要讲。

在罗伯特和我办公室的第一天,我们提出了如何处理多态性的问题。Ken 和我从 C 语言中了解到,qsort 可以作为一个困难的测试用例,于是我们三个开始讨论我们的雏形语言如何才能实现一个类型安全的排序例程。

罗伯特和我几乎同时提出了同样的想法:使用类型上的方法来提供排序所需的操作。这个想法很快就发展成了值类型有行为,定义为方法,方法集可以提供函数可以操作的接口。Go 的接口很快就出现了。

sort.inteface

这一点往往不被人所认识:Go 的排序是作为一个在接口上操作的函数来实现的。这不是大多数人熟悉的面向对象编程风格,但却是一个非常强大的想法。

这个想法让我们兴奋不已,而且它有可能成为我们的基础编程语言。

编程结构的可能性令人陶醉。 Russ 加入后,很快就指出了 I/O 如何与这一想法完美契合,并在很大程度上基于三个著名的接口:empty、Writer 和 Reader,平均每个接口包含三分之二个方法,迅速建立了函数库。 这些小方法对 Go 来说是习以为常的,而且无处不在。

接口的工作方式不仅成为 Go 的显著特征,还成为我们思考库、通用性和组成的方式。这真是令人兴奋的事情。

不过,我们可能不该就此打住。

我们之所以走上这条路,至少部分原因是我们经常看到通用编程是如何鼓励一种先关注类型再关注算法的思维方式的。早期抽象而非有机设计。容器而非函数。

我们在语言本体中定义了通用容器–映射、切片、数组、通道–却没有让程序员访问它们所包含的通用性。这可以说是一个错误。我们认为,大多数简单的编程任务都可以通过这些类型很好地处理,我现在仍然这么认为。但有些任务是无法完成的,语言提供的功能和用户可以控制的功能之间的障碍肯定会困扰一些人。

总之,虽然我不会改变界面的工作方式,但它们影响了我们的思维方式,我们花了十多年才纠正过来。伊恩-泰勒(Ian Taylor)从一开始就推动我们面对这个问题,但由于接口是go编程的基石,要做到这一点非常困难。

批评者经常抱怨我们应该直接使用泛型,因为泛型 “简单”,也许在某些语言中可以这样做,但接口的存在意味着任何新形式的多态性都必须考虑到接口。要找到一条能与语言其他部分完美配合的前进道路,我们需要多次尝试、数次流产的实现,以及数小时、数天和数周的讨论。最终,在 Phil Wadler 的带领下,我们请来了一些类型理论家帮忙。 即使到了今天,语言中已经有了坚实的泛型模型,但接口作为方法集的问题仍然挥之不去。


通用排序规范

如你所知,最终的答案是设计一种可以吸收更多形式多态性的通用接口,从 “方法集 “过渡到 “类型集”。这是一个微妙而深刻的举措,社区中的大多数人似乎都能接受,尽管我怀疑抱怨声永远不会停止。

有时候,要花很多年才能弄明白一件事,甚至要花很多年才能发现自己并不能完全弄明白它。但你还是坚持了下来。

顺便说一句,我希望我们能有一个比 “泛型 “更好的术语。”泛型 “起源于一种不同的、以数据结构为中心的多态性风格。”参数多态性 “是 Go 提供的正确术语,它是一个准确的术语,但说起来很难听。不过,”泛型 “是我们的说法,尽管它并不完全正确。

编译器

在他们看来,正确的方法是使用 LLVM 或类似的工具包,或者用 Go 本身编写编译器,这就是所谓的自托管。 出于几个原因,我们没有采用这两种方法。

首先,引导一种新语言至少需要在现有语言中完成编译器的第一步。对我们来说,C 语言是不二之选,因为 Ken 已经编写了 C 语言编译器,而且它的内部结构可以很好地作为 Go 语言编译器的基础。此外,在开发语言的同时用自己的语言编写编译器,往往会产生一种适合编写编译器的语言,但这并不是我们想要的语言。

早期的编译器是有效的。它能很好地引导语言。但它有点古怪,实际上是一个 Plan 9 风格的编译器,使用的是编译器编写中的旧思想,而不是静态单赋值等新思想。 生成的代码很一般,内部结构也不漂亮。 但它实用、高效,而且编译器代码本身规模不大,对我们来说也很熟悉,这使得我们在尝试新想法时很容易快速做出修改。其中一个关键步骤是增加了自动增长的分段堆栈。这很容易添加到我们的编译器中,但如果我们使用的是 LLVM 这样的工具包,考虑到需要更改 ABI 和垃圾收集器支持,将这一更改集成到整个编译器套件中是不可行的。

另一个运行良好的领域是交叉编译,这直接源于原始 Plan 9 编译器套件的工作方式。

用我们自己的方式,无论多么非正统,都能帮助我们快速前进。有些人对这一选择感到不快,但这是我们当时最正确的选择。

Go 1.5 后的 Go 编译器架构

在 Go 1.5 版本中,Russ 编写了一个工具,用于半自动地将编译器从 C 语言翻译成 Go 语言。那时,Go 语言已经完成,对编译器导向语言设计的担忧已经不重要了。网上有一些关于这一过程的演讲,值得一看。我曾在 2016 年的 GopherCon 上发表过一次关于汇编器的演讲,这也是我毕生追求可移植性的一个高潮。


Go 汇编程序的设计(GopherCon 2016)

我们从 C 语言开始是正确的,但最终将编译器翻译成 Go 语言,让我们能够在开发过程中发挥 Go 语言的所有优势,包括测试、工具、自动重写、性能分析等。现在的编译器比原来的编译器更简洁,生成的代码也更好。当然,这就是引导的工作原理。

请记住,我们的目标不仅仅是一种语言,而是更多。

我们的不同寻常绝不是对 LLVM 或语言界任何人的侮辱。我们只是使用了最适合我们任务的工具。当然,今天已经有了 LLVM 托管的 Go 编译器,还有许多其他语言的编译器,这也是理所应当的。

项目管理

我们从一开始就知道,要想取得成功,Go 必须是一个开源项目。但我们也知道,在我们想出关键的想法并付诸实施之前,私下开发会更有成效。最初的两年对于我们在不受干扰的情况下明确目标至关重要。

向开放源代码过渡是一个巨大的变化,也很有教育意义。来自社区的意见和建议是巨大的。与社区互动需要花费大量的时间和精力,尤其是伊恩,他总是能抽出时间回答每个人提出的问题。但这也带来了更多。我至今仍惊叹于 Windows 移植的速度之快,这完全是在 Alex Brainman 的指导下由社区完成的。这太不可思议了。

我们花了很长时间才理解转为开源项目的意义,以及如何管理它。

尤其是,可以说我们花了很长时间才明白与社区合作的最佳方式。本讲座的一个主题就是我们沟通不畅–即使我们认为自己沟通得很好–由于误解和期望不一致,浪费了大量时间。本来可以做得更好的。

不过,随着时间的推移,我们说服了社区(至少是留在我们身边的那部分人),让他们相信我们的一些想法虽然不同于通常的开源方式,但却很有价值。最重要的是,我们坚持通过强制代码审查和对细节的详尽关注来保持高质量的代码。


任务控制中心(由 Renee French 绘制)

有些项目的工作方式与众不同,它们快速接受代码,然后在代码提交后进行清理。Go 项目则反其道而行之,试图先保证质量。我认为这是一种更有效率的方式,但它会把更多的工作推回给社区,他们需要理解这种方式的价值,否则他们就不会感到自己受到了应有的欢迎。这里还有很多东西需要学习,但我相信现在情况已经好多了。

顺便提一下,有一个历史细节并不广为人知。该项目曾使用过 4 种不同的内容管理系统:SVN、Perforce、Mercurial 和 Git。Russ 做了一项艰巨的工作,让所有的历史都得以保留,所以即使在今天,Git repo 也包含了最早在 SVN 中做出的修改。我们都认为保留历史是很有价值的,我感谢他所做的繁重工作。

还有一点。人们通常认为谷歌会告诉 Go 团队该做什么。事实并非如此。谷歌对 Go 的支持慷慨得令人难以置信,但它并不制定议程。社区的参与度要高得多。谷歌有一个庞大的内部 Go 代码库,团队用它来测试和验证发布的版本,但这是通过从公共仓库导入到谷歌来实现的,而不是反过来。简而言之,核心 Go 团队由 Google 支付报酬,但他们是独立的。

软件包管理

Go 软件包管理的开发过程并不顺利。我认为,Go 语言本身的包设计非常出色,在我们讨论的头一年左右的时间里耗费了大量时间。如果你感兴趣的话,我之前提到的 SPLASH 讲座详细解释了它的工作方式。

其中一个关键点是在导入语句中使用纯字符串来指定路径,从而提供了一种灵活性,我们当时认为这种灵活性非常重要。但是,从只有一个 “标准库 “到从网上导入代码的转变过程非常艰难。


修复云(蕾妮-弗伦奇绘图)

有两个问题。

首先,我们这些早期 Go 核心团队的成员熟悉 Google 的工作方式,即使用 monorepo 和每个人都在头部构建。但是,我们在使用软件包管理器时并没有足够的经验,软件包版本众多,解决依赖关系图的问题也非常困难。时至今日,很少有人真正了解其中的技术复杂性,但这并不能成为我们从一开始就未能解决这些问题的借口。尤其令人尴尬的是,我曾在一个失败的项目中担任技术负责人,为谷歌的内部构建做了类似的工作,我本该意识到我们所面临的问题。


开发

我在 deps.dev 上的工作是一种忏悔。

其次,让社区参与帮助解决依赖性管理问题的初衷是好的,但当最终设计出炉时,即使有大量的文档和关于理论的文章,社区中的许多人还是觉得被轻视了。


pkg.go.dev

这次失败给团队上了一堂课,让他们明白与社区的互动应该如何真正发挥作用。

不过,现在事情已经尘埃落定,出现的设计在技术上非常出色,而且似乎对大多数用户都很有效。只是时间太长,道路崎岖。

文档和例子

另一件我们前期没有做好的事情是文档。我们编写了大量的文档,并认为自己做得很好,但很快就发现,社区需要的文档水平与我们的预期不同。


正在修理图灵机的地鼠(绘图者:Renee French)

即使是最简单的函数,关键的缺失也是示例。我们以为只要说出某个函数的作用就可以了;我们花了很长时间才认识到,展示如何使用它才更有价值。


可执行的示例

不过,我们已经吸取了教训。现在文档中有很多示例,大部分都是由开源贡献者提供的。我们很早就做了一件事,就是让它们可以在网络上执行。我在 2012 年的谷歌 I/O 大会上做了一次演讲,展示了并发的实际应用,安德鲁-杰兰德(Andrew Gerrand)写了一个可爱的网络工具,让我们可以在浏览器上直接运行这些代码段。我怀疑这是不是第一次这样做,但 Go 是一种编译语言,许多听众以前从未见过这种技巧。这项技术随后被部署到博客和在线软件包文档中。


Go

也许更重要的是,它被部署到了 Go playground,这是一个免费开放的沙盒,供人们尝试,甚至开发代码。

结论

我们走过了漫长的道路。

回顾过去,我们可以清楚地看到许多事情都做对了,它们都帮助围棋取得了成功。但是,还有很多事情可以做得更好,重要的是要正视这些问题并从中吸取教训。对于任何主持一个重要开源项目的人来说,双方都可以从中吸取教训。

我希望我对这些教训及其原因的历史回顾能对大家有所帮助,或许还能作为对那些反对我们的做法和方式的人的一种道歉/解释。


2023 年地鼠大会吉祥物 Renee French 创作

但是,我们在这里,在启动 14 年后。可以说,这里总体上还不错。

主要是因为我们在设计和开发 Go 时所做出的决定,让它成为了一种编写软件的方式,而不仅仅是一种编程语言。

我们之所以能取得今天的成就,部分原因在于

  • 强大的标准库,实现了服务器代码所需的大部分基本功能
  • 并发性是该语言的一流组件
  • 基于组合而非继承的方法
  • 明确依赖性管理的打包模型
  • 集成快速构建和测试工具
  • 严格统一的格式
  • 注重可读性,而不是聪明性
  • 兼容性保证

最重要的是,我们得到了Gopher 这个乐于助人、多元化社区的大力支持。

多元化社区(@tenntenn 绘制)

也许这些问题带来的最有趣的结果是,无论谁编写 Go 代码,它的外观和运行方式都是一样的,基本上不存在使用不同语言子集的派别,而且可以保证随着时间的推移继续编译和运行。这在主流编程语言中可能尚属首次。

我们绝对做到了这一点。

谢谢。

本文文字及图片出自 What We Got Right, What We Got Wrong

余下全文(1/3)
分享这篇文章:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注