始终追溯

matklad 工具

摘要

本文探讨了通过版本控制追溯代码演变来阅读和理解代码的策略,重点强调了 `git blame` 的使用以及理解作者视角的重要性。

<header> <h1>始终追溯</h1> <time class="meta" datetime="2026-05-18">2026年5月18日</time> </header> <p>关于提升代码理解能力的四维技巧的小建议。</p> <p>我之前写过关于阅读代码重要性的文章: <a href="https://matklad.github.io/2025/09/04/look-for-bugs.html" class="display"><em>Look Out For Bugs</em></a> 我默认的阅读方法是“预测式”:我实际上不会逐行阅读代码。而是尝试理解它要解决的问题,然后想象自己的解决方案,再阅读我脑海中与编辑器中所见之间的“差异”。非空的“差异”要么表示我理解中的错误,要么是改进代码的机会。</p> <p>这是二维阅读,理解一个时间点冻结的代码快照。通常这足以发现“感觉有点奇怪”的异常,值得进一步调查。</p> <p>理想的代码是无记忆的——它精确地解决当前的问题。大多数实际代码是马尔可夫式的——在时间 <code>T</code> 的代码形状不仅取决于问题描述,还取决于时间 <code>T - 1</code> 的代码形状。三维步骤是追踪代码随时间的演变, <a href="https://en.wikipedia.org/wiki/Where_Do_We_Come_From%3F_What_Are_We%3F_Where_Are_We_Going%3F#/media/File:Paul_Gauguin_-_D'ou_venons-nous.jpg"><em>Where Do We Come From? What Are We? Where Are We Going?</em></a>.</p> <p>接下来的步骤是理解<em>为什么</em>。当时我们在写这段代码时在想什么? 这里有“心智理论”的概念很有用。我个人学到这个词太晚了,所以让我为<a href="https://xkcd.com/1053/">今天的幸运一万名</a>做个简短介绍。心智理论是一种将自己置于他人处境的能力。不仅仅是设身处地(“我肯定会在那种情况下采取不同行动”),而是设身处地想其所想(“<em>我</em>不会那样做,但我理解<em>他们</em>为什么这么做”)。这是人们要学习的东西。实验设置是这样的:一个孩子在一个有玩具的房间里,一个洋娃娃坐在房间的另一端,问孩子“洋娃娃看到了什么?”。年龄较小的孩子从<em>自己的</em>视角描述房间,年龄较大的孩子开始直觉到洋娃娃的视角是不同的。</p> <p>所以,<em>这</em>就是阅读代码的目标——理解原作者在想<em>什么</em>以及<em>为什么</em>。</p> <hr> <p>废话少说,一些实用建议。首先,阅读 <span class="display"><a href="https://mislav.net/2014/02/hidden-documentation/"><em>Every line of code is always documented</em></a>,</span> 这篇文章非常好。</p> <p>第二,确保你能<em>毫不费力</em>地找到一段代码的演变过程。这比看起来难!仅仅<code>git blame</code>不是答案——要留意容易解决的问题和需要解决的问题之间的差距。</p> <p><code>git blame</code>回答了空间问题“每一行是如何出现在这个文件中的”,因为这有一个相对简单的UI——用提交哈希注释每一行。但这并不是大多数时候你问的问题!你不在乎文件!中间有一小段代码,你想知道<em>那一段</em>的时间历史。</p> <p>尽管我不喜欢在浏览器中工作,但 GitHub 的 blame 网页界面可能比本地默认的要好。它从 <kbd><kbd>y</kbd></kbd> 快捷键开始,该快捷键将类似下面的符号引用解析为</p> <figure class="code-block"> <pre><code><span class="line">https://github.com/tigerbeetle/tigerbeetle/blob/main/src/vsr/replica.zig</span></code></pre> </figure> <p>在 URL 中带有提交哈希的引用:</p> <figure class="code-block"> <pre><code><span class="line">https://github.com/tigerbeetle/tigerbeetle/blob/c54f613a2eb2a127a0ba212704e3fa988c42e5cb/src/vsr/replica.zig</span></code></pre> </figure> <p>这个提交哈希至关重要,因为它锚定了整个仓库——如果你从网页界面打开其他文件,它将以该提交的状态显示。这让你不仅仅局限于查看差异,还能吸收那个时间点的整个上下文。</p> <p>所以我通常的网络工作流程是:</p> <ul> <li> <kbd><kbd>ctrl</kbd>+<kbd>f</kbd></kbd> 查找我感兴趣的行 </li> <li> <kbd><kbd>b</kbd></kbd> 切换 blame </li> <li> 点击几次“blame prior to change”,重复使用 <kbd><kbd>ctrl</kbd>+<kbd>f</kbd></kbd> 返回我好奇的片段。 </li> <li> <kbd><kbd>cmd</kbd></kbd>-点击可能相关的提交,将其提交哈希固定在新标签页的 URL 中。 </li> <li> 然后,在提交页面,点击“Browse files”按钮,再按 <kbd><kbd>t</kbd></kbd> 转到其他文件。或者, <kbd><kbd>cmd</kbd>+<kbd>l</kbd></kbd> 聚焦浏览器地址栏,根据需要输入 <code>s/commit/tree/</code>(或返回),在差异和快照视图之间切换。 </li> </ul> <p>再次说明,我的目标不是注释文件上的差异,而是获得所感兴趣提交的“虚拟检出”。</p> <p>这种方法在我职业生涯的大部分时间都在使用,但我终于找到了在本地复现的方法。思路是“原地”追溯责任。不是使用<code>git blame</code>注释代码行,而是直接切换到历史提交。我有以下快捷键组合:</p> <p><kbd><kbd>, b l</kbd></kbd> 追溯行。它记录当前光标的 <code>$line</code>,运行 <span class="display"><code>git blame -L $line,$line</code></span> 找到引入该行的 <code>$commit</code>,然后运行 <span class="display"><code>git switch --detach $commit</code></span> 检出该提交。我有一个专用工作树来进行代码考古,所以不用担心破坏工作。此外,还有一个半心半意的尝试来维护“逻辑”光标位置,但效果不佳。有没有 git 命令可以直接告诉我“对于 <code>$sha-B</code>,<code>$sha-A</code> 中的 <code>$file:$line:column</code> 等价于什么?”</p> <p><kbd><kbd>, b p</kbd></kbd> 追溯父提交。就是切换到当前 <code>HEAD</code> 的父提交,类似 GitHub 上的“blame before this change”(但略有不同,因为它假设 <kbd><kbd>, b l</kbd></kbd> 是上一个命令)。</p> <p><kbd><kbd>, b u</kbd></kbd> 撤销上一次追溯操作,切换回上一个点。我<em>真的</em>喜欢在网络上,我可以 <kbd><kbd>cmd</kbd></kbd>-点击来创建探索的替代分支。理论上,这在本地也可以复制,但我更喜欢破坏性地改变磁盘上的单个工作树。偏好原地追溯的一个重要原因是 LSP、<code>./zig/zig build test</code>、<code>rg</code> 等都能正常工作。这对我来说比分岔路径的花园更重要,而撤销是一个可以接受的变通方案。</p> <p>最后,<kbd><kbd>, b w</kbd></kbd> 复制当前提交和行的 GitHub 链接,可以粘贴到浏览器中。现代版本控制领域的一个<em>巨大</em>问题是,以代码审查评论形式存在的绝对关键信息不属于 git 仓库的一部分,而是锁定在别人的专有数据库中。我利用一个周末的时间<a href="https://tigerbeetle.com/blog/2025-08-04-code-review-can-be-better/">未能解决</a>这个问题,只好不情愿地适应。在浏览器中打开提交还可以链接到 PR 及其讨论。</p> <p>实现这个 blame 工作流需要 <a href="https://github.com/matklad/config/b"} >...</p>
查看原文
查看缓存全文

缓存时间: 2026/05/18 15:33

# 始终问责 来源:https://matklad.github.io/2026/05/18/always-be-blaming.html 2026年5月18日关于将代码理解能力提升至四维的一些小贴士。 我之前写过关于阅读代码重要性的文章:*留意 Bug* (https://matklad.github.io/2025/09/04/look-for-bugs.html) 我默认的阅读方式是“预测性”的:实际上我并不逐行阅读代码。相反,我试图理解它要解决的问题,然后想象自己的解决方案,再阅读我脑海中与编辑器里看到的“差异”。非空的“差异”要么表明我的理解有 bug,要么意味着代码有改进空间。 这是二维阅读,理解代码在某一时刻的快照。这通常足以发现“感觉不对劲”的异常,值得进一步调查。 理想的代码是无记忆的——它精确地解决当前问题。大多数真实代码是马尔可夫式的——时间点`T`的代码形状不仅取决于问题描述,还取决于时间点`T-1`的代码形状。三维步骤是追踪代码随时间演化的历程,*我们从哪里来?我们是谁?我们往哪里去?* (https://en.wikipedia.org/wiki/Where_Do_We_Come_From%3F_What_Are_We%3F_Where_Are_We_Going%3F#/media/File:Paul_Gauguin_-_D'ou_venons-nous.jpg) 再下一步是理解*为什么*。我们当初写这段代码时在想什么?在这里最好准备好“心智理论”的概念。我本人学到这个词太晚了,所以让我给今天幸运的 10,000 人 (https://xkcd.com/1053/) 做个简短介绍。心智理论是设身处地想象自己处于别人处境的能力。不仅仅是站在他们的立场(“我肯定会在那种情况下采取不同做法”),而是用他们的心智(“*我*不会那样做,但我理解*他们*为什么那样做”)。这是人们后天习得的能力。实验设置是这样的:让一个孩子待在放有玩具的房间里,一个洋娃娃坐在房间另一端,然后问孩子“洋娃娃看到了什么?”年幼的孩子从*自己的*视角描述房间,年长的孩子开始直觉到洋娃娃的视角是不同的。 所以*这*就是阅读代码的目标——理解原作者*当时在想什么*,以及*为什么那么做*。 --- 废话结束,接下来是一些实用建议。首先,阅读*每一行代码永远都有文档* (https://mislav.net/2014/02/hidden-documentation/),它写得非常好。 其次,确保你能*毫不费力*地找出给定代码片段是如何演变的。这比看起来要难!仅仅`git blame`并不是答案——要注意“容易解决的问题”与“需要解决的问题”之间的差距。 `git blame`回答的是空间问题“每一行是如何出现在这个文件中的”,因为有一个相对直观的 UI——用提交哈希注释每一行。但这并不是你大多数时候要问的问题!你并不关心这个文件!你关心的是中间的一个小代码片段,而你想要的是*那个*片段的时间线历史。 尽管我不喜欢在浏览器中工作 (https://tigerbeetle.com/blog/2025-08-04-code-review-can-be-better/),但 GitHub 的 blame 网页界面可能比你本地默认的更好用。它从`y`快捷键开始,该快捷键将类似 `` https://github.com/tigerbeetle/tigerbeetle/blob/main/src/vsr/replica.zig `` 的符号引用解析为包含提交哈希的 URL: `` https://github.com/tigerbeetle/tigerbeetle/blob/c54f613a2eb2a127a0ba212704e3fa988c42e5cb/src/vsr/replica.zig `` 这个提交哈希至关重要,因为它锚定了整个仓库——如果你从网页界面打开另一个文件,它会显示为那个提交时的状态。这使你能够不仅仅狭隘地关注当前差异,还能吸收那个时间点的全部上下文。 所以我通常的网页工作流程是这样的: - `ctrl+f` 找到我感兴趣的行 - `b` 切换 blame - 点击“blame prior to change”几次,重复 `ctrl+f` 回到我好奇的代码片段 - `cmd+点击` 可能相关的提交,在新标签页中固定其提交哈希的 URL - 然后,从提交页面,点击“Browse files”按钮跳转到其他文件。或者 `cmd+l` 聚焦浏览器地址栏,根据需要将 `s/commit/tree/`(或反向)切换差异视图与快照视图 再说一次,我的目标不是注释文件上的差异,而是获得感兴趣提交时的“虚拟检出”。 这种网页方法是整个职业生涯中大部分时间使用的,但我终于找到了一种在本地复现它的方式。思路是让 blame “就地”进行。不再用`git blame`注释代码行,而是直接切换到历史提交。我有以下魔鬼 (https://susam.github.io/devil/) 九头蛇 (https://github.com/abo-abo/hydra) 快捷键组合: - `, b l` 对行进行 blame。它记下光标所在的行`$line`,运行 `git blame -L $line,$line` 找到引入该行的 `$commit`,然后运行 `git switch --detach $commit` 检出该提交。我有专门的工作树 (https://matklad.github.io/2024/07/25/git-worktrees.html) 用于代码考古,所以不用担心破坏我的工作。还半心半意地尝试保持“逻辑”光标位置,但效果不太好。有没有什么 git 命令可以直接告诉我“`$file:$line:column` 在 `$sha-A` 中对应 `$sha-B` 的什么位置”? - `, b p` 对父提交进行 blame。即切换到当前 `HEAD` 的父提交,相当于 GitHub 上的“blame before this change”(它的工作方式略有不同,因为假设 `, b l` 是前一个命令) - `, b u` 撤销上一次 blame 操作,切换到上一个点。我*非常*喜欢在网页上可以通过 `cmd+点击` 创建探索的替代分支。理论上,这可以在本地复现,但我更倾向于破坏性地改变磁盘上的单个工作树。偏好就地 blame 的一个重要原因是 LSP、`./zig/zig build test`、`rg` 等工具能够正常工作。这对我来说比分叉路径的花园更重要,而撤销是一个可接受的变通方案。 - 最后,`, b w` 将当前提交和行的 GitHub 链接复制到剪贴板,我可以粘贴到浏览器中。现代版本控制领域的一个*巨大*问题是,代码审查评论这种绝对关键的信息不属于 git 仓库,而是被锁定在别人的专有数据库中。我曾尝试在一个周末解决这个问题 (https://tigerbeetle.com/blog/2025-08-04-code-review-can-be-better/),但失败了,只好不情愿地适应。在浏览器中打开提交会链接到对应的 PR 及其讨论。 实现这个 blame 工作流需要一些自定义代码 (https://github.com/matklad/config/blob/801d0781b005db574e6b42b813058741dd8ef390/tools/my-code/src/blame.ts)。你可以自由使用,但注意它有些粗糙,尤其是在维护当前光标位置方面。将其做成生产级版本听起来是个有趣的项目 ;-)

相似文章

代码审查需要认真阅读代码

Lobsters Hottest

一篇开发者博客文章反对在不阅读 AI 生成代码的情况下直接将其部署到生产环境,强调代码审查具有至关重要的作用:分散责任、降低巴士因子风险,以及让团队成员保持对代码库的了解。

论代码的意图

Lobsters Hottest

作者认为,阅读和编写代码就像讲故事,意图通过显式和隐式的方式传达。他们讨论了在阅读代码时理解作者意图的重要性,并使用了 C++ auto、Python 类型注解和公司编码策略等示例。

迈向可理解的软件

Lobsters Hottest

本文批判了当前的编程实践和对大语言模型的依赖,反而主张通过更好的抽象、文档和软件栈来使代码更易于理解和维护。

为什么人们不善用Git?

Lobsters Hottest

一篇文章探讨为何许多开发者难以正确使用Git,涵盖常见错误如对合并冲突恐慌、提交过大以及分支管理不善,并分析根本原因。

接受混乱的 git 历史

Lobsters Hottest

一篇博客文章,讨论两种 git 工作流理念——频繁提交与谨慎变基——并解释为何在大型团队中,鉴于团队的韧性,混乱的历史是可以接受的,文中还提到了 GitHub 的新堆叠 PR 功能。