[转]Redis是如何写代码注释的?
许多人认为,如果代码写得足够扎实,注释就没什么用了。在他们看来,当一切都设计妥当时,代码本身会记录其作用,因此代码注释是多余的。我对此持不同意见,主要出于两个原因:
1. 许多注释并未起到解释代码的作用。
2. 注释使读者不必凭空想象太多细枝末节,帮助读者降低认知负担。
我的工作始于随机地阅读Redis源代码,以检查注释是否以及为什么在不同的上下文中起作用。我很快发现,注释的作用来源于多方面:它们在功能,编程风格,长度和更新频率方面往往非常不同。我最终转向了注释分类。
在研究期间,我确定了九种注释类别:
* 函数注释 Function comments
* 设计注释 Design comments
* 原因注释 Why comments
* 教学注释 Teacher comments
* 清单注释 Checklist comments
* 引导注释 Guide comments
* 琐碎注释 Trivial comments
* (代码)负债注释 Debt comments
* 备份注释 Backup comments
在我看来,前六个主要是非常积极的注释形式,而最后三个有点值得怀疑。在接下来的部分中,我将使用Redis源代码中的示例分析每种注释类型。
1. 函数注释
函数注释的目标是防止读者直接阅读代码。
在阅读注释之后,读者应该可以将一些代码视为应遵守某些规则的黑箱子。通常情况下,函数注释位于函数定义的顶部。
rax.c:
1 2 3 4 5 6 |
/ * 在当前节点的子树中寻找最大的key。 如果内存不足返回<span class="">0</span>,否则 返回<span class="">1.</span> * / <span class="">int</span> raxSeekGreatest(raxIterator * it){ ... } |
函数注释实际上是一种内联API文档。如果函数注释编写得好,那么用户在大多数时候能跳回到她正在阅读的内容(如阅读调用此类API的代码),而无需阅读函数(function),类(class),宏(macro)等的实现过程。
在所有注释类型中,函数注释被整个编程界广泛接受和需要。要分析的唯一一点是:在代码内部放置以API参考文档为主的注释是否是件好事。
对我来说答案很简单:我希望API文档与代码完全匹配。随着代码的更改,文档也得到更改。出于这个原因,我们将函数注释用作函数或其他元素的序言,使API文档接近代码,完成三个任务:
* 随着代码的更改,我们可以轻松更改文档,API参考也不会有过时的风险。
* 这种方法使得更改者(理应是最清楚更改目的的人)在最大限度上成为API文档的更改者。
* 读者能通过阅读代码直接找到函数或方法(method)的文档,以便阅读代码的读者只关注代码,而不是代码和文档之间的上下文切换。
2. 设计注释
“函数注释”通常位于函数的开头,而设计注释通常位于文件的开头。
设计注释一般说明了给定代码片段使用某些算法、技术、技巧和具体实现的方式和原因,对代码中实现的内容进行了更高级别的概述。在这样的背景下,阅读代码会更简单一些。
bio.c
1 2 3 4 5 6 7 8 9 |
*设计 * ------ * *设计很简单,我们用一个结构代表要执行的一项 Job *每种Job类型有不同的线程和Job队列。 *每个线程都在等待队列中的新Job,并按照顺序处理 *每个Job。 ... |
3. 原因注释
原因注释解释了代码执行某些操作的原因——即使代码执行的操作非常明确。请看以下来自Redis replication的代码 的示例。
replication.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<span class="">if</span>(idle> server.repl_backlog_time_limit){ <span class="">/* 当我们释放 backlog时,我们总是使用新的 * replication ID并清除ID2。这是 * 因为在没有backlog时,master_repl_offset * 未更新,但我们仍会保留我们的 * replication ID,由此导致以下问题: * * 1.我们是一个主实例(master instance)。 * 2.我们的副本成为主服务器(Master)。repl-id-2将会 * 与我们的repl-id相同。 * 3.我们作为主服务器,收到了一些更新命令,但不会 * 增加master_repl_offset。 * 4.稍后我们将变成副本,连接到新的 * 主服务器,它将接受我们第二个副本ID的 * PSYNC请求,但会有数据不一致的情况 * 因为我们接受了写命令。* / changeReplicationId(); clearReplicationId2(); freeReplicationBacklog(); serverLog(LL_NOTICE, "Replication backlog freed after %d seconds " "without connected replicas.", (int) server.repl_backlog_time_limit); } </span> |
如果我只检查函数调用,就没什么需要纠结的:如果超时了就更改主replication ID,清除辅助ID,最后释放replication backlog。
4. 教学注释
教学注释不会试图解释代码本身或我们应该注意的某些副作用。教学注释教授的是代码运行的“领域”(例如%