注释是代码的补充,是为了帮助读者更全面的理解代码。注释的多少与理解代码的难易程度并没有直接的关系,因此规定代码的注释率,是完全没有必要的。
用注释来说明“为什么”
一段时间以后,即使是代码作者也许都忘了当初代码为什么会写把代码成这样,因此用注释来说明“为什么”尤为重要。
以下代码摘自Linux 2.4.0内核:
void ll_rw_block(int rw, int nr, struct buffer_head * bhs[])
{
…………
/* Only one thread can actually submit the I/O. */
if (test_and_set_bit(BH_Lock, &bh->b_state))
continue;
…………
}
ll_rw_block尝试将一个记录块(一块文件内容)写入设备驱动,其中调用了test_and_set_bit,根据代码本身就能知道这里是要将记录块加锁,加锁成功就继续后续的处理,加锁失败就continue。但为什么要将记录块加锁?恐怕没有设备驱动背景的人就不太明白,因此代码上方做出了“为什么”的解释:对于同一个记录块,只能有一个线程对其进行submit(submit是指后面将会调用的submit_bh函数)。这样一来,读者就很容易理解代码的意图了。
避免在函数体中用注释说明“做什么”
代码在“做什么”,要依靠代码自解释。在一些规定了代码注释率的项目中,常常会看到一些画蛇添足的注释,比如:
i++; // i自身加1
当不得不用注释来说明一段代码在“做什么”时,应该考虑将这些代码封装为一个新的函数。以下代码摘自Linux-2.6.10内核源码,该函数的功能是在两个进程间拷贝一段内存页面。Linux采用分页式内存管理,因此其步骤分别为对页面目录、中间目录、页面表项的处理。在每一块代码上方,都有说明这部分代码是“做什么”的。另外,这些处理都集中在一个函数里,使得函数代码非常长,而且嵌套也很深。
int copy_page_range(…)
{
……
for (;;) {
……
/* copy_pmd_range */ <-拷贝页面目录
……
do {
……
/* copy_pte_range */ <-拷贝中间目录
……
do {
……
/* copy_one_pte */ <-拷贝页面表项
……
}
到了Linux-2.6.11的内核代码,这些部分已经被封装为函数,代码也变简洁,说明代码是“做什么”的注释自然也不再需要了:
int copy_page_range(...)
{
……
do {
……
err =copy_pud_range(…);
……
}
static inline intcopy_pud_range(...)
{
……
do {
……
err =copy_pmd_range(…);
……
}
static inline intcopy_pmd_range(…)
{
……
do {
……
err =copy_pte_range(…);
……
}
用注释删除代码
在小的迭代中,删除代码时建议采用注释的方式,而不要直接将代码删除。比如下面要删除对do_some_thing的调用:
/*不再支持该功能,liminyu 2012-5-27
do_some_thing();*/
这样做的好处是在进行代码比较时能更加清晰的知道修改的内容,比如在使用代码比较工具进行比较时,下面这样更为友好:
好的方式:
不好的方式:(不知道是左边新增了一行,还是右边删除了一行)
当然,这样做会导致垃圾代码。可以在项目进行到一定阶段时,再统一对这些垃圾代码进行清理。
强注意事项
一个大型的项目中,多人维护同一份代码是不可避免的,而且不同的开发者可能在不同的时期维护同一份代码,他们也许都不可能直接面对面交流,因此通过代码进行交流是一个较好的途径。
在代码中难免会存在一些特定的约束,或者修改代码时容易导致错误的陷阱。新的开发者刚开始时往往都容易犯这些错误。避免这些问题的一个较好的方法是前任开发者把这些注释事项都以醒目的方式写在注释中,以提醒后继的开发人员。
注意,上面提到这是一个“较好”的方法。那“更好”的方法是什么呢?对于避免修改代码时犯错,以下方法的“好”的程度递减:
用代码直接进行看护(assert)> PC用例进行看护 >在注释中进行强调 > 写在文档中
修改代码时同步更新注释
注释的描述和代码本身不一致,也许是让所有开发者都很痛苦的事之一。因此在修改代码时,记得先读一下之前的注释,如果新代码与注释不一致了,记得要同步更新注释。
小结
注释应该用来说明“为什么”,避免说明“做什么”。
不要对注释率进行规定。
在小的迭代中,用注释来删除代码。
注释说明修改时的注意事项。
修改代码时同步更新注释。