在学校里学习时,老师对注释的要求比较严苛。
你写代码的时候不写注释,过一段时间你自己都看不懂了,何况别人?
上一句还属于常规观点。某个老师甚至这么说:
C语言,注释量应该和代码量相当。
当时,我是心中敬仰的。然而,工作后发现,实际工程领域之中,并没有那么多注释。
而且,逐渐发现,大部分情况下不需要、也不应该注释。
由于工作中以Java为主——这真是一个悲伤的故事——以下代码以Java为例。
1. 错误的注释不如没有
注释写出来,就是给人看的。如果注释是错的,就会误导代码阅读者。
/**
* I am a boy, I like girls.
*
* @param someone is a Person who is welcome or not.
*
* @return true if someone is a Person you like.
*/
private boolean isWelcome(Person someone) {
return someone.isBoy();
}
比如上面那段,代码是个同性恋写的,注释的则是异性恋;或者,当他写注释的时候以为自己性取向很正常,写代码的时候心中忽然涌上一阵明悟……
这类注释在实际工作中碰到很多。在维护代码、或者是在以前的基础上开发代码的过程中,往往是进行增量修改。某些时候,Method内的实现已经改了,而代码注释未变,就容易出现二者不一致,甚至意义完全相反。
修改代码时,一定要先修改相关注释。这就带来了维护成本的提高。
问题是:增加的维护成本以及误导几率,是否能被注释带来的收益所抵消?
2. 没有额外语意的注释不如不写
注释不仅要说明,还必须要有额外语意,或者更简单地表达了与代码实际效果相同的意思。
public class Person {
/** The name of the Person. */
private String name;
/** The phone number of the Person. */
private String phoneNumber;
}
上面那段,这样的注释,有什么意义?
-
name
不就是name吗?难道还需要再说一遍,或者翻译成中文? -
phoneNumber
不就是电话号码吗?难道要在注释里拆开,代码的阅读者才能看懂?
这类为了注释而注释的注释,除了碍眼,并无意义。而且,还容易出现第1类问题。
比如,以后人类都不用注册、购买电话号码了,直接用身份证号作为联系方式。上面的代码需要把phoneNumber
改为id
,修改者会不会记得改注释?
他心中一定有一万头草泥马在狂奔:还不如删掉!
3. 必要的注释是代码的失败
其实,看代码的人都是码农……呃,我是指,看代码的人都是能看代码的人。一般不会存在没有注释就看不懂的代码。
注释只不过是加速理解代码的过程。
而且,看了注释,不代表就不需要看代码。因为有可能存在第1类问题。
/**
* @return true if this is a girl and she is beautiful.
*/
public boolean isGood() {
int value = this.getValue();
// If the 1st bit of value is 0, means this is a girl.
// If the 2nd bit of value is 0, means this person is beautiful.
if ((value & 0x01) == 0 && (value & 0x02) == 0) {
return true;
} else {
return false;
}
}
以上代码中,Method的注释已经写得很清楚了,isGood()
是看这个人是不是个美女。
这段注释的确加速了理解,因为isGood()
并不是很直观。什么样的好才是好?
但是,为什么不把Method命名为isBeautifulGirl()
?表达了同样意义的同时,也不用阅读者跳转到定义位置来看。
其次,在实现中的这段注释的确加速了理解,不然这个位操作要看很久(视脑年龄而定)。
但是,特么谁准你在Java里用位操作?!
不管你这个int
里存了多少位的信息,为什么不能拆分成多个值?
如果拆分成多个值,每个值都给予恰当的命名,配以适当的Method,为什么还需要注释?
例如以下代码,需要注释吗?
public boolean isBeautifulGirl() {
return isGirl() && isBeautiful();
}
当你被自己的代码逼迫,以致要写注释之前,你应该问自己:是不是这段代码写得太糟了?要么再改改?
4. 千万不要写中文注释
虽然代码往往都是以英文为基础的,代码旁边配以大段的英文注释也确实令人抓狂。
但是比英文更令人抓狂的是乱码!
更令人抓狂的是,怎么转换都是乱码的乱码!
/**
* ��Ҳ��֪�����Ǹ�ʲô��
*/
public void doSomething() {
}
谁能把这个乱码转回去,看看我注释里写了什么?
另外,是不是有一种删掉的冲动?
好的代码本身就是注释
Person person = I.meet();
if (person instanceof Girl) {
Girl she = (Girl) person;
if (she.isBeautiful()) {
if (she.isKind()) {
I.marry(she);
} else if (she.isBitch()) {
I.fuck(she);
} else {
I.play(she);
}
} else {
I.doNothing();
}
} else {
I.stayLonely();
}
以上代码,虽然if
深了一点,但是不需要注释也完全可读。哪怕从自然语言的语法角度看也问题多多,不过,代码的逻辑就在那里,比任何自然语言更准确地描述了程序的实际运行状况。
There are a thousand Hamlets in a thousand people's eyes.
一千个读者就有一千个哈姆雷特。
——莎士比亚(Shakespeare)
如果你强行要把编程语言编译成自然语言,你能确定你表达了你想表达的,你确定你想表达的就是程序实际运行的?
你确定没有犯前面的四类问题?
注释,需要是表达了和代码相同涵义(第1类问题)、而又不能是相同语意(第2类问题)、在代码比较复杂或难读(第3类问题)、并且不能用ASCII以外的字符的情况下,写在代码旁边解释代码的东西。
(其实一句话就能打败大多数中国程序员:你英文写得比代码好?)
所以,还是让一切尽在代码中吧!
Code is poetry.
log即注释
在实际的工作中,你可以不写注释,但不得不写log。
注释是写给代码阅读者看的,并不参与编译。仅存在与代码中,编译后的软件里并不存在。
而log是为了调试、后续debug的方便而添加的。良好的log,可以让我们在log文件中,看清软件的实际运行状态。
在阅读代码时,
一些例外
注释,这种语法的存在还是有必要的,只是大部分程序员其实根本用不到、或用了以后应该马上删除。
API
例如,最应该写注释的,是提供给别人——通常是另一个team、甚至公司——用的public接口,也就是所谓API。
Java的API文档,往往是通过javadoc生成的。这样的好处是,开发代码的同时开发文档,保持一致性。
不过通常不需要太多维护,因为公开的API是不能随意修改的,最大的改动往往是加一个@deprecated
。
所以,其实API文档分开单独写也不是不可以。
内部用的public
,代码实现都能被看见,文档也就可以免了。
实在是没有文档看不懂代码,不会问吗?
干“脏活儿”
有时候,需要表达的逻辑本身就很复杂。典型而又常见的就是多线程,往往难以理解和跟踪。
很多时候,某些语言,尤其是C语言,干的都是些“脏活儿”。为了保证性能,往往必须怎么高效怎么来。一些递归函数里,多定义一个变量,都能影响到空间复杂度。
干脏活儿,想不脏,不容易。
不过,如果真的比较复杂,还是单独的文档,或者UML图比较有效。
汇编就更不用提了。这个时代,你能写,你不能指望别人能看。
TODO和FIXME
这俩,说是注释,其实应该算是标记。
TODO: 表示还有未完成的工作,往往是开发过程中使用。
FIXME: 表示这里有bug,或者可能有bug,但暂时无法解决。这往往是发布时用。
不过,程序员的一大恨事就是:在别人release过来的代码里,发现这哥俩!
临时去掉一些代码
调试时常用注释来disable一些代码,以免删除了还得重写或粘贴。
但是,调试完成后,不要的代码一定要删掉!
结论
- 赶紧删注释去!
- 不要主动写API以外的注释!
- 代码要写得好看一点。
- 以后的工作中,谁叫写注释就有理有据地喷回去!
- 你因看了本文而产生的任何实际变化,如果在工作和学习中带来了任何负面结果,请自行负责。