本内容是Go项目负责人Russ Cox 2023年10月在 GopherCon 上发表的演讲[1] (后面重新录制)的摘要与记录.
主要内容是讲述为什么 Go 必须随着时间的推移而改变,以及为何加入遥测是重要且合适的
本次演讲不是关于Go某些特定的更改,而是修改的整体过程,特别是Go Team如何决定进行哪些更改.
显而易见,第一个问题是为什么Go 需要改变?为什么不能对 Go 感到满意而不管它呢?
简单的答案是,我们并不总能一次就把事情做好, 就像这张第一只毛绒 Go gopher 与最终在GopherCon上发布的最终版本相比(差异很大)一样.
但还有一个更深层次的答案,我的一位前同事多年来一直使用电子邮件,签名引用生物学家和科幻小说作家杰克·科恩的一句话
我们用生物学家这个特殊的词来表示‘稳定’. ” 这个词就是“死”.
一切有生命的东西都会发生变化,适应新的环境,修复损伤,等等.
编程语言也需要改变. 除非我们希望 Go 消亡,否则它需要适应新的环境,例如新协议、新操作系统和新的重要用例.
我们需要修复我们发现的错误,以及语言、库和生态系统的问题. 这些问题只有随着时间的推移,或只有在 Go 达到"特定年龄"或规模后才会变得明显.
Go 必须做出改变才能改进并跟上. 这次演讲就是关于我们如何 决定进行哪些更改.
本次演讲分为三个部分:
第一部分是关于我们想要和不想要 Go 进行哪些类型的更改.
第二部分是关于我们如何依据数据来决定进行哪些更改.
第三部分是关于我们计划将 选择性遥测 添加到 Go 工具链中,以能更好地了解 Go 的使用方式以及何时出现异常行为.
在演讲结束时,将了解我们思考和决定对 Go 进行更改的过程,将了解数据对于做出这些决策的重要性,我希望你将了解为什么选择加入遥测是一个很好的额外的数据来源,甚至可能愿意在(遥测)系统推出时选择加入.
让我们从这里开始:我们希望 Go 做出什么样的改变?
我们如不能在这个基本点上达成一致,显然也无法就具体的改变彼此认同. 例如,我们是否应该向 Go 添加一条 Perl 语句来支持我们可以用 Perl 编写函数?
(爽哥备注: 这个 驼身鼠面像 有意思)
我认为不应该这样做,但假设你不同意. 为了解决这个问题,需要了解“为什么”我们不同意.
John Ousterhout 写了一篇精彩的文档,题为开放决策,讲述了他经营初创公司的经验. 这也几乎完全适用于开源项目的工作.
爽哥备注:
关于 John Ousterhout [2], 更多请查看xx百科
Open Decision-Making[3] 全文
他提出的最重要的观点之一是: 如果一群聪明人都用相同的信息来看待相同的问题,并且如果他们有相同的目标,那么他们很可能会得出相同的结论.
如果你和我对于将 Perl 嵌入 Go 存在分歧,那么根本原因几乎可以肯定是我们对 Go 有不同的目标.
所以我们必须确定 Go 的目标是什么. Go 的目标是(致力于构造)更好的软件工程,尤其是大规模的软件工程. 几乎所有 Go 独特的设计决策都是为了这一目标. 我们经常说过这一点,包括在这两篇文章中.
那么 Perl(的目标) 呢?
二十年前,当我还年轻天真的时候,Go 还不存在,我编写并部署了一个完全用 Perl 编写的重要分布式系统. 我喜欢 Perl,但它的目标并不是面向更好的软件工程.
如果我们不同意这一点,那么也许我应该定义软件工程的含义.
Software engineering is what happens to programming when you did time and other programmers.
这句话是一种幽默的说法,用来描述随着时间的推移和加班,编程逐渐演变成了软件工程. 它包含了对编程和软件工程之间差异的一种看法.
编程通常指的是开发人员将代码编写成可执行的程序的过程. 这个过程可能是个人项目或小规模团队的工作,重点在于实现特定的功能或解决特定的问题.
然而,随着软件项目的复杂性和规模的增加,就需要更多的组织、规划和管理来确保项目的成功. 这就是软件工程的范畴. 软件工程强调系统化的方法和最佳实践,包括需求分析、设计、测试、部署和维护等方面,以确保软件项目的质量、可维护性和可扩展性.
所以,这句话的意思是,随着时间的推移和加班,编程不再只是简单的代码编写,而是演变为需要更多软件工程技能和实践的过程. 这反映了软件开发的复杂性和对项目管理的需求.
[Go语言实战: 编写可维护Go语言代码建议](http://www.hzhcontrols.com/new-1416736. html "Go语言实战: 编写可维护Go语言代码建议")
在我看来,软件工程就是你和其他程序员花费时间在编程上所发生的事情.
编程意味着让程序运行. 你有一个问题需要解决,你编写了一些代码,运行它,调试它,得到答案,你就完成了. 这就是编程,而且这已经够困难的了.
爽哥备注: 按我理解,Go是一种不强调个人风格的代码,强调同一,更适合团队协作. Go不是侠客手中单兵作战的可以玩出话的武器,而是军团方阵的制式规格统一的长矛
Go is a code that does not emphasize personal style, but emphasizes unity, and is more suitable for teamwork. Go is not a weapon in the hands of a knight that can be used to fight alone, but a unified standard and specification of the army square.
但是,当该代码必须日复一日地继续工作(即使有其他人在处理它)时,会发生什么呢?
然后,你需要添加测试,以确保你所修复的错误不会在以后重新引入,不会在六个月后由你重新引入,也不会由不熟悉代码的新团队成员重新引入.
这就是为什么 Go从第一天起就为测试提供内置支持,也是为什么我们建立了一种始终通过任何错误修复或添加的新代码来添加测试的文化.
即使 Go 发生变化,代码也必须年复一年地工作时会发生什么?
然后我们需要强调兼容性,这是 Go 自 Go 1 以来就具备的功能. 事实上,Go 1.21 带来了许多兼容性改进,我在 2022 年的 GopherCon 上预览了这些改进.
当你有大量代码并且需要某种全局清理时会发生什么?
你需要工具,而不可避免的第一个障碍是这些工具需要进行模仿其编辑代码的格式/样式的更改,以避免虚假差异.
Gofmt 的存在是为了支持goimports、gorename、gofix 和 gopls 等工具,并且开发者可以使用我们提供的软件包编写自定义工具
说到这里,当你使用其他人提供的包时,不可避免的第一个问题是多个人会编写具有相同名称的包,例如 sqlite 或 yaml. (爽哥备注: 比如两个第三方库同名,都utils)
我们如何确定在给定程序中使用哪一个?Go 的导入路径是URL,以便以明确的方式回答该问题.
随着时间的推移,接下来的问题是选择要使用的特定包的版本,并确定该版本是否 适用于所有其他依赖项. 这就是 Go 提供modules、workspaces、mirror和checksum数据库的原因.
接下来的问题是每个人的代码都有错误,包括安全错误.
你需要找出最重要的错误,以便知道要更新到哪个已修复的版本. 这就是我们添加 Go漏洞数据库(Go vulnerability database)和 govulncheck 的原因,Julie 在 GopherCon 上也谈到了这一点.
这些都是大例子,但也有一些小case,例如添加 HTTP/3 等新协议、删除对过时平台的支持,以及修复或弃用容易出错的 API以避免常见错误,尤其是在大型代码库中.
让我们进入 Go 提案流程[4]---这是我们决定接受哪些更改和拒绝哪些更改的方式.
当我们思考这些决策时,就会发现使用数据对于达成共识非常重要.
简而言之,任何人都可以在 Go 的 GitHub 问题跟踪器上提交 Go 更改提案. 然后就这个问题进行讨论,我们试图在参与者之间就是否接受或拒绝该提案,或者如何更改它以使其能够被接受达成共识. 随着时间的推移,我们逐渐认识到John Ousterhout第二句话的重要性.
当研究问题的人有共同的目标并且共享信息时,就有可能达成共识.
在 Go 的早期,只有我们几个人在做决定. 我们基于过去的技术经验和直觉做出判断. 这些经验就是我们使用的信息. 大多数时候我们都能达成共识,因为我们过去的经历有足够的重叠. 大多数小型项目都是这样工作的.
而随着决策扩展到更多人,共享经验就不再多,我们就需要一个新的共享信息来源. 我们发现的最佳来源是收集实际数据,然后使该数据成为我们使用的共享信息.
但我们从哪里获取这些数据呢?
对于 Go,我们有许多潜在的来源,每个来源都适合特定类型的决策.
让我向你展示其中的几个.
数据来源之一是与 *Talking to Users*[5]. 我们通过多种方式做到这一点. 首先是 Go 用户调查,自 2016 年以来我们每年都会进行这项调查,最近开始每年进行两次.
该调查有助于了解 Go 最流行的用途以及使用者面临的最常见问题.
多年来,最常见的问题是缺乏依赖管理和泛型. 我们使用这些信息来优先开发Go Modules,然后是泛型.
另一个数据来源是我们可以使用 VSCode Go 插件在 VSCode 内运行的调查. 这些调查帮助我们了解VSCode Go 体验的效果如何.
数据的最终来源直接来自Talking to Users和(VSCode的)用户体验调查
另一个数据源是阅读代码:我们可以分析已发布的开源的 Go 项目.
例如,在添加新的“go vet”检查之前,我们在开源语料库的子集上运行它,然后读取结果的随机样本,看看该检查是否指出了真正的问题,以及是否有太多的误报.
对于 Go 1.22,我们计划添加一个“go vet”检查,以检测对附加任何append内容的调用.
爽哥备注:
相视一笑...这个issue&代码实现是我做的,想不到居然提到了
cmd/vet: add a new analyzer for check missing values after append[6]
(省略了相关解释,请看上面两图代码)
由于我们采样的所有标记的代码片段都是有问题的或是彻底错误的,因此我们决定添加该检查. 数据在这里比直觉要好得多.
所有这些方法都适用于对少量样本进行代码分析,我喜欢阅读100示例,这只是世界上所有 Go 代码的一小部分.
上一次 Go 开发者调查显示,全球大约 300 万 Go 开发者中有不到 6,000 名受访者,不到 1%.
一个很好的问题是,为什么这些微小的分数能够告诉我们有关他们所来自的更大人群的任何信息.
答案是,抽样精度仅取决于样本数量,而不取决于总体总体有多大.
乍一看这似乎违反直觉,但假设我有一个装有一百万只地鼠的大盒子,我随机取出其中两只.
首先我得到一只蓝色地鼠,然后我得到了一只粉红色的地鼠. 然后根据这两个样本,我估计盒子大约一半是蓝色,一半是粉红色.
如果告诉你这个盒子是等份的粉色、蓝色和灰色的,是否感到多么惊讶?
并不要太惊讶. 如果盒子是粉色、蓝色和灰色的三分之一,那么这九对中每一对的可能性都是相等的,得到一只非灰色地鼠的几率是 2/3,得到两只的几率是 2/3 平方或 4/9. 几乎有一半的时间看不到灰色地鼠
现在假设我拿出 100 个,其中有 48 个蓝色和 52 个粉色.
我再次估计盒子大约一半是蓝色,一半是粉红色. 现在,如果我告诉你这个盒子是粉色、蓝色和灰色的,你会感到多么惊讶?
应该会感到更加惊讶. 事实上,你根本不应该相信我. 如果这是真的,那么连续获得 100 个非灰色的机会是 2/3 的 100 次方,大约是 10 的负 48 次方.
随机机会不可能是正确的解释. 要么我在盒子上撒了谎,要么我没有随机把它们拿出来,也许所有的灰色地鼠都在底部,而我取得还不够深.
请注意,这一切都不取决于盒子里有多少地鼠,这仅取决于我们拿出多少.
特定预测准确性的数学更为复杂,但它具有相同的效果:
只有样本数量重要,而不是盒子中地鼠的数量.
一般来说,数学太难手工计算,所以你可以在我的博客[7]上找到一张表格.
如果你采取一百个样本并根据这些样本估计百分比,那么你的估计值在 90% 的情况下将在真实百分比的 8% 范围内,而 99% 的情况下将在13% 范围内. 如果你有 5,000 个样本(例如 Go 调查),则90% 的情况下估算值将在 1% 之内,99% 的情况下将在 2% 之内. 需要注意的是,样本确实需要是随机的,或者至少与你的估计不相关,不能只从盒子顶部取出地鼠,然后对整个盒子进行断言.
如果你避免了这种错误,那么当你尝试估计新的 API 是否有用或特定的vet检查是否值得时,花一个小时左右的时间手动检查 100 个样本是合理的. 如果这是一个坏主意,很快就会显现出来. 如果这看起来是个好主意,那么再花几个小时手动或使用程序检查更多样本,将大大提高你的估计. 与做出错误决定的成本相比,这是一个非常小的成本
简而言之,抽样的神奇之处在于抽样将许多一次性估计变成了可以手动或使用少量数据完成的工作. 这就是为什么我们已经看到的所有数据源都能够很好地代表整个 Go 开发者群体
让我进入了演讲的第三部分:
Go 工具链中的遥测.
遥测也将是一个小样本的Go开发人员使用情况(获取),但它应该是具有代表性的样本,并且它回答的问题与上面的两种方式(调查和代码分析)不同.
遥测始终是一个有争议的话题,尤其是对于开源项目来说,所以让我从最重要的细节开始:上传遥测报告完全是自愿和选择加入的.
如果你不运行明确的命令来选择加入该数据收集,则不会上传任何数据. 这也不是那种上传你所做的一切详细痕迹的遥测系统.
爽哥备注: 之前曾因此引发过一些争议. 社区反对的意见居多,但Go团队最终决定添加该功能. 这其实就是作为语言使用者和语言开发者之间,立场和目标不同导致的正常分歧. 我个人比较支持加入该功能,只要是否上报相关数据的选择权绝对在我.
telemetry in the Go toolchain[8]
Transparent Telemetry[9]
此遥测也仅适用于我们作为 Go 发行版的一部分分发的命令,例如 gopls、go 命令、编译器.
它不会进入你构建的任何程序. 在我更详细地描述了该系统之后,我希望你会发现会很乐意加入此遥测系统.
事实上,我们给自己的主要设计约束是使遥测系统 成为大家乐意加入的. 正如我在 2023 年 11 月记录的那样,(遥测)系统刚刚开始运行,只有少数人被要求在 VSCode Go 内选择加入 gopls 遥测. 所以总的来说,你今天无法选择加入,但希望很快你就能够做到.
在我们深入了解细节之前,遥测的动机是它提供了与调查和代码分析不同的信息. 它提供的主要两个类别是使用信息和破损信息.
调查让我们可以询问有关 Go 使用情况的广泛问题,但它们不太适合提供详细的使用信息. 因为这些问题实在太多了,而且对90%的受访者来说,对其中的问题回答“不”也是浪费时间.
这张幻灯片显示了我们从 Go 中删除的内容列表,此前我们对早期版本中的删除内容发出了警告. 列表中的最后一项 buildmode=shared
是我们试图删除的内容,
但在宣布删除后,至少有一个用户表示反对,因此我们将其保留了下来.
buildmode=shared
基本无法适用于Go Modules,因此它似乎不太可能有多少实际用途. 但我们没有数据,所以它只能停留在代码库中.
遥测可以为我们提供基本的使用信息,以便我们可以根据数据而不是猜测做出这些决定.
另一个重要类别是破损信息. 如果 Go 工具链明显损坏,我们希望在 GitHub 上获得错误报告. 但 Go 工具链可能会以用户没有注意到的微妙方式被破坏.
一个例子是,macOS 上的 Go 1.14 到 Go 1.19 意外附带了使用非默认编译标志预先构建的标准库包二进制文件. 这使得它们看起来已经过时了,这使得 Go 命令在运行时重新编译它们,这意味着如果你的程序导入了 net,则需要 Xcode 中的 C 编译器来构建该程序. 而我们希望 Go 能够自行构建纯 Go 程序,而不需要其他工具链.
爽哥备注:
这其实和静态编译&动态编译相关,
对于Go,如果没有使用CGO,没有用到os/user
库和net
库,那100%为静态编译。 有这两个库,直接go build的话,会是动态链接
如果用到了这两个库,也可以通过 CGO_ENABLED=0
方式(最好加上标志位--ldflags='-extldflags=-static'),强制静态编译
这是因为os/user库和net库用到了gliab库(或者musl 的liab库),但还全部都是Go代码(即 纯Go代码)。这种情况可以强制CGO_ENABLED=0,这样可以正常静态编译。
而如果确实用到了CGO,即调用了C的什么(普通应用)程序,如计算曲线面积的,做什么其他东西的,即代码中包含 import "C"和 include,则一定是动态编译。强制用 CGO_ENABLED=0
编译会报错,是编译不过的
所以需要 Xcode 是一个错误. 但我们没有注意到,也没有用户在 GitHub 上报告此事. 任何遇到此弹出窗口的人显然都只是安装了 Xcode并继续了他们一天(的工作).
而遥测可以提供基本的性能指标,例如标准库缓存命中率,以便 Go 工具链开发人员注意到这个问题,即使用户没有注意到.
另一个例子是内部编译器崩溃. Go 编译器不会在程序出现第一个错误时停止,它会继续前进,尝试查找并报告尽可能多的不同错误. 但有时继续分析具有已知错误的程序会 导致意外的恐慌. 我们不想向用户展示这样的崩溃.
相反,编译器会从恐慌中恢复,并且只报告它已经发现的错误. 这样,Go 用户就可以纠正这些错误,这也可能会纠正隐藏的恐慌. 用户的工作不会因看到编译器崩溃而中断. 这对用户来说是件好事,但 Go 工具链开发人员仍然想了解崩溃情况并修复错误.
遥测可以确保我们发现错误,即使用户没有发现.
为了收集使用情况和损坏信息,Go 遥测设计会记录“计数器和崩溃”. Go 工具链程序(例如go 命令、Go compiler或 gopls)可以定义命名事件计数器,然后在事件发生时递增计数器. 事件也可以这些计数器在本地磁盘文件中一次维护一周.
在幻灯片上,gopls 和其他工具正在向"每周的文件"写入计数器. 每周一次,Go 工具链中的上传程序将从遥测服务器获取“上传配置”,其中列出了该周收集的特定事件名称.
只有在特定于遥测的提案审核流程中达成共识后,才会更改该配置. 该配置作为一个模块来保护下载的完整性并保留过去配置的公共记录. 然后,上传者仅上传配置中列出的计数器.
在幻灯片上,上传者仅向 gopl 发回一份报告,其中只有几个计数器,尽管磁盘上可能有更多计数器. 在报告中,有关于哪些编辑器正在使用 gopls以及完成请求的延迟的统计信息,并且有一个发生过一次的 gopls/bug 事件并包含堆栈跟踪.
请注意,上传中根本没有事件跟踪或任何用户数据,只有计数、公共上传配置中已列出的事件名称以及Go 工具链程序内部的函数名称.
另请注意,堆栈跟踪不包含函数的任何参数,仅包含函数名称,因此不包含用户数据.
开源遥测可能会在有权访问数据的人和无权访问数据的人之间造成信息不平衡. 我们希望避免这种情况. 请记住奥斯特豪特规则:
为了达成共识,我们需要每个人都拥有相同的信息.
由于 Go 的遥测上传不包含任何敏感数据,并且是在明确选择同意的情况下收集的 ,因此我们可以重新发布完整的这些报告,以便任何人都可以进行他们想要的任何数据分析. 我们还将发布用于做出决策的基本图表.
我们唯一可能看到的、我们不会重新发布的内容是报告来自哪些 IP 地址,并且我们的服务器确实会在报告中记录该信息.
一个明显的问题是是否有足够多的人会选择遥测以使数据足够准确以做出决策.
幸运的是,采样的魔力在这里有所帮助. 全球约有 300 万Go 开发者. 当(遥测)系统准备就绪并且我们要求人们启用遥测时,即使0.1%的选择加入率,也将是 3,000 名开发者,我们的图表显示,在99% 的置信度下,错误率低于 3%; 如果世界上0.67%的 Go开发人员启用遥测,则样本数将达到 20,000 个,误差率低于 1%,置信度为 99%.
除此之外,我们真的不需要更多样本. 如果我们持续收到更多报告,还可以调整上传配置,告诉系统随机选择在给定的一周内不上传任何内容. 例如,如果有200,000 个系统选择加入,我们可以告诉每个系统在任意给定的一周内以 10% 的概率上传.
因此,即使我们预计选择加入率较低,该系统也应该运行良好,并且随着选择加入率的上升,Go 遥测将从任何给定系统收集的数据减少. 当然,这使得每个选择加入的人对我们来说都更加重要.
Go 遥测在很大程度上还没有准备好供你们选择加入,但当它准备好时,我希望你们会这样做.
结束语,这就是我希望你从这次演讲中得到的收获.
首先,Go 需要不断变化,尤其是当它周围的计算世界发生变化时.
其次,任何改变的目标都是让 Go 更好地用于软件工程,尤其是大规模的软件工程.
第三,一旦我们就目标达成一致,就任何变革达成共识的下一个最重要的部分就是共享数据作为决策的基础.
第四,Go 工具链遥测是补充我们现有调查和代码分析数据的重要数据来源.
最后,虽然整个演讲都是关于数据和适当的统计数据,但我们正在评估的想法、假设 和潜在的变化总是从个人story和对话开始. 我们乐于听这些story,并与大家讨论如何使用 Go、什么时候有效、什么情况无效.
因此,无论在什么情况下,如果你正在参加会议、在邮件列表中或在问题跟踪器上,请务必让我们知道 Go 对你的效果如何,以及哪些方面不起作用. 我们总是喜欢听到这个. 非常感谢.
GopherCon 上发表的演讲: https://research.swtch.com/gochanges
[2]John Ousterhout : https://web.stanford.edu/~ouster/cgi-bin/home.php
[3]Open Decision-Making: https://web.stanford.edu/~ouster/cgi-bin/decisions.php
[4]Go 提案流程: https://go.dev/s/proposal
[5]Talking to Users: https://go.dev/blog/survey2021-results
[6]cmd/vet: add a new analyzer for check missing values after append: https://github.com/golang/go/issues/60448
[7]我的博客: https://research.swtch.com/sample
[8]telemetry in the Go toolchain: https://github.com/golang/go/discussions/58409
[9]Transparent Telemetry: https://research.swtch.com/telemetry
本文由 mdnice 多平台发布