Clean Code之注释
在很多源码中,注释常常与代码相伴而行。有的作为作者信息声明,有的作为程序逻辑解读,有的作为更改时间记录。或多或少都是留下注释的痕迹。
在初学编程之际,一把梭的编程手法,总是会让观者无法理解具体含义,往往就采用了注释的方法在各行各处声明所书写代码的含义。
为什么要用注释
随着渐渐地深入学习,也听到那句“代码即注释”的名言。相信大部分读者都听过这么一句。
那么注释是否是真的就没有必要性呢?我们可以看下下面这段关于公平锁和非公平锁实现源码中的核心差异代码。不能说有注释就好,但确实使读者对代码的理解更加容易。
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
但是,如果能把t,h,s赋予合适有意义的命名,或者这段代码就可以删减其注释。关于如何更好的命名,读者可查看第一集内容“Clean Code之命名”。
好的注释必然有用,但往往注释会乱七八糟,会随着时间变得失效、错误。
所以,我们更该用尽心思去编写好的代码,以保证无需编写多余的注释。
若编程语言足够有表达力,或者我们长于用这些语言来表达意图,就不那么需要注释——也许根本不需要。
在程序中,代码是唯一真正准确的表达信息来源。只有代码能忠实的告诉你它做的事。
如果说到为什么要写注释的原由?有时,代码不足以解释其自身的行为,则需要注释进行补充说明。比如
// check to see if the employee is eligible for full benefits
if (employee.flags & HOURLY_FLAG) && (employee.age > 65)
但是,若是使用代码也可达成同样的效果,解释代码的意图。例如:
if (employee.isEligibleForFullBenefits())
写注释的常见动机之一是糟糕的代码的存在,编写模块后发现代码并不能清晰让人理解其所做所为,因此才需要注释来解释并不尽人意的代码。
本文结合《Clean Code》注释章节和《Alibaba Java 开发手册》部分内容,总结书写注释常见问题及何为好注释。
什么是坏注释
如上所述,坏注释常常会出现我们并不在意的地点。比如:毫无意义的注释、因时间太久已经失效的注释、用于记录更新的注释、为了注释而注释的注释等等。
本节对常见坏注释进行总结,以警示坏注释出现的场合。
- 毫无作用的注释:比如所书写的注释并不能提供比代码本身更多的信息;亦或者注释表述不清晰,更多只是为了提醒书写者自身;亦或者本身注释就是为了注释而已。
// 例如:以下注释毫无作用
// logger name
protected String logName = null;
// default constructor
protected AnnualDataRule
//
public void loadProperties(){
...
...
{
// this comment is used to remind me to implement handle exception if necessary
}
}
- 误导性注释:其实很多时候,由于需求变更或者产品迭代升级导致代码被大幅修改,而在之前遗留的注释却没有同步更新或删除,那么遗留的注释会产生误导性作用导致读者无法理解代码与注释思想不对等的情况。这种时常发生,所以,读者应当注意,若无法书写准确无误的注释并及时更新,应主动避免书写注释的现象。
- 为了注释而注释的注释:代码中本不需对每个变量和方法都书写注释,例如有些要求每个函数都要有Javadoc,这只会使程序更加难以阅读。也许你也曾遇到类似于日志型注释或者用于归属与署名的注释,即每次有同事更新此段代码时,都会在代码内部添加一条注释记录此次修改的人员和内容,这也许在上古时代还有一定必要性,现在的程序已经使用了源码版本控制系统(比如git、svn),因此现在不再需要这样的注释。
// user name
private String userName;
// user age
private String userAge;
- 括号后的注释:有时候,当函数过长时,为了更清楚显眼的找到代码块的结尾,会使用在右括号后增加代码注释的形式,比如
// if 、 // while等待形式。那么当出现这类情况时,应当如何呢?当时思考如何书写更优雅的函数,让每个函数都更有意义。若不懂如何更好的书写函数,可查看本系列第二季“Clean Code之函数”。 - 注释掉的代码:不需保留,请删除请删除请删除。
- 与代码无关的注释:比如注释与函数毫无干系,并未描述当前函数,可能描述的是整个业务模块。或者注释描述及其不清晰,仍需细读代码结合注释才能理解业务。
- 函数头的注释:当你需要为一个函数头书写注释的时候,你更应该思考,如何为只做一件事的函数书写一个更优雅的名字。
// get user name
public String gun() {}
public String getUserName() {}
也许读者会纳闷,为什么直接先讲些坏注释的示例呢?
当你看到以上总结时,你会或多或少看到自己曾经的影子,比如作者曾经常书写括号后的注释,并且很少删除注释掉的代码。那么通过反思自身就会更感受到好注释的优点。
什么是好注释
了解了以上几类坏注释出现的场景,我们一起看看《Clean Code》中对好注释的场景总结。
好的注释必然首先是有意义的,值得去书写的。
唯一真正好的做法仍旧是想方设法不书写注释。
- 法律信息相关注释:有时,为保证权益,需要书写法律相关的注释。其不需将具体法律条律内容书写出来,只将注释内容指向标准许可或其他外部文档即可。
- 解释性注释:不仅将难以理解的内容翻译成某种可读的形式,还对此代码实现的意图进行解释。这样的注释更易让读者清晰明确书写者的目的和起因。但也不是必要的。
- 用于警示或放大作用的注释:当代码对系统极其重要,可设立警示型代码提示代码改动可能出现的结果是有必要性的,或者用注释去放大这部分代码的重要性也是可性的。
- TODO注释:常见的注释,定期查看,删除不需要的即可。
经过简单的分析,我们其实也可以发现,非必要情况,一定一定不要使用注释是十分重要的。注释并不能美化糟糕的代码。所以,我们在编写代码时,更应考虑的是如何减少注释量,保证“代码即注释”。
什么也比不上放置良好的注释来得有用。什么也不会比乱七八糟的注释更有本事捣乱一个模块。什么也不会比陈旧、提供错误信息的注释更有破坏性。
希望本文可以提示读者,从现在开始,反思之前的坏注释,考虑之后相同场景如何避免注释,为书写好的注释做好理论基础。
Reference