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