2. 良好的补丁实践

大多数人参与开源软件都是从为别人的软件编写补丁开始的,然后才发布自己的项目。假设你已经为别人的基线代码编写了一组源代码更改。现在设身处地为那个人着想。他将如何判断是否应该包含这个补丁?

事实是,判断代码的质量非常困难。因此,开发者倾向于通过提交的质量来评估补丁。他们会从提交者的风格和沟通行为中寻找线索——表明这个人已经设身处地为他们着想,并且理解评估和合并传入补丁是什么样的感受。

这实际上是代码质量相当可靠的替代指标。在多年处理来自数百个陌生人的补丁的过程中,我很少见到一个经过深思熟虑、尊重我的时间的补丁,但技术上却是错误的。另一方面,经验告诉我,看起来粗心大意或以懒惰和不体谅的方式打包的补丁很可能实际上错误的。

以下是一些关于如何让你的补丁被接受的技巧

2.1. 请发送补丁,不要发送整个存档或文件

如果你的更改包含代码中不存在的新文件,那么你当然必须发送整个文件。但是,如果你修改的是已存在的文件,请不要发送整个文件。发送一个差异(diff)文件;具体来说,发送运行 diff(1) 命令的输出,以比较基线发布版本和你修改后的版本。

diff 命令及其对应的 patch(1)(它自动将差异应用于基线文件)是开源开发最基本的工具。差异文件比整个文件更好,因为你发送补丁的开发者可能在你获得副本后更改了基线版本。通过向他发送差异文件,你可以省去他从你的更改中分离出他的更改的麻烦;你表现出对他的时间的尊重。

2.2. 发送针对代码当前版本的补丁。

向维护者发送针对几个版本之前的代码的补丁,并期望他完成所有工作,以确定哪些更改与他后来完成的事情重复,以及哪些事情实际上是你的补丁中的新内容,这既适得其反,又很粗鲁。

作为补丁提交者,你的责任是跟踪源代码的状态,并向维护者发送一个最小的补丁,表达你希望对主线代码库执行的操作。这意味着发送针对当前版本的补丁。

如今,实际上所有开源项目都通过公共匿名访问项目的版本控制仓库来提供其源代码。确保你正在修补最新版本的代码的最有效方法是从项目的仓库中检出代码的 head 版本。所有版本控制系统都有一个命令,可以让你在你的工作副本和 head 之间创建差异;使用它。

2.3. 不要包含针对生成文件的补丁。

在你发送补丁之前,检查一下,并删除补丁中用于文件的任何补丁段,这些文件将在他应用补丁并重新构建后自动重新生成。这种错误的经典例子是由 BisonFlex 生成的 C 文件。

现在,这种错误最常见的形式是发送一个差异文件,其中包含一个巨大的补丁段,而这个补丁段仅仅是你的 configure 脚本和他之间的变更条。这个文件是由 autoconf 生成的。

这是不体谅人的。这意味着你的接收者要费力地从大量冗余信息中分离出补丁的真实内容。这是一个小错误,不如我们稍后将要讨论的一些事情那么重要——但它会对你产生不利影响。

如果你已经从项目仓库中检出了代码,并使用版本控制系统的 diff 命令来生成你的补丁,你可能会自动避免这种情况。

2.4. 不要发送仅调整版本控制 $-符号的补丁段。

有些人将特殊的标记放在他们的源文件中,这些标记在文件被检入时由版本控制系统展开:例如 RCS、CVS 和 Subversion 使用的 $Id$ 构造。

如果你自己使用本地版本控制系统,你的更改可能会更改这些标记。这实际上并没有什么危害,因为当你的接收者在应用你的补丁后将他的代码重新检入时,它们将根据他的版本控制状态重新展开。但是这些额外的补丁段是冗余信息。它们会分散注意力。不发送它们更体谅人。

这是另一个小错误。如果你把大事做对了,你会被原谅的。但你还是应该尽量避免它。

2.5. 请使用 -c 或 -u 格式,不要使用默认的 (-e) 格式

diff(1) 的默认 (-e) 格式非常脆弱。它不包含任何上下文,因此如果自你获取修改后的副本以来,基线代码中插入或删除了任何行,补丁工具就无法处理。

收到 -e 差异文件令人恼火,并表明发送者要么是极端新手、粗心大意,要么是笨手笨脚。大多数这样的补丁都会被毫不犹豫地扔掉。

2.6. 请在你的补丁中包含文档

这非常重要。如果你的补丁对软件的功能进行了用户可见的添加或更改,请在你的补丁中包含对相应的 man 手册页和其他文档文件的更改不要假设接收者会乐于为你记录你的代码,或者让未记录的功能潜伏在代码中。

良好地记录你的更改表明了一些优点。首先,这对你试图说服的人来说是体谅的。其次,它表明你充分理解你的更改的影响,足以向看不到代码的人解释它。第三,它表明你关心最终将使用该软件的人。

良好的文档通常是区分可靠贡献和快速而肮脏的 hack 最明显的标志。如果你花时间和精力来生成它,你会发现你已经完成了 85% 的工作,大多数开发者都会接受你的补丁。

2.7. 请在你的补丁中包含解释

你的补丁应包括封面注释,解释你为什么认为该补丁是必要的或有用的。这种解释不是针对软件的用户,而是针对你向其发送补丁的维护者。

注释可以很短——事实上,我见过的最有效的封面注释只是说“请参阅此补丁中的文档更新”。但它应该显示正确的态度。

正确的态度是乐于助人、尊重维护者的时间、冷静自信但不自负。最好表现出对你正在修补的代码的理解。最好表明你能理解维护者的问题。最好也坦诚地说明你认为应用补丁可能存在的任何风险。以下是一些示例,说明了我喜欢在封面注释中看到的解释性评论的类型

“ 我已经看到这段代码存在两个问题,X 和 Y。我修复了问题 X,但我没有尝试解决问题 Y,因为我认为我不理解我相信涉及到的那部分代码。”

“ 修复了当 foo 输入之一太长时可能发生的 core dump。当我在做这件事的时候,我顺便查找了其他地方类似的溢出。我在 blarg.c 的 666 行附近发现了一个可能的溢出。你确定发送者每次传输不能生成超过 80 个字符吗?”

“ 你有没有考虑过使用 Foonly 算法来解决这个问题?在 <http://www.somesite.com/~jsmith/foonly.html> 有一个很好的实现。”

“ 这个补丁解决了当前的问题,但我意识到它以一种令人不愉快的方式使内存分配复杂化。对我来说可以工作,但你可能应该在发布之前在重负载下对其进行测试。”

“ 这可能是功能蔓延,但我还是发送了它。也许你会知道一种更简洁的方式来实现这个功能。”

2.8. 请在你的代码中包含有用的注释

通常作为维护者,我希望在合并你的更改之前,对你的更改有很强的信心。这不是一个一成不变的规则;如果你有良好的工作记录,对于我来说,我可能只是在半自动地检查它们之前,随意地浏览一下这些更改。但是,你能做的任何有助于我理解你的代码并减少我的不确定性的事情,都会增加我接受你的补丁的机会。

你代码中的好的注释可以帮助我理解它。糟糕的注释则不会。

这是一个糟糕的注释的例子

/* norman newbie fixed this 13 Aug 2009 */

这没有传达任何信息。这只不过是你在我的代码中间留下的一个泥泞的领地靴印。如果我接受你的补丁(你已经降低了这种可能性),我几乎肯定会删除这个注释。如果你想要署名,请为项目包含一个补丁段NEWSHISTORY文件。我可能会接受那个。

这是一个好的注释的例子

/*
 * This conditional needs to be guarded so that crunch_data()
 * never gets passed a NULL pointer.  <norman_newbie@foosite.com>
 */

这个注释表明你不仅理解我的代码,还理解我需要拥有的信息类型,才能对你的更改充满信心。这种注释给予我对你的更改的信心。

2.9. 每个补丁只修复一个错误或添加一个新功能。

不要收集各种错误修复、新功能或其他东西,并将它们作为一个单独的差异文件发送。相反,为每个错误修复或功能构建一个单独的差异文件。每个单独的差异文件都应该使用代码的当前版本生成,并且不应该依赖于你或其他人之前发送的任何其他补丁。

这有助于维护者阅读和理解你的代码,并且他可以决定是否要接受或放弃这个单独的错误修复或功能。例如,如果你添加了一个功能,保证每个人都有完全的 root 访问权限,因为它在你的特殊设置中很有用,那么维护者可能不会接受你补丁的这一部分。当你发送一个单独的差异文件,将这个 root 访问功能与一些错误修复和其他有用的东西混淆在一起时,你很有可能维护者会忽略你的整个补丁。

对于每个作为单独差异文件的错误修复和新功能,维护者可以例如在几分钟内包含你的错误修复,并在稍后仔细查看你的新功能或安全问题。

依赖于其他补丁的补丁会引发类似的问题。如果维护者拒绝你的基本补丁,他就无法应用其他补丁。如果你无法避免这种依赖关系,请向维护者表示,你将捆绑一个包含他想要添加的部分或功能的补丁。