首页 » 编写高质量代码:改善Java程序的151个建议 » 编写高质量代码:改善Java程序的151个建议全文在线阅读

《编写高质量代码:改善Java程序的151个建议》建议146:让注释正确、清晰、简洁

关灯直达底部

从我们写第一个/"Hello World/"程序开始,就被谆谆教导“代码要有注释”,而且一加就是好多年,基本上是代码就有注释,而且有的还很多,甚至有的是长篇累牍的。我们先来看一些不好的注解习惯。

(1)废话式注释

比如这样的注释:


/*

*该算法不如某某算法优秀,可以优化,时间太紧,以后再说

*/

public void doSomethong(){

}


想说明什么?只是表明这个算法需要优化?还是说这是未完成的任务?那可以用TODO标记呀,注释是给人看的,此类代码提交上去后,基本上不会再修改它了,除非它出现Bug,或者维护人员碰巧看到这个注释,然后选择了优化它——注释不是留给“撞大运”人员的。

(2)故事式注释

曾经检查过一段极品代码,注释写得非常全面,描述的是汉诺塔算法,从汉诺塔的故事(包括最原始的版本和多个变形版本)到算法分析,最后到算法比较和实际应用,写得那是栩栩如生,而且还不时加入了一些崭新的网络用语,幽默而又不失准确,可以这么说,看完这段注释基本上对汉诺塔的“前世今生”有了深刻的了解,但是我在检查后的改正意见是:把注释修改为“实现汉诺塔算法”即可。注释不是让你讲故事的地方,就这7个字,已经完全可以说明你的代码了!

我们的代码是给人看的,但不是给什么都不懂的外行看的,相信我们代码的阅读者一定是具有一定编码能力的,不是对代码过敏的“代码白痴”。

(3)不必要的注释

有些注释相对于代码来说完全没有必要,算不上是废话,只能说是多余的注解,看下面的例子。


class Foo{

//默认值为0

private int num;

//取值

public int getNum(){

return num;

}

//输入int类型变量,无返回值

public void setNum(int num){

this.num=num;

}

public void doSomething(){

//自增

num++;

}

}


以上四个注解是不必注释的典型代表(本书的代码上也有一些类似的注释,只是为了阐明代码片段,不用作生产代码):

第一个默认值完全没有必要说明,相信代码的阅读者这点智商还是有的,他应该明白实例变量初始值为0,即使加上个初始值也完全没有必要注释,除非有特殊含义。

第二个注释在get方法上,如此简单的代码,看代码比看注释所花费的时间长不了多少,不要低估了代码阅读者(很可能就是你,代码的编写者)的智商。

第三个注释描述了输入和输出参数类型,相信IDE吧,相信它会这么“智能化”的提示吧(基本上每个IDE都能实现输入补全和输入输出提示)!不需要我们手工撰写。这些注释难道是为那些用记事本编写代码的狂人准备的?可是在看到输入的int类型,输出的void后,难道他还不能明白吗?——注释完全多余了。

第四个注释也蔑视了代码阅读者的智商,这是编码的最基本算法,不用注释。

(4)过时的注释

注释与代码的版本不一致,注释是1.0版本,而代码早已窜到了5.0版本,相信读者对此类注释深有感触:代码一直在升级,但注释永远保持不变,直到有一天,某一个“粗心”的家伙根据注释修正了一个代码缺陷,然后发现产生了连锁的缺陷反应才知道“代码世界”已经早已发生了变化,而此处的注释只是描述了最原始的信息。

这类注释不仅仅会出现在你我的代码中,同时也会出现在一些非常著名的开源系统中,毕竟注释不参与运行,只是给人看的,修改代码而不修改注释照样可以运行,添加注释只是“额外任务”而已。解决此类问题的最好办法就是保持注释与代码同步。

(5)大块注释代码

可能是为了考虑代码的再次利用,有些大块注释掉的代码仍然保留在生产代码中,这不是一个好的习惯,大块注释代码不仅仅影响代码的阅读性,而且也隔断了代码的连贯性,特别是在代码中的间隔性注释,更增加了阅读的难度,会使Bug隐藏得更深。

此类注释代码完全可以使用版本管理来实现,而不是在生产代码中出现。这里要注意的是,如果代码临时不用(可能在下一版本中使用,或者在生产版本固化前可能会被使用),可以通过注释来解决,如果是废弃(在生产版本上肯定不用该代码),则应该完全删除掉。

(6)流水账式的注释

比如有这样的注释:


/**

*2010-09-16张三创建该类,实现XX算法

*2010-10-28李四修正算法中的XXXX缺陷

*2010-11-30李四重构了XXX方法

*2011-02-06王五删除了XXXX无用方法

*2011-04-08马六优化了XXX的性能

*/

class Foo{

}


相信读者看到过很多这样的注释,而且深以为这样的注释是一种好的方式,如果没有版本管理工具,这确实是一种非常好的注释,可以很清晰地看出该类的变化历程,但是有了版本管理工具,此类注释就不应该出现在这里了,应该出现在版本提交的注释上,版本管理可以更加清晰地浏览此类变更历程。

(7)专为JavaDoc编写的注释

在注释中加入过多的HTML标签,可以使生成的JavaDoc文档格式整齐美观,这没错,但问题是代码中的注释是给人看的,如果只考虑JavaDoc的阅读者,那代码的阅读者(很可能就是一年后的你)就很难看懂代码上的注释了,能做的办法就是生成JavaDoc文档,然后文档和代码分开来阅读,这是一种让人迅速崩溃的“绝世良药”。

建议在注释中只保留<p>、<code>等几个常用的标签,不要增加<font>、<table>、<p>等标签。

解释了这么多不好的注释,那好的注释应该是什么样子的呢?好的注释首先要求正确,注释与代码意图吻合;其次要求清晰,格式统一,文字准确;最后要求简洁,说明该说明的,惜字如金,拒绝不必要的注释,如下类型的注释就是好的注释:

(1)法律版权信息

这是我们在阅读源代码时经常看到的,一般都是指向同一个法律版权声明的。

(2)解释意图的注释

说明为什么要这样做,而不是怎么做的,比如解决了哪个Bug,方法过时的原因是什么。像这样一个注释就是好的:


public class BasicDataSource implements DataSource{

static{

//Attempt to prevent deadlocks-see DBCP-272

DriverManager.getDrivers();

}

}


(3)警示性注释

这类注释是我们经常缺乏的,或者是经常忽视的(即使有了,也常常是与代码版本不匹配),比如可以在注释中提示此代码的缺陷,或者它在不同操作系统上的表现,或者警告后续人员不要随意修改,例如tomcat的源码org.apache.tomcat.jni.Global中有这样一段注释:


public class Global{

/**

*Note:it is important that the APR_STATUS_IS_EBUSY(s)

*macro be used to determine

*if the return value was APR_EBUSY, for portability reasons.

*@param mutex the mutex on which to attempt the lock acquiring.

*/

public static native int trylock(long mutex);

}


(4)TODO注释

对于一些未完成的任务,则增加上TODO提示,并标明是什么事情没有做完,以方便下次看到这个TODO标记时还能记忆起要做什么事情,比如在DBCP源代码中有这样的TODO注释:


public class DelegatingStatement extends……implements……{

/*

*Note was protected prior to JDBC 4

*TODO Consider adding build fags to make this protected

*unless we are using JDBC 4.

*/

public boolean isClosed()throws SQLException{

return_closed;

}

}


注释只是代码阅读的辅助信息,如果代码的表达能力足够清晰,根本就不需要注释,注释能够帮助我们更好地理解代码,但它所重视的是质量而不是数量。如果一段代码写得很糟糕,即使注解写得再漂亮,也不能解决腐烂代码带来的种种问题,记住,注释不是美化剂,不能美化你的代码,它只是一副催化剂,可以让优秀的代码更加优秀,让拙劣的代码更加腐朽。

注意 注释不是美化剂,而是催化剂,或为优秀加分,或为拙劣减分。