创建干净的补丁

来自 PostgreSQL Wiki
跳转至导航跳转至搜索

创建格式更好的补丁示例。由 格雷格·史密斯(PostgreSQL 主要贡献者和自豪的格式吹毛求疵者)原创撰写

如果您对人们对身为一名成功程序员“卫生”重要性的感受进行调查,您可能得不到非常恭维的回应。但优秀的编码人员以对编码卫生挑剔而著称,尤其是格式。有一些不好的方法可以了解这一点。向严格执行标准的开源项目提交格式不佳的内容就是一种方式。在您通过交换补丁与某人进行协作之前,您确实还不会知道自己应该怎么做。

看到新补丁提交者努力学习良好的做法激发了这篇文章。它介绍了您为创建一个好的补丁需要了解的无聊小知识。在本例中,目标是使用 git 作为您的版本控制工具,以满足 PostgreSQL 的编码标准(请参阅开发者常见问题解答中的“PostgreSQL 源代码中使用哪种格式样式?”)。这种工作介于使用 Git提交补丁在本项目的正常开发顺序中。但对于形成一个好的补丁来说,基本概念应适用于以源代码补丁的形式交换代码的任何项目。

补丁编码

我们需要一个补丁首先进行调整,所以这是一个微不足道但有用的示例。向表中添加新数据时,PostgreSQL 中可能会出现一些瓶颈,数据库称之为“关系扩展”。其思想是,如果您在表或索引中(两种常见的类型关系)分配新空间,数据库有时会使关系数据库页大 8K。对该代码进行试验的第一步是添加一些日志记录以确认正在源代码中的预期位置进行,这是我将在此处探讨的补丁。我编写了一个快速补丁,从附近的代码中借用部分内容,我在使用 pgbench 命令填充数据库时结果如下所示

INFO:  extending relation base/16494/16507 to add block 16373
CONTEXT:  COPY pgbench_accounts, line 998754: "998754	10	0	"
INFO:  extending relation base/16494/16507 to add block 16374
CONTEXT:  COPY pgbench_accounts, line 998815: "998815	10	0	"

这是个开始。我可以确切地看到文件增长有多快,如果需要,我可以解码关系文件名,我甚至可以使用此日志估算有多少行输入数据适合数据库页。在进行代码更改后,我运行“git diff”以获得一个补丁。

尊重你的读者

这正是某些人第一次犯错的地方:由于代码有效,因此他们“运送”此补丁作为提交,而无需进一步完善。你编写的代码并非是最终产品;补丁差异才是。在提交补丁给项目之前,你应与审查新代码一样谨慎地审查补丁,查看补丁涉及的所有内容。这可以捕获格式错误,并且修复其中一些问题将使你的补丁更小。较小的补丁意味着较少的工作供审稿人用于决定该补丁是否有价值,并减少了制定人用于应用该补丁的工作量。(在较小的项目中,该人可能是同一人。)最大程度地减少某人审查你的补丁所需的时间极大地增加了该补丁被接受到开源项目中的可能性。

缩小补丁差异大小

让我们看看我的琐碎日志补丁产生了什么结果

diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f96685d..a257b9d 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -420,11 +420,14 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
        bufBlock = isLocalBuf ? LocalBufHdrGetBlock(bufHdr) : BufHdrGetBlock(bufHdr);
 
        if (isExtend)
-       {
+    { 
                /* new buffers are zero-filled */
                MemSet((char *) bufBlock, 0, BLCKSZ);
                smgrextend(smgr, forkNum, blockNum, (char *) bufBlock, false);
-       }
+        elog(INFO, "extending relation %s to add block %u",
+            relpath(smgr->smgr_rnode, forkNum),
+            blockNum);
+    }
        else
        {
                /*

所有的垃圾信息都在做什么?我所希望的是我添加的三行新的“elog”,这是此处的唯一内容。但是有所有这些额外的垃圾。这是为什么?嗯,我故意犯了一个很多人会意外犯下的错误。包含大括号的行:{ } 在开发补丁时接触过,即使文件中的最终内容看起来相同,我的编辑器也破坏了该行。PostgreSQL 公约表明缩进应使用 4 个字符制的制表符完成。我的编辑器将它们改为了空格。这就是你看到一个删除带有左大括号的一行的“-”符号的原因,然后在其后有一行更改内容,将其添加回来。为了解决这个问题,我需要返回并重新格式化我接触到所有行,将大括号前的空格更改到正确的制表符大小。我进行了一轮处理,然后获取另一个差异

diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f96685d..a240c79 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -420,10 +420,13 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
        bufBlock = isLocalBuf ? LocalBufHdrGetBlock(bufHdr) : BufHdrGetBlock(bufHdr);
 
        if (isExtend)
-       {
+       { 
                /* new buffers are zero-filled */
                MemSet((char *) bufBlock, 0, BLCKSZ);
                smgrextend(smgr, forkNum, blockNum, (char *) bufBlock, false);
+               elog(INFO, "extending relation %s to add block %u",
+                       relpath(smgr->smgr_rnode, forkNum),
+                       blockNum);
        }
        else
        {

这样会更好......但剩余的问题更为微妙。为什么它在这里删除左大括号,然后立即再次添加它?嗯,在这种情况下,我引入的错误是添加一些尾随空格。你在此处看不到它,但事实上我接触到的那行是“{ ”,而原始行只是“{”。尝试在你的网络浏览器中突出显示它,你可能能够在那里看到它。这使得此差异比必要的更大,并违反了代码约定。

不可见空格

此时你可能正在思考“我该如何找到不可见空格以删除它?”,这是一个优秀的问题。一种选择是使用彩色终端并执行差异如下所示

git diff --color

在这种模式下,我添加的不需要的空间立即消失。进行编辑以修复,新的差异

diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f96685d..eec261b 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -424,6 +424,9 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
                /* new buffers are zero-filled */
                MemSet((char *) bufBlock, 0, BLCKSZ);
                smgrextend(smgr, forkNum, blockNum, (char *) bufBlock, false);
+               elog(INFO, "extending relation %s to add block %u",
+                       relpath(smgr->smgr_rnode, forkNum),
+                       blockNum);
        }
        else
        {

这是值得自豪提交的补丁差异的样子。它非常明显地展示了更改的内容。没有任何无关的更改使问题复杂化。选项卡和空白正确使用,从而使得所有新代码与现有代码对齐。

一个干净的补丁提交更有可能因其代码优点而被考虑,而一个混乱的补丁会浪费审阅者的时间,并且从一开始就自动使他们对你产生偏见。回顾一下与这个补丁相比,阅读原始补丁是多么混乱。如果你以前从未见过此代码变更,修改了哪些内容在这里很明显。在较早的版本中,所有空白更改使得提取该含义变得更加困难。尽管此处实际代码没有差异!编译器在两种情况下都会执行完全相同的事情。不过,这是为了使补丁对人可读,并且遵循项目的编码标准会提高你的可能性。你可以考虑使用文本编辑器自动删除尾部空白,不过这是你只希望有选择地进行的操作,因为在编辑其他类型的文件时,尾部空白可能是适当且必要的。

通过变基合并修复

对于 git 而言更具体(此处的大多数想法适用于任何版本控制系统),定期变基在几个方面对更干净的补丁创建很有用。假设在你本地分支提交更改之后才发现空白错误。你可以在额外的提交中修复它,但是你的代码历史将更加混乱。如果你尚未将本地树发送到任何其他位置,那么你可以轻松地处理此问题。将空白修复提交为新的提交,然后运行

git rebase -i origin/master

你用原始分支的名称替换那里最后的部分,原始分支是你希望针对它创建差异的分支。你将看到以下历史记录

pick 57c7bfe Awesome new feature
pick 08dcf68 Whitespace cleanup

你现在可以编辑它,以便将第二个补丁“合并”到第一个补丁,方法是将其更改为如下所示

pick 57c7bfe Awesome new feature
s 08dcf68 Whitespace cleanup

保存该文件,你将获得第二个编辑器屏幕来调整合并后的提交消息。擦除与空白清理相关的内容,保存它,现在这个小空白错误已经成为历史。我有时会做几次这样的操作,每次都压缩更多空白或代码

git diff --color HEAD^ HEAD
vi <file>
git commit -a
git rebase -i origin/master

值得在测试代码上练习这个循环:提交、差异、修复空白、重新合并、新差异。不幸的是,使用重新合并很容易损坏你的工作,因此请小心谨慎。在我的偏执狂下,我通常会每次通过这个循环时保存一份初始补丁的副本。如果你出错,它可用作备份,你可以使用诸如“wc”之类的工具来跟踪在每次循环中缩小其大小的改进。(当然,有一种更快的方法可以使用 git 来完成这一系列步骤,但这种方法易于理解和记忆)

如果您在足够长的时间内开发补丁程序,并且在针对其进行开发的主树发生显著变化的情况下,学习如何很好地使用重新设定也很重要。您可以移动较早的提交,使其成为提交历史中最新的提交,这对于获得提交到项目的干净差异也很关键。

耐心差异

差异有的时候会生成输出,虽然这些输出是干净的,但不是表示新添加内容的最佳方式。最常见的情况是使用大括号的代码块最终超出同步一行,而您的新代码显示为以一种奇怪方式重复使用现有代码中的开始大括号或结束大括号。差异有一种可用的备用方法,称为“耐心”,它通常在这种情况下效果更好。

git diff --color HEAD^ HEAD --patience

耐心差异优势的描述和示例显示了这种方法的工作原理。如果您的代码因为重复使用碰巧包含在您的添加内容中的现有行而奇怪地偏移,您应该尝试耐心模式并查看它是否生成更干净的补丁程序。

代码阅读和差异补丁创建

以补丁程序的形式阅读您的代码变更出于很多原因都是一项极好的练习。通过这种方式查看我自己的代码,我发现了很多错误。有的时候查看差异会让我想到减少我已变更内容的代码占用空间的方法,这会导致更小,更干净的补丁程序。对于一个实质性的补丁程序,成功的补丁程序贡献者在提交到项目之前轻松花一个或多个小时进行此类工作,调整格式,将补丁程序精简到最低限度。它让您的代码变得更好,也让它更有可能被阅读、接受和提交。当我要合并提交到我负责的一个项目,或者查看提交到 PostgreSQL 时,发现基本的格式错误或一个混乱的差异意味着我开始设想代码的其余部分也不太可能是干净的。这并不是您想要留下的第一印象。

您可能认为这些都是无聊的苦差事,但完美掌握代码的细节是优秀程序员培养出的习惯之一。如果您想对任何软件项目贡献代码,最小化差异的大小并确保它尽可能干净是最重要的技能之一。差异不仅仅是编码过程的输出:它是一个独立的产品,您应该仔细且直接地监控它的良好质量。该补丁程序是对项目永久历史记录的提议添加项,并且补丁程序中的错误、遗漏和不必要的部分会使项目历史记录变得复杂,并影响诸如精挑细选、恢复和二分查找之类的流程。