向 Go 2 进发

未匹配的标注

本文为官方 Go Blog 的中文翻译,详见 翻译说明

Russ Cox
2017年7月13日

介绍

[这是我在Gophercon 2017的 我今日之演讲 的讲稿文本,我们与整个 Go 社区讨论关于 Go 2 的计划。]

2007年9月25日,在 Rob Pike,Robert Griesemer和Ken Thompson讨论了一种新的编程语言几天之后,Rob提出了“ Go ”这个名字。

第二年,Ian Lance Taylor和我加入了团队,我们五个人共同构建了两个编译器和一个标准库,最终在2009年11月10日发布了 开源发布

接下来的两年中,在新的Go开源社区的帮助下,我们尝试了各种改进,完善了Go并制定了 Go 1计划 ,在2011年10月5日发布。


在 Go 社区的帮助下,我们修订并实施了该计划,最终于2012年3月28日 发布了Go 1

Go 1 的发布标志着将近五年的极富创造性的、疯狂的努力达到了顶峰,这使我们从名称和一堆想法变成了稳定的可供生产环境使用的编程语言。这也标志着从变化和流失到稳定的明显转变。

在开发 Go 1 的那些年中,我们改变了 Go ,几乎每周都打破 Go 的标准。我们了解到,这使 Go 不能在生产环境中使用,因为生产环境无法每周重写程序以适应语言变化。正如 宣布 Go 1 发布的博客文章 所说的那样,其驱动动机是为创建可靠的产品、项目和出版物 (博客,教程,会议演讲和书籍)提供稳定的基础。使用户确信自己的程序在未来几年内将继续编译并运行而无需更改。

Go 1 发布后,我们知道我们需要花时间去为生产环境优化 Go。我们明确地从改变语言转变为在自己的项目中使用 Go 并改进实现:我们将 Go 移植到许多新系统上,我们重写了几乎所有对性能至关重要的部分,以使 Go 更加高效地运行,并添加了诸如 竞态探测器

现在,我们已有5年使用Go来构建大型,生产质量系统的经验。我们已经了解了什么有效,什么无效。现在是时候开始 Go 的发展和增长的下一步,计划 Go 的未来了。我今天在这里请求所有 Go 社区中的所有人,无论您是 GopherCon 的听众还是今天晚些时候观看视频或阅读 Go 博客的观众,在我们计划和实施 Go 2 时与我们一起工作。

在本讲的其余部分,我将解释 Go 2 的目标;我们的制约和局限;整个向 Go 2 前进的过程;写下使用我们使用 Go 的经验,尤其是与我们可能尝试解决的问题有关;可能的解决方案;我们将如何交付 Go 2;以及大家如何提供帮助。

目标

今天,Go的目标与2007年相同。我们希望使程序员更有效地管理两种规模:生产规模,尤其是与许多其他服务器交互的并发系统,今天以云软件为例;以及开发规模,特别是许多工程师松散地协调工作而开发的大型代码库,今天的现代开源开发就是例证。

这些规模出现在各种规模的公司中。即使是五人创业公司,也可能会使用其他公司提供的基于云的大型API服务,并且使用的开源软件要比他们自己编写的软件更多。生产规模和开发规模在该初创公司中的重要性与在Google中一样重要。

Go 2的目标是修复Go无法规模化的最重要的方式。

(有关这些目标的更多信息,请参见Rob Pike在2012年的文章“Go ta Google: 软件工程服务中的语言设计”和我的GopherCon 2015演讲“Go,开源,社区。”)

约束

Go的目标从一开始就没有改变,但是Go的约束肯定已经改变。最重要的限制是现有的Go使用情况。我们估计全世界至少有50万个开发人员,这意味着有数百万个Go源文件和至少十亿行Go代码。这些程序员和源代码代表了Go的成功,但它们也是Go 2的主要限制。

Go 2必须带动所有这些开发人员。我们必须要求他们只有在回报丰厚的情况下才能取消旧习惯,学习新习惯。例如,在Go 1之前,通过错误类型实现的方法被命名为String。在Go 1中,我们将其重命名为Error,以将错误类型与可以自行格式化的其他类型区分开来。前几天,我实现了一个错误类型,并且不加思索第将其方法命名为String而不是Error,该方法当然不会编译。五年后,我仍然还没有完全忘记旧方法。这种澄清的重命名是Go 1中要进行的一项重要更改,但如果没有充分的理由,那么对于Go 2来说就太具破坏性。

Go 2还必须带来所有现有的Go 1源代码。我们决不能分裂Go生态系统。混合程序(其中Go 2编写的包导入Go 1编写的包,反之亦然)必须在多年的过渡期内毫不费力的工作。我们必须确切的知道如何做到这一点。像go fix这样的自动化工具肯定会发挥作用。

为了最大程度地减少中断,每个更改都需要仔细考虑、规划和工具化,从而限制了我们可以进行的更改数量。也许我们可以做两三个,当然不能超过五个。

我没有在统计细微的内务变化,例如可能允许更多的口头语言或添加二进制整数字面量。此类微小更改也很重要,但更容易解决。今天,我将重点放在可能的重大更改上,例如对错误处理的额外支持,引入不可变或只读值,添加某种形式的泛型或尚未建议的其他重要主题。我们只能进行其中的一些重大更改。我们将必须谨慎选择。

####流程

这就提出了一个重要的问题。 Go的开发流程是什么?

在Go的早期,我们只有五个人,我们在一对相邻的共享办公室工作,这些办公室被玻璃墙隔开。将所有人召集到一个办公室讨论一些问题,然后回到我们的办公桌来实施解决方案很容易。在实施过程中出现一些皱纹时,很容易再次召集所有人。罗伯和罗伯特的办公室有一个小沙发和一个白板,所以通常我们当中一个人进来并开始在黑板上写一个例子。通常在示例出现时,其他所有人都在自己的工作中达到了一个很好的停止点,并准备坐下来讨论。这种非正式性显然无法扩展到当今的全球围棋社区。

自从Go的开源发布以来,部分工作是将我们的非正式流程移植到更正式的邮件列表和问题跟踪器以及50万用户中,但是我认为我们从未明确描述过我们的整体流程。我们可能从未有意识地考虑过这一点。但是,回顾一下,我认为这是我们在Go上工作的基本要点,这是自第一个原型运行以来我们一直遵循的过程。

步骤1是使用Go积累经验。

第2步是确定Go可能需要解决的问题,并将其表达出来,向他人解释,然后写下来。

步骤3是提出该问题的解决方案,与他人讨论,然后根据该讨论修改解决方案。

步骤4是实施解决方案,对其进行评估,然后根据该评估对其进行完善。

最后,第5步是交付解决方案,将其添加到人们日常使用的语言,库,工具集中。

同一个人不必针对特定更改执行所有这些步骤。实际上,通常有许多人在任何给定步骤上进行协作,并且可能针对单个问题提出许多解决方案。另外,在任何时候我们都可能意识到我们不希望对某个特定想法走得更远,而是回头一步。

尽管我不相信我们曾经讨论过整个过程,但是我们已经解释了其中的一部分。 2012年,当我们发布Go 1并说现在是时候使用Go并停止对其进行更改时,我们在解释步骤1。在2015年,当我们介绍Go更改提案流程时,我们在解释步骤3、4和3。 5.但是我们从未详细解释过第2步,所以我现在想做。

(有关Go 1的开发以及如何摆脱语言变化的更多信息,请参见Rob Pike和Andrew Gerrand在OSCON 2012上发表的演讲“ [The Go to Go 1](《Go Blog 中文翻译》 -go-1)。有关提案流程的更多信息,请参阅安德鲁·格朗德(Andrew Gerrand)在GopherCon 2015上发表的演讲“ How Go was Made”和“ 提案流程文档

####解释问题

有两个部分来解释问题。第一部分-较容易的部分-确切说明问题所在。我们的开发人员非常擅长于此。毕竟,我们编写的每项测试都是要解决的问题的陈述,其语言如此精确,甚至连计算机都可以理解。第二部分-较难的部分-很好地描述了问题的重要性,每个人都可以理解为什么我们应该花时间解决问题并维护解决方案。与精确地陈述问题相反,我们不需要经常描述问题的重要性,而且我们也不擅长于描述问题。计算机从不问我们“为什么这个测试用例很重要?您确定这是您需要解决的问题吗?解决这个问题是您可以做的最重要的事情吗?”也许他们有一天会,但不是今天。

让我们看一下2011年的一个旧示例。这是我在计划Go 1时将os.Error重命名为error.Value的内容。

它以对问题的精确的单行声明开始:在非常低级的库中,所有内容都为os.Error导入“ os”。然后有五行内容,我在这里重点说明了问题的严重性:“ os”使用的程序包本身无法在其API中出现错误,而其他程序包则依赖“ os”,其原因无济于事。做操作系统服务。

这五行是否使确信此问题很严重?这取决于您能否很好地填写我遗漏的上下文:被理解需要预见别人需要知道的内容。对于当时的我的听众-Google的Go团队中的其他十个人正在阅读该文档-五十个单词就足够了。为了在去年秋天向GothamGo的受众群体(背景和专业领域更加多样化的受众群体)提出同样的问题,我需要提供更多的上下文,并且我使用了大约200个单词以及真实的代码示例和图表。当今全球围棋社区的一个事实是,描述任何问题的重要性都需要添加上下文,尤其是通过具体示例进行说明,而与同事交谈时会忽略这些上下文。

说服别人一个重大问题是至关重要的一步。当问题显得无关紧要时,几乎所有解决方案都显得过于昂贵。但是对于一个重大问题,通常有许多合理成本的解决方案。当我们不同意是否采用特定解决方案时,我们通常实际上不同意要解决的问题的重要性。这是如此重要,以至于我想看两个最近的例子,至少在事后看来,这些例子清楚地表明了这一点。

示例:Le秒

我的第一个例子是关于时间的。

假设您想计时事件花费的时间。写下开始时间,运行事件,写下结束时间,然后从结束时间中减去开始时间。如果事件花费了十毫秒,则减法将得出十毫秒的结果,可能加上或减去一个很小的测量误差。

开始:= time.Now()// 3:04:05.000
事件()
结束:= time.Now()// 3:04:05.010

过去:= end.Sub(start)// 10毫秒

这个明显的过程可能会在le秒期间失败。当我们的时钟与地球的日常旋转不完全同步时,便会在午夜之前插入leap秒(正式时间为11:59 pm和60秒)。与leap年不同,leap秒没有遵循可预测的模式,这使其很难适应程序和API。操作系统通常不尝试表示偶尔的61秒分钟,而是通过将时钟调回正好在午夜之前的一秒来实现a秒,从而使11:59 pm和59秒发生两次。此时钟重置使时间似乎向后移动,因此我们的十毫秒事件可能被计时为负990毫秒。

开始:= time.Now()// 11:59:59.995
事件()
结束:= time.Now()// 11:59:59.005(真的11:59:60.005)

过去了:= end.Sub(start)// –990毫秒

由于像这样的时钟重置中的定时事件,时钟时间不准确,因此操作系统现在提供了第二个时钟,即单调时钟,它没有绝对含义,但计数秒数,并且永不重置。

除了在奇数时钟重置期间外,单调时钟并不比每日时钟好,而且每日时钟还具有用于告诉时间的附加好处,因此为简单起见,Go 1的时间API仅公开时间时钟。

2015年10月,一份错误报告指出,Go程序无法在整个时钟重置期间正确计时事件,尤其是典型的leap秒。建议的修补程序也是原始问题标题:“添加新的API以访问单调时钟源。”我认为此问题的重要性不足以证明新API的合理性。几个月前,在2015年中期的and秒中,Akamai,亚马逊和Google整天降低了时钟速度,吸收了额外的秒数而又不倒转时钟。似乎最终广泛采用这种“ leap smear”方法将消除leap秒时钟重置,这是生产系统上的一个问题。相比之下,向Go中添加新的API会带来新的问题:我们将不得不解释两种时钟,教育用户何时使用每种时钟,以及转换许多行现有代码,所有这些都是很少发生且可能合理的问题自行消失。

如果没有明确的解决方案,我们会做我们总是会做的事情:我们等待着。等待使我们有更多的时间来增加经验和对问题的理解,也有更多的时间来寻找好的解决方案。在这种情况下,以一种感激的[Cloudflare的小故障]的形式增加了对问题重要性的理解(www.theregister.co.uk/2017/01/04/c...) 。他们的Go代码在2016年底将DNS请求计时为leap秒,大约为990毫秒,这导致其服务器同时出现恐慌,高峰时打破了0.2%的DNS查询。

Cloudflare正是Go打算使用的那种云系统,并且由于Go无法正确计时事件,他们发生了生产中断。然后,这是关键点,Cloudflare在John Graham-Cumming的博客文章“ [如何以及为什么experience秒影响Cloudflare DNS]”(blog.cloudflare.com/how-and-通过分...

示例:别名声明

我的第二个示例是在Go中支持别名声明。

在过去的几年中,Google建立了一个致力于大规模代码更改的团队,这意味着API迁移和错误修复适用于我们的数百万个源文件和数十亿行代码的代码库,以C ++,Go,Java,Python和其他语言编写。从该团队的工作中学到的一件事是,当将API从使用一个名称更改为另一个名称时,能够以多个步骤而不是一次全部更新客户端代码的重要性。为此,必须可以编写一个声明,将旧名称的使用转发到新名称。 C ++具有#define,typedef,并使用声明来启用此转发,但是Go没有任何内容。当然,Go的目标之一就是很好地扩展到大型代码库,并且随着Google的Go代码数量的增长,很明显,我们既需要某种转发机制,也需要其他项目和公司来解决此问题。随着他们的Go代码库的增长。

2016年3月,我开始与Robert Griesemer和Rob Pike讨论Go如何处理渐进式代码库更新,并且我们到达了别名声明,这正是所需的转发机制。在这一点上,我对Go的发展方式感到非常满意。我们从Go成立之初就开始谈论别名-实际上,第一个规范草案已经使用别名声明的示例,但是每次我们讨论别名,然后再讨论别名时,我们都没有明确的用例,因此我们将其省略。现在,我们提议添加别名不是因为它们是一个优雅的概念,而是因为它们满足了Go的可扩展软件开发目标,从而解决了一个重大的实际问题。我希望这将成为将来Go更改的模型。

春天晚些时候,罗伯特和罗布(Robert and Rob)撰写了提案,罗伯特在[Gophercon 2016闪电演讲](https:/// www。 youtube.com/watch?v=t-w6MyI2qlU)。接下来的几个月进展并不顺利,它们绝对不是未来Go变革的典范。我们吸取的许多教训之一是描述问题重要性的重要性。

在一分钟前,我向您解释了该问题,并提供了有关该问题的产生方式和原因的背景信息,但没有具体示例可以帮助您评估该问题是否会在某种程度上影响您。去年夏天的提案和闪电演讲给出了一个抽象的示例,涉及C,L,L1和C1到Cn包,但没有与开发人员相关的具体示例。结果,社区中的大多数反馈都基于这样的想法,即别名只能解决Google的问题,不能解决其他所有人的问题。

正如Google最初并不了解正确处理leap秒二次重置的重要性一样,我们也没有有效地向更广泛的Go社区传达在大规模变更期间处理渐进代码迁移和修复的重要性。

在秋天,我们从头开始。我使用了talk并写了介绍问题的文章从开源代码库中提取的多个具体示例,展示了这个问题如何在所有地方发生,而不仅仅是在Google内部。现在,更多的人了解了这个问题并看到了它的重要性,我们进行了富有成果的讨论关于哪种解决方案是最好的。结果是类型别名包含在Go 1.9中,这将有助于Go扩展到更大的代码库。

体验报告

这里的教训是,以一种在不同环境中工作的人可以理解的方式来描述问题的重要性是困难的,但至关重要。要讨论Go社区的重大变化,我们将需要特别注意描述我们要解决的任何问题的重要性。最清晰的方法是显示问题如何影响实际程序和实际生产系统,例如 Cloudflare的博客文章我的重构文章

诸如此类的经验报告将一个抽象的问题变成一个具体的问题,并帮助我们理解其重要性。它们还充当测试用例:可以通过检查其对报告所描述的实际问题的评估来评估任何建议的解决方案。

例如,最近我一直在研究泛型,但是我对Go用户需要使用泛型来解决的详细而具体的问题并不清楚。结果,我无法回答一个设计问题,例如是否支持通用方法,也就是说与接收器分开参数化的方法。如果我们有大量的实际用例,则可以通过检查重要的用例来开始回答这样的问题。

再举一个例子,我看到了以各种方式扩展错误接口的建议,但是我还没有看到任何经验报告,这些报告显示了大型Go程序试图完全理解和处理错误,更不用说显示当前错误接口如何阻碍了它。这些尝试。这些报告将帮助我们所有人更好地了解问题的细节和重要性,这是我们在解决问题之前必须做的。

我可以继续。 Go的每项主要潜在变化都应由一份或多份经验报告来激发,这些报告记录了人们今天如何使用Go以及为何效果不够好。对于我们可能会考虑使用Go进行的重大更改,我不了解许多此类报告,尤其是没有以实际示例说明的报告。

这些报告是Go 2提案流程的原始资料,我们需要所有人编写它们,以帮助我们了解您的Go经验。你们中有一半的人在广泛的环境中工作,而我们当中的人并不多。在您自己的博客上撰写帖子,或撰写Medium帖子,或撰写Github Gist(添加一个.md Markdown的文件扩展名),或编写Google文档,或使用您喜欢的任何其他发布机制。发布后,请将帖子添加到我们的新Wiki页面,golang.org/wiki/ExperienceReports

解决方案

现在我们知道了如何识别和解释需要解决的问题,我想简要地指出,并非所有问题都可以通过更改语言来最好地解决,这很好。

我们可能要解决的一个问题是,计算机通常可以在基本算术运算期间计算其他结果,但是Go不能直接访问这些结果。罗伯特(Robert)在2013年提出,我们可以将二结果(“逗号分隔”)表达式的概念扩展到基本算术。例如,如果x和y是uint32值,则lo,hi = x * y将不仅返回乘积的通常低32位,而且还返回高32位。这个问题似乎并不特别严重,因此我们记录了可能的解决方案但未实现。我们等了。

最近,我们为Go 1.9设计了一个数学/位包,其中包含各种位操作功能:

包位//导入“数学/位”

func LeadingZeros32(x uint32)int
func Len32(x uint32)int
func OnesCount32(x uint32)int
func Reverse32(x uint32)uint32
func ReverseBytes32(x uint32)uint32
func RotateLeft32(x uint32,k int)uint32
func TrailingZeros32(x uint32)int
...

该软件包对每个函数都有良好的Go实现,但是编译器在可用时也会替代特殊的硬件指令。基于对数学/位的这种经验,Robert和我现在都认为通过更改语言来提供附加的算术结果是不明智的,相反,我们应该在数学/位之类的程序包中定义适当的函数。最好的解决方案是更改库,而不是更改语言。

在Go 1.0之后,我们可能想解决的另一个问题是,goroutines和共享内存使将种族引入Go程序太容易了,从而导致崩溃和生产中的其他不良行为。基于语言的解决方案将是找到某种方法来禁止数据争用,以使其无法编写或至少不能通过数据争用来编译程序。在编程语言世界中,如何将其适应Go语言仍然是一个悬而未决的问题。取而代之的是,我们在主发行版中添加了一个工具,使其使用起来很简单:该工具种族检测器已成为Go体验中不可或缺的一部分。最好的解决方案是更改运行时和工具,而不是更改语言。

当然,语言也会发生变化,但是并不是所有的问题都能用语言得到最好的解决。

交付Go 2

最后,我们将如何运输和交付Go 2?

我认为最好的计划是,作为Go 1的发布顺序的一部分,逐步地、逐个功能的将Go 2的向后兼容的部分运输。这有几个重要的特性。首先,它使Go 1的发布保持在通常的时间表上,以继续进行用户现在所依赖的及时的错误修复和改进。第二,它避免了Go 1和Go 2之间开发工作的分裂;第三,它避免了Go 1和Go 2之间的分歧,便于大家最终的迁移。第四,它可以让我们一次专注于并交付一个改变,这应该有助于保持质量。第五,这将鼓励我们在设计功能时要向后兼容。

我们需要时间来讨论和规划,然后才会在Go 1的版本中开始实施,但在我看来,一年后的Go 1.12左右,我们可能会看到一些小的改动。这也让我们有时间先把包管理支持落地。

一旦所有的向后兼容工作都完成了,比如说在Go 1.20中,我们就可以在Go 2.0中进行非向后兼容的修改。如果结果发现没有不向后兼容的改动,也许我们就宣布Go 1.20Go 2.0。无论如何,到了那个时候,我们将从Go 1.X的发布顺序过渡到Go 2.X的发布顺序,也许会有一个支持Go 1.X最终版本的扩展窗口。

这都是一些推测,我刚才提到的具体发布数字只是一个临时性的估计,但我想说明的是,我们不会放弃Go 1,事实上,我们会尽可能地把围棋1带到最远。

求助

我们需要你的帮助。

Go 2的对话从今天的开始,这将在公开场合进行,比如邮件列表和问题追踪器等公共论坛上进行。请大家在每一步都要帮助我们。

今天,我们最需要的是体验报告。请告诉我们Go是如何为你工作的。请写一篇博文,包括真实的例子,具体的细节,真实的经验。并将其链接到我们wiki页面。这样我们就会开始讨论我们——Go社区,可能会对Go做出什么样的改变。

谢谢你。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/go-blog/toward-...

译文地址:https://learnku.com/docs/go-blog/toward-...

上一篇 下一篇
Summer
贡献者:4
讨论数量: 0
发起讨论 只看当前版本


暂无话题~