不再是我最喜爱的 Git 提交
原文由 Michael Lynch 于 发布,订阅该博客
六年前,David Thompson 写了一篇颇受欢迎的博客文章“My favourite Git commit”,盛赞同事写的一条既详尽又妙趣横生的提交信息。我当时很喜欢这篇文章,还把它发给了好几位同事,作为优秀提交信息的范例。
最近,在编写自己的如何写出有用的提交信息指南时,我重读了 Thompson 的文章。当被问及究竟是什么让 Thompson 的例子如此有效时,我惊讶地发现自己竟无法回答。作为旁观者,这篇文章读起来很有趣,但我无法再把它当作良好软件工程实践的典范来为其辩护。
Thompson 最喜爱的提交
就是这条当时让 Thompson 和包括我在内的许多人着迷的提交信息:
将模板转换为 US-ASCII 以修复错误
我在功能分支上添加了一些测试,用于匹配
/etc/nginx/router_routes.conf的内容。当使用bundle exec rake spec或bundle exec rspec modules/router/spec运行时,它们都能正常通过。但当以bundle exec rake运行时,每个 should 块都会报错:ArgumentError: invalid byte sequence in US-ASCII我最终发现,去掉
.with_content(//)匹配器后错误就消失了。spec 文件里并没有什么奇怪的字符。而且,只要在同一个解释器中引入 Puppet 就能复现这个问题:rake -E 'require "puppet"' spec那个模板似乎是我们代码库中唯一被识别为
utf-8编码的文件,其他文件都是us-ascii:dcarley-MBA:puppet dcarley$ find modules -type f -exec file --mime {} \+ | grep utf modules/router/templates/routes.conf.erb: text/plain; charset=utf-8尝试将该文件转回 US-ASCII 后,发现罪魁祸首是一个看起来像空白字符的字符:
dcarley-MBA:puppet dcarley$ iconv -f UTF8 -t US-ASCII modules/router/templates/routes.conf.erb 2>&1 | tail -n5 proxy_intercept_errors off; # Set proxy timeout to 50 seconds as a quick fix for problems # iconv: modules/router/templates/routes.conf.erb:458:3: cannot convert(手动)替换掉它之后,该文件又被识别为
us-ascii了:dcarley-MBA:puppet dcarley$ file --mime modules/router/templates/routes.conf.erb modules/router/templates/routes.conf.erb: text/plain; charset=us-ascii现在测试终于通过了!白白浪费了我一小时的生命……
这段长长铺垫之后的“笑点”在于,Thompson 展示了实际的 diff:
没错,这条提交信息用了整整六段文字和五个代码片段,仅仅为了解释一个空白字符的改动。
喜爱的不等于最好
很容易看出这条提交为何讨人喜欢。
大多数开发者只会把这次改动简单记为“修复空白字符”,因此有人如此详尽地解释自己排查和修复 bug 的过程,着实让人眼前一亮。
就 Thompson 列举的那些理由而言,这确实是一条不错的提交信息:它留下了一份可供搜索的记录,也分享了关于开发者工具和流程的有用见解。
这并不是对 Thompson、甚至对原提交作者的攻击。Thompson 从未声称这是“最好”的提交信息,只是说这是他最喜欢的。
不过,如今我看到了其中的一些缺陷,这让我无法再把它当作提交信息的范例来推荐。
它把最重要的信息埋在了最后
Thompson 最初发表这篇博客时,最常见的批评是说这条提交信息过于冗长。我当时觉得这种批评有些偏颇。
提交信息中的详尽细节只要与主题相关,就是有价值的,而 Thompson 例子中的细节正是如此。它们能帮助经验较少的同事学习作者的调试思路和工具使用,也能让经验更丰富的同事有机会判断开发者是否有所疏漏,或是否不知道某个相关工具。
人们之所以觉得 Thompson 的例子过于冗长,原因在于它把最重要的信息深深埋在了提交信息的末尾。
再读一遍第一段:
我在功能分支上添加了一些测试,用于匹配
/etc/nginx/router_routes.conf的内容。当使用bundle exec rake spec或bundle exec rspec modules/router/spec运行时,它们都能正常通过。但当以bundle exec rake运行时,每个 should 块都会报错:ArgumentError: invalid byte sequence in US-ASCII
整整三句话加一个代码片段之后,读者依然对这次改动究竟做了什么一无所知。
提交信息应该把最重要的信息放在最前面,再逐步展开到更细节的内容。记者们称这种写法为倒金字塔结构。
记者以倒金字塔结构来组织新闻报道,将与最多读者相关的信息放在最顶部。
当我浏览提交历史时,我希望能快速判断每条提交是否与自己相关。提交信息理应在开头就提供对改动的高度概括。
它始终没有真正讲清楚问题所在
读完 Thompson 示例中的那条提交信息,你真的明白这次改动是怎么回事吗?
这是其中最接近问题解释的一段:
那个模板似乎是我们代码库中唯一被识别为 utf-8 编码的文件,其他文件都是 us-ascii:
dcarley-MBA:puppet dcarley$ find modules -type f -exec file --mime {} \+ | grep utf modules/router/templates/routes.conf.erb: text/plain; charset=utf-8
信息中说 routes.conf.erb 是 UTF-8 编码,却始终没有解释原因。好在该项目是开源的,我可以自己一探究竟。
问题出在 第 463 行的 routes.conf.erb:
$ cat modules/router/templates/routes.conf.erb | head -n 463 | tail -n 1
# where civica QueryPayments calls are taking too long.
用普通文本编辑器或浏览器是看不出问题的,但如果用 xxd 这样的工具导出文件的原始字节,就能发现端倪:
$ cat modules/router/templates/routes.conf.erb \
| head -n 463 | tail -n 1 \
| xxd | head -n 1
00000000: 2020 23c2 a077 6865 7265 2063 6976 6963 #..where civic
^^ ^^
如果你没有背下 US-ASCII 和 UTF-8 编码表,这里是该行开头几个字符的对照:
| 字节表示 | 文本表示 |
|---|---|
0x20 | ' '(空格) |
0x20 | ' '(空格) |
0x23 | '#' |
0xC2 0xA0 | ' '(UTF-8 不换行空格) |
因此,该文件包含了字节序列 0xC2 0xA0,这意味着它不可能是 US-ASCII 文件,因为 0xC2 和 0xA0 都超出了 US-ASCII 的字节范围。
字节序列 0xC2 0xA0 意味着任何读取 routes.conf.erb 的应用都必须以 UTF-8 编码来解析它——UTF-8 是一种更新、更具国际化友好性的文本编码方案。
Thompson 的代码库使用的是 Ruby 1.9.3,它虽然支持 UTF-8 编码,但如果文件未显式声明,则默认会按 US-ASCII 处理。
翻阅源码历史后,我发现是提交 5a8607最早引入了这个 UTF-8 字符。该提交信息并未提及引入该字符的任何理由,因此很可能只是个意外。
一位 Hacker News 评论者提出了一个颇为可信的推测,解释了 routes.conf.erb 中这个多余的 UTF-8 字符为何会出现:
这个无效字符很可能的来源是,有人使用了爱尔兰/英国的苹果键盘布局,其中 # 是通过 Option-3(AltGr-3)输入的,而不换行空格则是通过 Option-Space(AltGr-Space)输入的。
-messe 在 Hacker News 上
它引用了代码却没有给出链接
Thompson 的示例提交以一段对外部代码的引用开头:
我在功能分支上添加了一些测试,用于匹配
/etc/nginx/router_routes.conf的内容。当使用bundle exec rake spec或bundle exec rspec modules/router/spec运行时,它们都能正常通过。
但提交信息既没有给出分支名,也没有提供提交哈希,读者根本无法复现开发者的发现。
后文提交信息又写道:
我最终发现,去掉
.with_content(//)匹配器后错误就消失了。我在 spec 文件中并未看到任何奇怪的字符。
没有提交哈希或链接,读者无从得知开发者所指的究竟是哪些匹配器、哪个 spec 文件。
如果提交信息中引用了外部代码,就应该明确给出链接,这样代码审查者和未来的维护者才能看到改动的确切上下文。
我的改写版本
以下是我对 Thompson 最喜爱的这条 Git 提交的改写建议:
将 routes.conf.erb 模板转换为 US-ASCII
routes.conf.erb中有一个多余的 UTF-8 字符,似乎是在 5a8607 中意外引入的。
rake要求文件为 US-ASCII 格式,因此routes.conf.erb中这单个 UTF-8 字符会导致rake中的测试失败。本次改动将该 UTF-8 字符替换为等效的 US-ASCII 字符,以避免
rake中的测试失败。多余的 UTF-8 字符
问题位于
modules/router/templates/routes.conf.erb的第 463 行:$ cat modules/router/templates/routes.conf.erb \ | head -n 463 | tail -n 1 \ | xxd | head -n 1 00000000: 2020 23c2 a077 6865 7265 2063 6976 6963 #..where civic ^^ ^^
0xC2 0xA0不是合法的 US-ASCII 字节序列,而是 UTF-8 不换行空格字符。任何以 US-ASCII 编码读取该文件的工具都会失败。我是如何发现这个问题的
我在功能分支上添加了一些测试,用于匹配
/etc/nginx/router_routes.conf的内容(见 abcd123)。当使用bundle exec rake spec或bundle exec rspec modules/router/spec运行时,它们都能正常通过,但当以bundle exec rake运行时,每个should块都报错:ArgumentError: invalid byte sequence in US-ASCII我最终发现,去掉
.with_content(//)匹配器后错误就消失了。我在 spec 文件中并未看到任何奇怪的字符。只要在同一解释器中引入 Puppet,就能复现该错误:rake -E 'require "puppet"' spec那个模板似乎是我们代码库中唯一被
file识别为utf-8的文件,其他文件都是us-ascii:$ find modules -type f -exec file --mime {} \+ | grep utf modules/router/templates/routes.conf.erb: text/plain; charset=utf-8尝试将该文件转回 US-ASCII 后,发现罪魁祸首是一个看起来像空白字符的字符:
$ iconv -f UTF8 -t US-ASCII modules/router/templates/routes.conf.erb 2>&1 \ | tail -n5 proxy_intercept_errors off; # Set proxy timeout to 50 seconds as a quick fix for problems # iconv: modules/router/templates/routes.conf.erb:458:3: cannot convert(手动)替换掉该 UTF-8 字符后,
file又将routes.conf.erb识别为us-ascii:$ file --mime modules/router/templates/routes.conf.erb modules/router/templates/routes.conf.erb: text/plain; charset=us-ascii现在测试终于通过了!白白浪费了我一小时的生命……
我的改动如下:
- 我在信息开头添加了高度概括的总结。
- 我对 UTF-8 字符及其来源给出了更明确的解释。
- 我将作者原文中的大部分内容移到了“我是如何发现这个问题的”一节,以表明这部分属于额外阅读内容。
- 我做了一些轻微的语法修正。
- 我去掉了被动语态,以减少歧义。
- 我将终端提示符从
dcarley-MBA:puppet dcarley $简化为$,因为前者大多是冗余信息。
值得注意的是,我并没有删减细节,因为问题不在于冗长,而在于开发者组织和呈现信息的方式。
明确个人原则的价值
重读 Thompson 的文章让我意识到,为自己明确软件工程原则是多么有价值。
我曾因为认同 Thompson 所说的优点而将其视为优秀范例。直到我坐下来,明确自己认为提交信息最重要的特质时,才看出了 Thompson 例子中的不足。
这些年来,我曾就多种软件工程实践阐述过自己的观点,而每一次这样做都让我成为更好的开发者。它迫使我批判性地审视那些被视为理所当然的观念,并帮助我记住自己心中的理想形态,即使我未必总能达到它。
延伸阅读
- “如何写出有用的提交信息” - 关于我认为何为优秀提交信息的更详细阐述。
摘自 govuk-puppet 项目的内容版权归 Crown Government Digital Service 所有,依据 MIT 许可证使用。
随机一篇博客

评论
登录后参与讨论