开源14年,Go语言之父的深度解析

发表时间: 2024-01-12 17:20

近日,Go语言之父 Rob Pike 在悉尼 GopherConAU 会议上发表了一场耐人寻味的演讲。Go 语言发展 14 年,它已不在是一门编程语言。

原文链接:https://commandcenter.blogspot.com/2024/01/what-we-got-right-what-we-got-wrong.html

未经允许,禁止转载!

作者 | Rob Pike 译者 | 弯月
责编 | 夏萌
出品 | CSDN(ID:CSDNnews)
2023年11月10日是Go语言作为开源项目发布的十四周年纪念日。
我还记得那一天,美国加利福尼亚时间下午3点,Ken Thompson、Robert Griesemer、Russ Cox、Ian Taylor、Adam Langley、Jini Kim和我怀着无比期待的心情,看着网站上线,将我们的工作成果展示给全世界。
十四年过去了,有很多事情值得回顾。今天,我想借此机会谈谈那天以来我们所学到的一些重大经验教训。即使是最成功的项目,回顾时也会发现一些本可以做得更好的地方。当然,也有一些方面事后看来似乎是成功的关键。
我想事先明确的是,此次发言仅代表我个人,不代表Go团队,也不代表Google。继往开来,Go是一个由一支专注的团队和庞大社区共同努力的巨大项目,所以如果你同意我的发言,请感谢他们。如果你不同意,一切责任在我,请不要责怪他们。
鉴于本文的标题,很多人可能以为我会分析Go语言的优点和缺点。这方面的内容我当然会谈到,但出于一些原因我也会提到其他方面。
首先,对于编程语言来说,什么是好什么是坏,很大程度上是主观而非客观的,很多人对Go或任何其他语言,乃至一些非常细节的特性发表了很多争议。
还有很多关于诸如换行符放在哪里,nil的工作原理,使用大写字母进行导出,垃圾收集,错误处理等方面的讨论。虽然可以讨论的方面很多,但几乎都已说过了。
但我要谈论的不仅仅是语言本身,因为这门语言并不是这个项目的全部。我们的目标并不是创建一种新的编程语言,而是创建一种更好的编写软件方式。是什么语言,我们每个人可能都会认为我们使用的语言有一些问题,但问题根源并非来自这些语言的特性,而是因为使用这些语言创建软件的过程。
新语言的创建为探索其他思想提供了一条新路径,但这只是促成因素,而不是最终目的。如果不是因为当初我在工作中构建二进制文件需要45分钟之长,Go就不会诞生,但这45分钟并不是因为编译器慢(实际上编译器并不慢),也不是因为编写代码所使用的语言很糟糕(实际上语言本身也不差),慢的原因来自其他因素。
而这些因素正是我们想要解决的问题:构建现代服务器软件的复杂性,控制依赖性,大型编程团队的人员不断变动,如何易于维护、高效测试、有效利用多核CPU和网络等等。
简而言之,Go不仅仅是一种编程语言。当然,Go也确实是一种编程语言,这是它的定义,但它的目的是提供一种更好的方法来开发高质量的软件,至少优于我们14年前的环境。
而这仍然是今天的目标:Go是一个旨在降低构建生产软件的难度并提高效率的项目。
几周前,在准备此次演讲时,我只有一个标题。为了激发灵感,我在Mastodon上询问了人们的意见。在众多回复之中,我注意到了一个趋势:人们认为我们做错的方面都集中在语言上,但从更高层面来看我们都做对了,比如gofmt、部署和测试等相关方面。这实际上让我很受鼓舞,因为我们的努力似乎有了结果。
但我必须承认,或许是因为我们觉得这些目标很明显,最初我们并没有清楚地阐明真正目标。为了弥补这一不足,2013年我在SPLASH大会上做了一个演讲,题为《Google的Go语言:服务于软件工程的语言设计》。
那次演讲以及相关博文或许是Go语言诞生动机的最好解释。而本文可以看作是那次演讲的后续,回顾我们在完成语言构建后,投入到更广泛以及更高层面工作的过程中积累的经验。
首先,当然是Gopher。

Gopher
虽然Gopher的诞生很奇特,但它是Go成功的最早因素之一。远在Go语言发布很久之前,我们就知道我们需要一个吉祥物来纪念这个项目——每个项目都需要纪念品,最终Renee French为我们创造了一个,我们的这个决定很正确。
下面是gopher毛绒玩具的第一张照片。

下面是gopher的一个不太成功的原型图片。

Gopher是一个吉祥物,是Go程序员的荣誉徽章,甚至是身份象征。有一场Go语言大会就叫做GopherCon。从第一天起,拥有一个有辨识度、有趣的生物对Go的发展至关重要。它给人一种看似愚蠢、实则聪明的感觉,它可以构建任何东西!

图:Gopher正在构建一个机器人(Renee French创作)
这个吉祥物奠定了社区与项目的互动基调,即技术卓越与真正乐趣相结合。最重要的是,Gopher是Go社区的一面旗帜,尤其是在Go在编程世界仍然是新兴力量的早期,众人都集结在这面大旗之下。
下面是多年前一众Gopher参加巴黎会议的照片。看看它们有多兴奋!

话虽如此,以创作共用署名许可证发布Gopher的设计或许并不是最好的选择。一方面,这鼓励人们以有趣的方式进行二创,而这反过来又有助于培养社区精神。

Renee创建了一份“模型表”来帮助艺术家们进行创作,同时坚守Gopher的精神。
一些艺术家利用这些特性进行了一些很有趣的创作,Renee和我最喜欢的是日本设计师@tottie制作的版本:

以及游戏程序员@tenntenn:

但是,许可证中的“归属”部分常常引发争论,而有些创作并非出自Renee,也不符合原作的精神,但“赞誉”却阴差阳错地给到了她。老实说,有人很不情愿地写出归属,而有人则根本没有写出归属。例如,我怀疑虽然@tenntenn的Gopher插图被有些人使用,但他并没有得到任何报酬,甚至连致谢都没有。

因此,如果从头来过,我们会认真考虑确保吉祥物忠于其理念的最佳方法。维护吉祥物是一个难题,解决方案可遇而不可求。
下面,我们来谈谈技术。

做对了的方面
以下是我认为从客观的角度来看,我们做对了的方面。并不是每个语言项目都做了这些事情,但每一项对Go的最终成功都至关重要。我会尽量简洁,因为大家都很熟悉这些主题。
1. 规范。
我们的起点始于一个正规化的规范。这不仅确定了编译器的行为,而且允许多种实现存在,并且不同实现拥有相同的行为。只有编译器并不能构成规范。总得有个标准才能测试编译器。
顺便说一下,规范的第一版草稿是在悉尼达令港的一座大楼的18楼写的。此次我们庆祝Go的生日正是在Go的故乡悉尼。
2. 多个实现。
我们有多个编译器,都实现了相同的规范。有了规范,实现这一目标就变得更容易。
有一天,Ian Taylor突然发送电子邮件跟我们说,他在阅读我们的规范草案后,动手编写了一个编译器,这让我们感到十分惊讶。
惊讶之余,许多人纷纷效仿,这都得益于规范的存在。

图:许许多多的编译器

拥有多个编译器可以帮助我们完善语言并改进规范,同时还可以为那些不喜欢我们的类似于Plan-9的经营方式的人提供了另一种环境。(稍后详谈。)
如今我们有很多兼容的实现,这非常好。
3. 可移植性。
我们大幅降低了交叉编译的难度,因此程序员都可以在自己喜欢的任何平台上工作,并在需要的任何平台上发布。就这一点而言,使用Go语言要比其他任何语言都更容易。想象一个在代码运行的机器上的原生编译器很容易,但实际情况并不一定如此。打破这种设想很重要,而且许多开发人员都不太了解这一点。
4. 兼容性。
我们为编写1.0版本付出了很大努力,并确保了兼容性。鉴于这对Go的普及产生了如此巨大的、有据可查的影响,我很费解为什么大多数其他项目会抵制这种做法。没错,维护强大的兼容性确实需要付出代价,但可以防止我们过分追求新特性,避免陷入几乎没有什么是稳定的世界,不必担心新版本破坏项目,多么舒心啊。
5. 库。
尽管大量库的涌现在一定程度上属于偶然事件,因为刚开始的时候安装了Go代码的人只有我们自己,但拥有一个坚实的、精心编写、其中包含编写21世纪服务器所需的大部分代码的库是一项重要资产。为此,社区一直在使用相同的工具包,直到我们有足够的经验,了解还应该提供哪些工具。这种方式很有效,有助于防止出现变体库,同时有助于统一社区。
6. 工具。
我们努力确保Go语言易于解析,以方便构建工具。起初,我们认为Go需要一个集成开发环境(IDE),但简便的工具意味着,Go的IDE会自动出现。随着gopls的出现,IDE确实出现了,并且非常出色。
此外,我们还提供了一组编译器的附加工具,自动化测试、覆盖率和代码审查。当然,还有go命令整合了整个构建过程,对于许多项目来说,这足以构建和维护Go代码了。
而这并没有妨碍Go快速编译的好名声。
7. Gofmt。
我将gofmt从工具中拿出来单独讨论,是因为这个工具不仅影响了Go,而且对整个编程社区都产生了影响。在Robert编写gofmt之前(顺便说一下,他坚持从项目之初就编写这个工具),自动格式化工具的质量都不高,因此基本上没有人使用。
事实证明,Gofmt可以出色地完成这项工作,如今几乎每种值得使用的编程语言都有一个标准的格式化工具。无需再争论空格和换行符,为我们节省了很多时间,这些时间比花费在定义标准格式和编写这类自动化代码格式工具上的时间更有价值。
此外,人们在gofmt的基础之上构建了许多其他工具,比如简化器、分析器,甚至是代码覆盖工具。由于gofmt本质上是一个任何人都可以使用的库,因此你可以解析一个程序,编辑AST,然后直接打印出精确到每个字符的、人类和机器都可以阅读的代码。
谢谢你,Robert。
姑且就表扬到这里,下面我们来谈谈一些更有争议的话题。

并发
并发有争议吗?至少在2002年,也就是我加入Google的那一年是有的。John Ousterhout曾写文章说线程很糟糕,许多人赞同他的观点,因为线程似乎很难使用。
Google的软件几乎总是在回避线程,甚至可以说线程被彻底禁用了,而实施禁令的工程师引用了John Ousterhout观点。这让我感到不安。自上世纪70年代以来,我一直在使用并发,有时甚至没有意识到,在我看来,并发非常强大。但仔细一想,John Ousterhout特犯了两个错误。首先,他把自己对于线程的看法推广到了自己负责之外的领域;其次,他主要是在抱怨使用笨拙的低级包(如pthread)来使用线程,而不是线程的基本思想。
这是所有工程师都会犯的一个错误,他们没有想明白解决方案和这类的问题。有时,提出的解决方案比解决的问题更难,而且很难看到其实还有更容易的方法。但这是另外的话题了。
根据我的经验,我们有更好的方法来使用线程,或者是并发等其他的叫法,甚至在Go语言发布之前我就在一次讲座中谈到过线程。
但并不是只有我一个人清楚这一点,许多关于并发编程的语言、论文,甚至是书籍,都表明并发的效果不错。只不过它还没有成为主流思想,而Go的诞生在某种程度上强调了这点。在上文我提到的45分钟的构建时间里,我尝试将线程添加到了一个非线程化的二进制文件,由于使用了错误的工具,导致我的工作困难重重。
回顾过去,平心而论,我认为Go在以下这个方面做出了很大贡献:说服编程世界并发是一种强大的工具,尤其是在多核网络化的世界中,而且并发比pthread有更好的表现。如今,大多数主流语言都提供了很好的并发支持。
此外,Go的并发版本算是比较新颖的,至少相较于“前辈”语言是很新颖的,因为goroutines是一种原始的并发模型。没有协程、没有任务、没有线程、没有名称,只有goroutines。我们发明了"goroutine"这个词,因为现有的术语都不太合适。直到今天,我仍然希望Unix的spell命令能够学会这个词。
顺便说一句,我经常被问到async/await,所以我打算谈一谈这个问题。让我有点难过的是,许多语言采用async/await模型及其相关的样式来支持并发,但它的改进绝对远胜于pthread。
相较于goroutines、channels和select,对于语言实现者来说,async/await更容易、更小巧,可以轻松地构建或整合到现有平台。但一些复杂性仍然抛给了程序员,常常引发Bob Nystrom所说的“被着色的函数”。
我认为Go展示了CSP(一种不同但更古老的模型)可以完美地融入过程式语言,而且不会太复杂。我甚至见过很多次有人编写这类的库。但是,这类良好的实现需要一个复杂的运行时,我能理解为什么有些人可能不愿意将其构建到系统中。然而,不管提供哪种并发模型,都要确保只提供一种,这一点很重要,因为一个环境提供多个并发实现可能会带来问题。当然,Go直接将CSP融入了语言,并没有采用库的形式。
有关这些问题可以专门做一期演讲,本文暂时就说到这里。
并发的另一个价值在于,它让Go看上去焕然一新。如前所述,以前有一些语言也支持并发,但并发从未成为主流,而Go对并发的支持是一个主要的吸引因素,有助于推动早期采用,吸引了以前没有使用过并发、但对其可能性感到好奇的程序员,
在这方面,我们犯了两个重大错误。
首先,并发是一种有趣的方式,我们都愿意使用,但我们所想到的大多数用例都是与服务器相关的,这意味着我们必须在核心库中实现并发,比如net/http,而且并非每个程序的每个地方都会使用并发。因此,许多程序员在尝试使用并发时,都很难弄清楚如何真正有效利用并发。我们本应事先说明语言中的并发支持带来的好处在于简化服务器软件。对于许多人来说,这个问题空间很重要,并非每个尝试Go的人都应该使用并发,这种指导上的欠缺是我们的责任。
第二点也与此相关,我们花费了太长时间来澄清并行和并发之间的区别,前者指的是在多核机器上支持多个计算,而后者是通过良好的代码组织方式实现并行计算。
无数程序员尝试使用goroutines来并行执行代码,以提升代码的运行速度,结果却适得其反,这让他们感到非常困惑。只有当底层问题本质上是并行的情况下,例如服务HTTP请求,并发代码才能提高速度。我们未能清楚地解释这一点,结果让许多程序员感到困惑,并导致一些人放弃了Go。
为了解决这个问题,2012年我在Waza,Heroku的开发者大会上发表了一段名为“并发不是并行”的演讲。那次演讲本身很有趣,但我应该早一点站出来。
对此我深表歉意。但也有好的一面:Go帮忙推广了并发作为构建服务器软件的一种方式。

接口
很明显,接口与并发一样,是Go的一个标志性概念。它们是Go对面向对象设计的回应,采用了最原始的、以行为为中心的风格,尽管许多刚刚加入Go社区的人不断要求让结构来承担这一重担。
接口动态化后,就无需提前声明哪些类型实现了它们,这在早期让一些人感到很困惑,至今仍有人不满意,但这对Go培养的编程风格非常重要。标准库的大部分都建立在它们的基础之上,而更广泛的主题,如测试和管理依赖性,也非常依赖动态接口的“慷慨”以及“包容万象”的本质。
我认为,接口是Go中最好的设计之一。
除了一些关于是否应将数据包含在其定义中的早期讨论外,接口在讨论的第一天就成形了。
说到此处,我想起一段往事。
就在上述所说的第一天,我和Robert在办公室里提出了关于多态性的问题。根据我和Ken的C语言的经验,qsort可以作为一个棘手的测试案例,于是我们三个人开始讨论我们还处于萌芽期的语言如何实现类型安全的排序例程。
我和Robert几乎同时提出了相同的想法:使用类型的方法提供排序所需的操作。这个想法迅速演变成一个概念:值类型具有行为,定义为方法,并且方法的集合可以提供函数操作的接口。紧接着,Go的接口就诞生了。
有一个经常被忽视的事实:Go的排序是作为函数实现的,而这个函数操作的是接口。虽然不同于大多数人熟悉的面向对象编程风格,但这是一个非常强大的概念。
这个概念让我们感到兴奋,而这有可能成为一个基础性编程结构的可能性让人陶醉。后来Russ来了,很快他就指出I/O非常适合这个概念,于是一个库迅速成形,主要基于三个著名的接口:empty、Writer和Reader,平均每个接口都包含三分之二的方法。这些微方法是Go的习惯用法,并且无处不在。
接口的工作方式不仅成为了Go的一个标志性特征,而且也成为了我们思考库、通用性和组合的方式。它让我们激动不已。
但我们的那次谈话就此打住可能是一个错误。
可以看出,我们选择这条路是因为见过太多次泛型编程鼓励在算法之前多多关注类型的思维方式。要尽早抽象,不要作有机设计。要使用容器,不要用函数。
我们在语言中定义了通用的容器:映射、切片、数组、通道,而没有为程序员提供访问它们所包含的通用性。有人认为这是一个错误。我相信(我至今仍认为这是正确的),大多数简单的编程任务可以通过这些类型很好地处理。但有一些任务是无法完成的,语言的功能与用户的控制之间的壁垒确实让一些人感到困扰。
简单来说,虽然我不想改变接口的工作方式,但它们在很多方面影响了我们的思维,而这需要十多年的时间来纠正。Ian Taylor很早就催促我们面对这个问题,但这很难,因为接口是Go编程的基石。
批评者经常抱怨我们应该使用泛型,因为它们很“简单”,而且在某些语言中可能确实如此,但接口的存在意味着任何新形式的多态性都必须考虑它们。找到一种能够与语言的其他部分良好协同工作的前进方式需要反复尝试,放弃很多实现,而且还需要投入大量时间(几个小时,几天,甚至是几周)来讨论。最终,我们找到了一些类型理论学家(由Phil Wadler领导)来帮助。时至今日,尽管Go语言已经有了一个坚实的泛型模型,接口作为方法集的存在仍然会引发一些潜在的问题。
最终的答案,相信大家都知道了:设计一个接口的泛化,可以吸收更多形式的多态性,从“方法集”过渡到“类型集”。尽管这个设计很深奥,却影响巨大,大多数社区似乎都能接受,但我认为抱怨永远不会停止。
有时候,有些问题需要花费好几年来弄清楚,有时你甚至会发现根本没有解决方法。但你仍要前行。
顺便说一下,我希望我们有一个比“泛型”更好的术语,能够代表一种不同的、以数据结构为中心的多态性风格的术语。“参数多态性”能够正确描述Go提供的功能,而且很准确,但不太好听。尽管不太正确,但我们仍采用了“泛型”。

编译器
编程语言社区感到困惑的事情之一是早期的Go编译器是用C编写的。在他们看来,正确的方法是使用LLVM或类似的工具包,或者就使用Go语言编写编译器,这个过程称为“自举”。但我们没有采取这两种方式,原因有如下几个。
首先,启动新语言需要先用现有语言完成其编译器的第一步。对于我们来说,很明显应该选择C,因为Ken已经写过一个C编译器,而且它的内部结构可以很好地作为Go编译器的基础。此外,在开发语言的同时,用语言自身编写编译器往往会导致语言变成适合编写编译器的语言,但这并不是我们想要的语言。
早期的编译器能够正常工作。它可以顺利启动Go语言。但这个编译器有点怪异,实际上是我们使用旧思维编写的一款Plan 9风格的编译器,而不是静态单赋之类的新型编译器。生成的代码一般,内部也不好看。但这款编译器很实用且高效,代码本身规模适中,而且我们都很熟悉,因此很方便我们在尝试新想法时快速修改。一个关键步骤是添加了自动增长的分段栈。这很容易添加到我们的编译器中,如果我们使用了类似LLVM的工具包,就无法将这个变化整合到编译器套件中,因为我们需要修改API和垃圾收集器支持。
另一个成功的地方是交叉编译,来自原始的Plan 9编译器套件的工作方式。
虽然我们的方式不正统,但可以帮助我们快速前进。有些人不太满意这种选择,但对我们来说,这是一个正确的决定。
到了Go 1.5版,Russ编写了一个工具,可以将编译器从C语言转换为Go语言(半自动)。当然,Go语言已经完成,我们无需再担心编译器的语言设计。网上有一些关于这个过程的讲座是值得一看。我在2016年的GopherCon上做了一个关于汇编器的演讲,这在我终身追求可移植性的历程中算是一个巅峰。
以C语言作为起点,这个选择是正确的,但最终我们将编译器转换为Go,这样在开发编译器的过程中就可以利用Go的所有优势,包括测试、工具、自动重写、性能分析等等。如今的编译器比最初的版本更加整洁,生成的代码也更好。当然,这就是自举编译器的目标。
请记住,我们的目标可不仅仅是一门语言。
我们不走寻常路绝对不是不尊重LLVM或语言社区中的任何人。我们只是使用了最适合的工具。当然,如今Go也有一个基于LLVM的编译器,还有许多其他编译器也有,这都是理所应当的。

项目管理
我们从一开始就知道,Go语言想要成功就必须是一个开源项目。但我们也知道,在明确核心理念,并拥有一个可行的实现之前,私下开发更为高效。刚开始的两年里,搞清楚我们究竟想实现什么,而且不会被干扰是至关重要的。
转向开源是一个巨大的变化,而且对我们很有意义。社区的意见非常多样。与社区互动需要大量的时间和精力,特别是对Ian来说,他不知道如何腾出时间来回答每个人的问题。但开源也带来了很多好处。Windows版的移植速度令我刮目相看,这完全是由社区在Alex Brainman的指导下完成的。真是太神奇了。
我们花了很长时间才理解转成开源项目的影响以及如何管理。
特别是平心而论,我们花了太长时间才搞清楚与社区合作的最佳方式。贯穿此次演讲的一个主题是我们在沟通方面的不足,尽管我们自认为我们之间的沟通良好,但种种误解和期望不匹配浪费了很多时间。我们本可以做得更好。
不过,最终我们说服了社区,至少留下来的那部分人与我们站到了一起,我们的一些想法尽管与常见的开源方式不同,但是很有价值。其中最重要的是,我们坚持通过强制性的代码审查和对细节的关注来保持高代码质量。
有些项目采用了不同的方式,快速接受代码,然后在提交后清理。Go项目则采用了相反的方式,我们希望在第一时间内确保代码的质量。我相信这是更高效的方式,但这意味着将更多的工作推给了社区,他们需要理解这种价值,否则就会感觉自己不受欢迎。我们在这方面仍然有很多需要学习的地方,但我相信现在已经好多了。
顺便提一下一个不太为人知的细节。Go项目使用过4种不同的内容管理系统:SVN、Perforce、Mercurial,然后是Git。Russ费尽心思保留了所有的历史记录,因此即使到了今天,Git代码库仍包含当初SVN中最早的变更记录。我们都认为保留历史记录是有价值的,我非常感谢他为此付出的辛勤努力。
还有一点,人们经常以为Google可以向Go团队发号施令,实则不然。Google给予了Go非常慷慨的支持,但Go语言的发展方向并不取决于Google。Google拥有一个庞大的内部Go代码库,我们团队使用它来测试和验证发布,但这些代码是从公共代码库导入的。简单来说,Go的核心团队是由Google出资建立的,但他们是独立的。

包管理
Go的包管理过程并不顺利。我认为语言本身的包设计很优秀,而且在最初一两年中,我们耗费了大量时间讨论。我前面提到的SPLASH演讲详细解释了Go的包管理,如果你感兴趣的话,可以看看。
一个关键点是在import语句中使用普通字符串来指定路径,这种做法很灵活,而且我们相信灵活性很重要。但从只有一个“标准库”到从网上导入代码的过渡并不顺利。
主要有两个问题。
首先,Go团队的早期核心成员都非常熟悉Google的工作方式,包括只使用一个代码库,以及每个人都在主干上构建。但是,Go的包管理器拥有大量包版本,而且解决依赖关系也很难,而我们这方面的经验很欠缺。直到今日,理解这方面的技术复杂性的人仍然寥寥无几,但这并不能成为我们未能从一开始就解决这些问题的借口。我感到非常惭愧,因为我曾是一个类似的项目(该项目旨在为Google的内部构建实现类似的功能)的技术负责人,我本应该意识到我们将要面对的问题。
我构建的deps.dev算是赎罪吧。
其次,虽然与社区合作解决依赖管理问题的想法本意是好的,但当最终的设计出现时,即使有大量的文档和理论说明,社区中的许多人仍感觉被怠慢了。
吃一堑长一智,Go团队学会了如何真正与社区合作,而且结果也有了很多改善。
然而,如今情况已经稳定下来了,而且从技术的角度来看,我们的设计很优秀,而且大多数用户都很满意。只是这中间花费的时间太长,一波三折。

文档和示例
文档也是一个不足的方面。我们写了很多文档,而且自以为很不错,但很快我们就发现,社区想要的文档的深度与我们的预期有一定的差距。
其中一个关键的缺失部分是最基本的功能示例。我们以为,只需说明这个东西是干什么的。我们花费了很长时间才明白展示如何使用更有价值。
不过,我们已经吸取了教训。如今我们的文档中有大量示例,主要由开源贡献者提供。此外,我们在很早的时候就确保这些示例可在Web上执行了。我在2012年的Google I/O大会上展示了并发,Andrew Gerrand编写了一小段Web代码,你可以直接在浏览器中运行这段代码。我认为这是直接在浏览器中运行代码的首例,因为Go是一种编译语言,许多人从未见过这种技巧。后来,这种技术被应用到了博客和在线包的文档中。
更重要的是,我们将这个技术部署到了Go Playground,所有人都可以在这个免费的公开沙盒中尝试Go,甚至可以开发代码。

总结
回首间,我们已走过漫漫长路。
如今看来,很多方面我们做的很正确,也因此成就了Go。但还有很多方面本可以做得更好,重要的是认识到这些错误,并从中吸取教训。对于管理重大开源项目的人来说,我们和社区双方的经验教训都值得借鉴。
我希望说明这些教训及其原因能有所帮助,或许对于那些对我们的工作内容以及方式持有异议的人来说,这也可以作为一种道歉或解释。
此时此刻,距离Go的发布已经过去了14年了。总的来说,结果还算不错。
很大一部分原因是我们从编写软件的方式来考虑Go的设计和开发,而不仅仅是作为一种编程语言,这些决策让我们抵达了新的彼岸。
我们能有此成就,主要原因如下:
  • 强大的标准库,实现了编写服务器代码所需的大部分基础功能。
  • 并发成为Go语言的一等组件。
  • 基于组合而非继承的方式。
  • 表明依赖管理的打包模型。
  • 集成的快速构建和测试工具。
  • 格的统一格式化。
  • 可读性高于巧妙性。
  • 保证兼容性。
最重要的是,我们拥有极其强大且多样化的Gopher社区的支持。
无论出自何人之手,Go代码及其工作方式都是一样的;基本上没有人使用不同的子集,无论经过多长时间,代码都可以正常编译和运行,这也许是最有趣的结果之一,在主流编程语言之中可能也是首例。
这一点,我们确实做对了。