2012 年 Google 重新格式化 10 万个文件的故事

神秘的会面

2012 年 9 月,我当时是谷歌的一名初级工程师,负责 巴塞尔 (Google 的构建工具,内部称为 Blaze)。有一天,我的收件箱里收到了一封神秘的日历邀请。这是由美国的两名工程师发送的,我和我的团队负责人一起收到了邀请。

我很快就认出了他们的名字:Rob Pike 和 Russ Cox。虽然我没有和他们合作过,但我对他们的名声很熟悉: 拉斯·考克斯 因为我喜欢读他的博客文章, 罗布·派克 因为,嗯……他很有名。在会议期间,Rob 和 Russ 分享了他们的雄心勃勃的计划:重新格式化 Google 代码库中的每个 Bazel BUILD 文件,并使用预提交脚本强制执行此格式。

代码格式

当时,代码格式化程序还不那么常见。Python 的 Black、Clang Format 或 Prettier 等流行工具还不存在。到处强制执行的格式化程序的主要示例是 戈夫姆特。由于 Russ 和 Rob 在 Go 团队工作,他们希望在 Bazel 的 BUILD 文件中复制这一成功。

BUILD 文件的格式不一致,混合了各种缩进样式,没有标准指南。复制粘贴的代码块通常很容易被发现,因为它们的格式很突出。过去关于样式指南的讨论表明,工程师之间存在很多分歧。之前编写格式化工具的尝试被采用的程度有限。它存在一些问题,很难被采用。

Russ 已经使用 Go 开发了该工具的新版本,称为 Buildifier。 编写格式化程序可能非常困难 在某些情况下,但复杂性取决于以下几点:

  • 如何处理评论?Buildifier 将评论附加到 语法树 (数据结构不是超细粒度的,因此在某些情况下,Buildifier 会移动注释)。
  • 如何分割行?Buildifier 在一些硬编码情况下会分割行,但它并不关心行的长度。这是一个重大的简化。
  • 如何保留现有的行分割?Buildifier 在语法树中保留了其中一些, 在一些特定的地方
  • 我们需要部分格式化吗?Buildifier 总是重新格式化整个文件,而不仅仅是被修改的行。

由于设计选择,Buildifier 相当简单。实施看起来很有希望,但我们必须考虑如何推广它。Russ 在整个代码库上对其进行了测试,并能够在几分钟内重新格式化所有文件。他请求我们允许推广该工具并将其作为提交前检查来执行。

重新格式化每个 BUILD 文件的想法听起来很疯狂,而且非常具有破坏性。每天大约有 10,000 名工程师在代码库中更改 BUILD 文件,强制执行严格的格式规则似乎很可怕。我们能否拒绝每个与输出字节不匹配的提交?我们能否放宽提交前规则或让工具可选择加入?Russ 坚持说:是的,必须对每个人都严格执行。没有例外,工具中没有设置,没有个人偏好。

几天后,该计划获得正式批准。

推出

我在几个地方帮助了推广。例如,我修改了语法以使构建语言形式化,并编写了样式指南——因此我一劳永逸地决定了 BUILD 文件应该是什么样子。我还需要记录哪些列表可以安全地重新排序,因为 Buildifier 的一个显着功能是它能够在某些情况下对列表进行排序(例如,在构建中,源文件列表有时与顺序无关)。

当然,我们必须将 Buildifier 集成到每个主要的代码编辑器中。如果代码在保存时格式化,那么在尝试提交代码时就不会出现意外。在代码库中,一些工具会生成 BUILD 文件。工具不应尝试自行生成格式化的代码;它们应该将其委托给格式化工具,格式化工具充当事实来源,并且可能会随时间而变化。

如何测试格式化程序是否不会破坏代码?第一个想法是比较语法树,但当我们重新排序列表项时,这种方法不起作用。Bazel 有一个非常方便的功能,称为 Bazel Query,它可以输出有关包的信息。我教过 Bazel 哪些列表是与顺序无关的,所以我们可以使用 Bazel Query 来检查重新格式化是否不会影响 Bazel 对文件的理解。最重要的是,Google 有一个不错的测试基础设施。当然,在这种更改之后运行测试是可能的(但速度很慢,计算成本很高)。

如何提交对 100,000 多个文件的更改?在这种规模下,许多工具都无法正常工作。因此,Google 开发了一个工具,可以将大更改拆分为可以独立提交的小更改。如果发生冲突,该工具会根据指示还原任何有冲突的文件。为了绕过代码审批,更改会被发送给“全局审批者”,该审批者可以在 Google 代码库的任何地方批准更改。

后果

与我的预期相反,这件事相对平淡无奇。几乎没有人抱怨新的要求。几乎没有人抱怨新的格式风格。

我记得之前关于缩进、括号位置等的讨论非常冗长,没有达成任何共识。然而,当 Buildifier 推出时,人们实际上并不关心样式决定。他们只是喜欢统一性。

“格式化问题本不应该存在,我们非常关心这个问题,愿意花费自己的工程时间来消除它。我明白,自动格式化似乎不会带来重大改变。直到我们从 Go 代码审查和代码编辑流程中消除了它,我才真正理解它。希望在六个月或一年后,当一切都转换完毕,我们回顾这一点时,回想起来,它的好处会更加明显。”—— Russ Cox

那么,这值得吗?是的,一千次。这次经历让我认识到了统一性的价值以及自动化工具提高生产力的威力。

重新格式化的好处很快就显现出来了。Buildifier 不仅重新格式化了我们的 BUILD 文件;它从根本上改变了我们的代码维护和大规模更改方法。在使用 Buildifier 之前,对 BUILD 文件进行大规模更改非常困难。重构工具试图检测并匹配现有的格式样式,但它们无法很好地工作(特别是在存在代码注释和多行表达式的情况下),并且在代码审查中会遇到很多阻力。使用 Buildifier,此类更改变得很常见。这实现了 Bazel 中许多以前被认为不可能实现的增强功能。这使我们能够修复遗留的设计问题。

特别是,这使得我以后可以将 Bazel 构建语言从 Python 替换为 Starlark。

更新

  • 我们还要感谢编写 Buildifier 原始版本并帮助过渡和维护新版本的 Nilton Volpato。
  • 为什么不在不更新所有文件的情况下启用新格式化程序?这样一来,每当有人修改文件时,就会产生很长的差异,使得更改难以审查。成本将分摊给公司的所有工程师。
  • 在迁移开始时文件数量实际上是 193k,迁移结束时文件数量是 216k。此类迁移必须同时处理代码库的增长。
  • 那是 2012 年。标题之前写的是 2011 年。

    讨论

    评论已关闭,但其他地方有关于该文章的讨论,例如

  • HackerNews 上的讨论。特别是,Russ Cox (rsc) 添加了更多背景信息。
  • Reddit 上的讨论
  • lobste.rs 上的讨论

Leave a Reply

Your email address will not be published. Required fields are marked *

近期新闻​

编辑精选​