Jane Street 如何用 Iron 审查代码

原标题How Jane Street Reviews Code with Iron

Jane Street 的内部审查工具记录每位审查者实际读过的内容,将反馈保存在代码中,并在发布前执行审查要求。

频道
Jane Street
发布
2017年12月4日
视频
56:06
阅读
11 分钟
幻灯片
5
原文
英语
在 YouTube 打开
往下滚动阅读时,视频会缩到右下角继续播放。字幕跟随阅读语言。
阅读语言共 2 种语言可读
由 AI 从英语原文翻译。打开“对照原文”可以逐段核对。
摘要

30 秒看懂

一位 Jane Street 程序员介绍了基于 Mercurial 的代码审查和发布工具 Iron 背后的设计理念。演讲讲解了合并过程中审查状态如何变化、行内审查评论和分支协作,以及有意保留杂乱历史带来的取舍。

  1. 1用差异记录审查状态Iron 为每位审查者和每个分支关联一个名为 brain 的可变差异。它记录此人接受过的内容,而不依赖于各个提交的标识。
  2. 2审查差异的变化合并或 rebase 后,Iron 会比较已审查的差异与当前分支的差异。这能显示冲突解决结果,而不必重读所有已审查的代码。
  3. 3评论写在代码里审查者直接向分支添加带标记的评论。编辑器集成让他们能从差异跳到源代码,甚至直接修复小问题。
  4. 4审查义务功能审查者明确确认分支后,文件审查者检查各自负责范围内的变更。元数据规定了负责人、审查严格程度和团队审查要求。
  5. 5大量合并带来的代价讲者表示,在大量合并提交中进行普通二分查找很痛苦;按功能分支进行二分查找会有帮助,但尚未实现。

要点建议

  1. 每位审查者的 brain 记录已经审查过的差异,使 Iron 在分支变化后能够识别尚需审查的内容。
  2. 对差异再做差异比较,可以显示合并冲突的解决结果,而无须让审查者重读整个分支。
  3. 审查者直接在代码中写评论,也可以在作者的分支上做小修复。
  4. 针对文件设定的审查义务和严格程度决定了发布前谁必须审查变更。
  5. 嵌套分支、独立工作区和仪表盘支持以分支为中心的审查流程。
  6. 这种方法用整洁的提交历史和便捷的二分查找换取持续审查与协作。
全文

按章节阅读整理后的全文

只看原始文稿
01

Iron 与阅读代码的难题

讲者介绍内部工具,并说明分支变化时审查为何在操作上变得困难。合并和 rebase 让追踪已审查内容更加复杂。

我想介绍 Jane Street 如何审查代码,重点讲我们内部工具 Iron 背后的理念。Iron 兼具版本控制系统和审查工具的特点。我是使用它的程序员,而不是设计者,所以我会侧重使用体验和设计理念,不会把自己当作实现细节的权威。

Iron 是开源的,但公开仓库很难直接用于实际安装:开发在内部基于 Mercurial 的系统中进行,公开发布的代码则呈现为大块差异,安装说明也很少。我并不是要推销这个软件,而是希望其中一些想法能启发其他工具。

我用过的许多审查工具采用类似的模式:以网页和分支为中心,查看补丁并发表评论。Iron 走了另一条路。有些单独的设计选择看起来可能更糟,但当各部分配合起来时,好处就更明显了。

认真阅读代码很难。简单检查代码风格,与充分理解变更并提出改进建议不是一回事。审查的收获取决于投入;但我今天不打算解决理解程序语义的难题,而是聚焦如何在操作上追踪自己读过的内容。

一系列提交可以逐个查看差异,审查起来很直接。麻烦出现在分支发生分叉,有人将上游分支合并进来时。合并提交包含不相关的上游变更,但也可能包含需要审查的冲突解决结果,而这些结果可能带来真正的语义变化。

Rebase 带来另一种问题:旧提交被新提交取代。变更可能等价,但执行 rebase 的人也可以修改它们。审查者要么全部重读,要么信任 rebase 的结果,都不太理想。在 Jane Street,我们总是使用合并,虽然我们称这个操作为 rebase;因此提交历史比较杂乱,而 Iron 围绕分支设计,不追求整洁的单个提交。

无论按文件还是按提交审查,如果审查途中有新变更进来,都会出现类似问题。我希望工具能展示所有变更,包括冲突解决结果,又不用让人重复审查相同内容。在审查进行期间,它应该允许新变更进入,也允许合并上游或进行 rebase。

02

Brain 与差异的差异

Iron 将每位审查者接受过的变更记录为名叫 brain 的差异。通过比较这一状态与分支,可以发现尚未审查的变更,包括合并冲突的解决结果。

Iron 的答案是一种叫作 brain 的状态,它与一个人和一个分支关联。它最初是空差异。当我明确接受已经审查的变更时,这些变更就会应用到我的 brain 中。关键区别在于,Iron 记住的是我审查过的内容,而不只是我见过哪些提交的引用。

可以把分支看成一个差异,而我的 brain 则是我理解的差异。一旦两者一致,就说明我审查完了这个分支。这个简单模型让工具能够追踪我的审查进度,而不依赖我阅读的顺序,也不依赖最初包含这些变更的提交。

合并或 rebase 改变分支后,Iron 会将我的 brain 与新的分支差异进行比较。结果是差异的差异:它描述了我已经知道的内容与现在需要知道的内容之间的区别。读完这个结果,我的 brain 就能更新到与分支一致,无须从头开始。

来看一个小改动:某个函数在目标时间之后调用另一个函数,分支为了清晰起见颠倒了比较条件的顺序。与此同时,上游变更重命名了目标变量,并把一个匿名函数改成无点式写法。双方修改了相同的代码行,因此合并时产生冲突。

已审查的比较条件变更
幻灯片 1 / 5已审查的比较条件变更示例展示了构成审查者 brain 基础的原始代码变更。
冲突后的差异的差异
幻灯片 2 / 5冲突后的差异的差异Iron 比较此前已审查的变更与更新后的变更,显示上游编辑的影响。

Iron 得到的差异的差异同时展示变更的新旧版本。有些行只是说明上下文移动了;有意义的部分则显示比较条件如何使用新变量名完成颠倒。即使这个简单例子看起来也有些吓人,这正是这种方法的代价之一。

大多数时候,Iron 会使用启发式方法将比较结果呈现为更易读的普通差异。遇到真正的冲突时,审查者可能需要阅读更复杂的差异的差异。从概念上说,这有点像函数式方法:工具追踪实际审查过的值,而不是依赖提交的引用。

一个缺点是,rebase 之后可能很难再按提交逐个审查分支:你可能需要考虑整个分支的差异。我们通过创建小分支来弥补。如果差异的差异很难读,审查者也可以丢弃相关审查状态,重新审查那一部分。

好处是,分支可以随时更新,而每位审查者仍能保持一致的视图。Rebase 引入的改动或合并产生的冲突解决结果,会作为相对于审查者已接受内容的差异显示出来。没有变化的内容不会仅仅因为历史改变就需要重读。

03

行内评论与协作

审查者直接向分支提交带标记的评论,也可以在同一工作区内修复问题。讲者讨论了这样做的好处以及由此产生的杂乱历史。

审查还能传播知识、降低巴士因子,但一个核心目的仍是通过反馈改进代码。Jane Street 的做法起初让我吃惊:审查者检出作者的分支,直接在源代码中添加特殊标记的评论,提交后再推回该分支。

传统审查评论可能附着在提交、差异、分支或某个特定版本的文件上。代码移动或提交在 rebase 中消失时,这些关联就可能变得尴尬。评论写在源代码中,分支更新时就会跟着代码一起走。

行内审查评论
幻灯片 3 / 5行内审查评论带 CR 标记的评论直接出现在相关代码旁。

审查评论用 CR 标记注明作者和接收者。接收者会把它看作分支上的新变更,可以处理它,也可以用 XCR 标记回复,把讨论发回去。评论就在相关代码旁边,无须让工具猜测它该放在哪里。

这种做法有代价。历史中会包含添加和删除审查评论的提交,而每次评论都手动检出分支、找到相应代码行也会很痛苦。审查结束时评论已经删除,因此它们不会以同样的方式影响 blame,但历史看起来仍然很不寻常。

我们的 Emacs 审查客户端减轻了这些麻烦。阅读差异时,我可以按一个键跳转到该分支工作区中的对应代码行,然后在编辑器里写评论。从审查切换到编辑,比听起来要轻松得多。

这种安排也让修复代码和更新评论可以一起完成。如果审查时看到拼写错误或不够清晰的变量名,我通常可以直接修改,而不用先发表评论,再等待作者做一个很小的修复。随着分支变化,上下文仍然跟在代码旁。

允许审查者推送变更,也改变了人们对所有权的感觉。一个功能最初可能是我的工作,但它最终是 Jane Street 的代码,其他人可以从早期就参与进来。由于我也会审查自己的分支,brain 会向我展示别人做出的变更。

产生的历史无疑很杂乱,里面有针对单条评论和小修正的提交。Jane Street 接受了这种取舍,没有把整洁的提交图作为首要目标。我花了一些时间适应,但后来逐渐欣赏它所带来的协作。

04

审查义务与嵌套分支

功能审查者和文件审查者有明确职责,审查严格程度按文件配置。嵌套分支使小规模、相互依赖的审查单元成为可能。

审查义务决定谁必须查看某项变更。作者先审查自己的分支,然后指定整个功能的审查者,由他们阅读并明确确认分支。之后,审查范围扩大到对该功能所修改文件负有审查义务的人。

文件审查者了解相关代码,可以判断变更是否符合代码的设计意图。纳入版本控制的元数据为文件或文件组指定负责人、审查严格程度和审查义务。更重要的文件可以要求更多关注;规则也可以要求某个团队中至少指定数量的人审查,而非全员参与。

文件审查义务
幻灯片 4 / 5文件审查义务纳入版本控制的元数据为一组文件声明负责人、审查严格程度和审查要求。

嵌套分支让一个功能可以依赖另一个功能。例如,测试 DSL 可能依赖新建的 monad transformer 库。手动维护这种层级可能很麻烦,但它适合我们的流程:审查的基本单元是分支,所以一系列相关变更会变成一系列相互依赖的小分支。

在我们的单体仓库中,长期存在的项目专属分支可以充当中间主分支,变更先合并到那里,再进入仓库的其他部分。Iron 将分支称为 feature,用 Mercurial 书签实现,并附加负责人、描述等元数据;描述的作用类似提交信息。

05

仪表盘、工作区与发布控制

仪表盘追踪审查和分支状态,独立工作区则方便切换任务。锁和集中式检查为发布提供保障。

Iron 客户端有一个待办仪表盘,显示审查、评论、分支状态和建议的下一步操作。它还会列出未清理的工作区:当许多分支拥有独立工作目录时,很容易忘记尚未提交或推送的工作,这个列表就很有用。

Iron 待办仪表盘
幻灯片 5 / 5Iron 待办仪表盘匿名化的仪表盘分类显示审查、未清理的工作区、负责的功能以及待跟进评论。

仪表盘让我们更容易在多个功能之间切换,同时不遗漏进展。我还把审查评论用作个人提醒。在 Emacs 仪表盘中,我可以发起异步 rebase;如果操作因为冲突失败,待办列表之后会提醒我介入处理。

工作区是为某个分支准备的独立工作目录,用途类似 Git worktree。它让我切换任务时不用替换某个检出目录的内容,也不用暂存未完成的工作。在我们庞大的单体仓库中,独立工作区还能让 OCaml 增量构建进程持续运行,而不必每次切换分支都重新构建。

Iron 使用锁的方式不同于传统的文件排他锁。如果某个功能有已知的严重缺陷,我可以锁住它,阻止发布;如果一次 rebase 需要谨慎处理,我也可以锁住该操作。生产环境补丁的分支也可以固定在较旧版本,避免自动更新意外引入较新的变更。

集中式服务器会执行规则:未完成审查的分支,或仍有阻塞性 CR 评论的分支,都不能发布。这限制了一些结构性操作,需要在安全与便利之间权衡。Mercurial 仍然允许不联系服务器就进行本地工作;这些限制主要在共享和发布变更时起作用。

06

源代码之外与问答

同一审查流程也用于配置和交易限额。问答中,讲者讨论了提交历史、生产环境回滚、缺陷标记、二分查找和文件所有权。

有了好用的审查工具后,我们把它用于源代码之外的内容。配置变更和博客文章都可以走同一流程。交易限额也设有审查义务,让有权批准变更的人在发布前进行审查。同一机制甚至可以用来审查一篇用 Markdown 写的演讲稿。

在问答环节,我指出嵌套分支主要是一种形式化的工作流程,而不是独特的基本功能。可以用分层的分支名称和更新依赖分支的脚本建立类似机制。至于 Mercurial 的历史和 rebase 惯例如何影响 Iron 的设计,我明确说过那只是猜测。

我以前会在审查前整理提交历史并写详细的提交信息。在 Jane Street,我不再把单个提交当作叙述变更的重要载体:审查和描述都在分支层面进行。很多人甚至会配置固定的提交信息,让自己自由提交中间工作,而不必精心整理出完美的提交序列。

生产环境出现缺陷时,我们通常把已部署的可执行程序回滚到归档版本。Iron 还有 trait 功能:可以标记某个修订版本包含一个缺陷,再标记后来的某个修订版本修复了它。由于已发布的历史通过合并保留,而非重写,工具可以追踪哪些修订继承了缺陷,以及重新构建是否包含修复。

我们通过新增变更解决问题,或者应用一个反向差异并提交,而不是从历史中改写掉旧提交。同样的反向差异方法也可以从分支中撤销某个变更,同时保留历史记录。

二分查找体现了这种历史的一个实际代价。通常,在已知正常和异常的修订版本之间进行二分搜索,可以找到引入缺陷的提交。我们的历史中有大量合并提交,沿相关祖先关系查找很痛苦。按功能发布节点进行分支级二分查找可能会有帮助,但我们还没有这个功能。

最后,文件所有权在操作上有明确含义。如果分支发布时仍有不阻塞发布的 CR-soon 评论,后续处理责任就会转交给文件负责人,并出现在其待办列表中。文件审查义务则是另一回事:它决定谁有机会审查变更,尽管负责人通常也会是审查者。

同频道的其他文章

1:48:29EN → 简体中文
JSJane Street· 2026年10月16日AI 如何改变编程:John Krazit 与 Ron Minsky 的对话Ron Minsky, John Krazit · Jane StreetJane Street 的 AI 工具负责人和 Signals and Threads 主持人探讨智能体基础设施、安全、代码审查,以及软件开发体验的变化。编程智能体Developer productivity评估+2
1:36:37EN → 简体中文
JSJane Street· 2026年9月23日Aaron Bauer:在 AI 时代教授软件工程Ron Minsky, Aaron Bauer · Jane Street当模型能够生成代码时,学习目标、主动练习和编辑器设计如何影响工程教育。AI in EducationSoftware engineeringEducation+2
9 张幻灯片1:05:45EN → 简体中文
JSJane Street· 2026年9月11日Cyrus Omar:实时编程与人与 AI 共享的计算公共空间Cyrus Omar通过 Hazel 演示,探讨即时反馈、结构化协作和精细权限如何帮助人类与 AI 智能体共同工作。Programming LanguagesHuman-AI Collaboration开发流程与测试+2
1:25:00EN → 简体中文
JSJane Street· 2026年9月2日Eric Mannes:如何将杂乱的外部数据用于交易研究Ron, Eric Mannes · Jane Street从商品期货和 Excel 风险系统,到时点数据集和 AI,Eric Mannes 解释 Jane Street 数据工程背后的领域知识。Data quality & curation开发流程与测试Investing+2
7 张幻灯片1:16:29EN → 简体中文
JSJane Street· 2026年8月26日Grant Sanderson 与 Alok Puranik:从群论看位置编码Grant Sanderson, Alok Puranik · 3Blue1Brown从注意力机制的假设出发,他们推导矩阵指数,并将旋转、衰减和 Jordan 块与 RoPE、ALiBi 联系起来。Transformers & attentionMathematics机器学习基础+1
4:41EN → 简体中文
JSJane Street· 2026年7月6日走进 Jane Street 实习项目:工作、导师与学习Cody · Jane Street实习生和员工讲述协作式面试、从零学习金融,以及参与公司实际使用的项目的经历。CareersCompany CultureMachine Learning+1