邪恶的注释(clean code笔记之三)
注:正文中的引用是直接引用作者Bob大叔的话,两条横线中间的段落的是我自己的观点,其他大约都可以算是笔记了。
作者Bob大叔在这一节中就注释展开讨论,如果你之前曾经仔细阅读过一些开源软件的注释,你会发现很多本文中将要谈到的坏注释
的例子在很多著名的开源软件的源代码中都出现过。作者也清晰的表明是自己的立场,即是「这些注释都是邪恶的
」。没错,一种代码写作风格出现在某些著名的开源软件中,并不代表这些风格就是好的。
Don't comment bad code — rewrite it.
---Brian W. Kernighan and P. J. Plaugher
适当的注释对于理解代码的意图很有帮助,但是不好的代码却会使得代码变得十分杂乱不堪,甚至会对代码造成伤害。而通常的情况是,大部分的代码注释都十分糟糕。
「Clean Code」的作者(Bob大叔)对注释整体上是持反对意见的,他认为写代码就像是写文章(这里的文章通常指的是记叙文),好的代码应该是能清晰地自解释的,所以在好的代码里不应该出现注释。来看这段阐述:
所以当你发现自己在某个项目的一个地方不得不使用注释时,停下来好好想想,看是否还有另一种方法去改变当前的状况,进而使用代码(而不是注释)来解释你要表达的意思。
注释并不能弥补代码的烂
往往如果我们写了一个模块,之后发现它结构混乱,代码本身令人费解,然后我们对自己说「哦,那我加条注释就好了」。这是不对的,与其把时间花在写注释上,还不如花点时间把代码修改得更好一点。
好注释的例子
这里列举了一些「好的注释」的例子
1. 版权信息
有时必须要把版权信息以注释的方式写进代码里去。
2. 提供信息的注释
比如代码3-1中有个很复杂的正则表达式,这时我们需要添加适当的注释告诉读者这个表达式匹配的具体格式有哪里。
//代码3-1
// format matched kk:mm:ss EEE, MMM dd, yyyy Pattern
Pattern timeMatcher = Pattern.compile(
"\\\\d*:\\\\d*:\\\\d* \\\\w*, \\\\w* \\\\d*, \\\\d*");
3. 解释意图
有时注释要表达的不仅仅是代码实现(implementation)的信息,还有作者使用这种实现方法(implementation)的意图(intent)。
4. 说明
把一些「本身意图的表达」不是很清晰的代码的「真实意图」表达出来
这条主要是在低级语言中比较适用,在如Java这样的高级语言的使用中,大部分的情况都可以通过函数封装、修改命名或其他方式来消除这种注释的场景的。
5. 对于严重后果的警告
比如某个函数的功能是删除某个重要的数据,且不能恢复,这种情况添加对于此函数的后果的警告注释是十分必要的。
6. TODO注释
有时候需要添加TODO注释来标识对于将来开发工作的规划,或者解释为什么「在这里暂时使用了一个不好的实现方法」,或者表达对于某段代码将来的实现的期许,如代码3-2中所示。
//代码3-2
//TODO-MdM these are not needed
// We expect this to go away when we do the checkout model
protected VersionInfo makeVersion() throws Exception {
return null;
}
7. 使某段代码更醒目
8. 公共API的javadoc
坏代码
大多数的注释可以被归入这一类,它们通常都只是坏代码的遮羞布。
1. 自言自语
这类注释通常只有代码的作者自己能真正的理解其含义,对于其他人来说则是一筹莫展。
所以如果你要在某处写注释,一定要表达清楚此注释的上下文,不然这条注释对于代码阅读者来说就是没有意义的。
作者在这里又举了一个FitNesse
的例子(代码3-3),这个例子中的注释其实是对于阅读代码有帮助的,但是表述的还是不够清晰,会造成读者的误解,搞不清楚。
//代码3-3
public void loadProperties() {
try {
String propertiesPath = propertiesLocation + "/" + PROPERTIES_FILE;
FileInputStream propertiesStream = new FileInputStream(propertiesPath);
loadedProperties.load(propertiesStream);
} catch (IOException e) {
// No properties files means all defaults are loaded
//
}
}
这里的注释并没有表达清楚默认值是在哪里被加载进来的,如果别人或者作者自己在一段时间后要来修改默认值的配置,但他只看到这段注释,还是要再进一步去代码里找相应的实现。
2. 冗余注释
在有的代码中,代码本身其实已经写的很好了,但是开发者还是会在其中添加大量的注释,去解释用途或功能,在作者看来,这种注释是冗余的,同时它们又没有代码本身能表达的准确度,无端地增加了代码的阅读难度。
//代码3-4
public abstract class ContainerBase implements Container, Lifecycle, Pipeline, MBeanRegistration, Serializable {
/**
* The processor delay for this component.
*/
protected int backgroundProcessorDelay = -1;
/**
* The lifecycle event support for this component.
*/
protected LifecycleSupport lifecycle = new LifecycleSupport(this);
作者在这里拿了Tomcat源码(代码3-4)来举例,可能很多人都看过这样的代码,如果我们从Bob大叔的这一整套的编程哲学出发,这种注释确实是冗余的。
Bob大叔的这条规则其实有些代码洁癖的感觉。在某种程度上,他说的都是正确的,但是在一般的编程场景中,又很难完全避免对于代码添加冗余的代码,因为我们总是不能保证team中每个人还有将来可能维护或使用这段代码的人都具有这种良好的阅读代码的习惯。
3. 令人误解的注释
有些注释会表达的意思和函数的本意是不同的,就会导致代码的阅读者对于代码产生误解。
4. 强制性注释
作者认为强制的
对于每个函数
都添加入代码3-5中所示的javadoc
是一件愚蠢的事情,
代码3-5
/**
*
* @param title The title of the CD
* @param author The author of the CD
* @param tracks The number of tracks on the CD
* @param durationInMinutes The duration of the CD in minutes
*/
public void addCD(String title, String author,int tracks, int durationInMinutes) {
CD cd = new CD();
cd.title = title;
cd.author = author;
cd.tracks = tracks;
cd.duration = duration;
cdList.add(cd);
}
�
5. 日志性质的注释
有些人会有这种习惯:每次去编辑某个模块之后,都会在模块的最开始加入一段类似日志性质的注释,在版本控制如此普及的今天,这种注释是完全没有必要的。
6. 噪音注释
这种注释会让我们读起代码来十分烦躁,你去仔细地阅读它们吧,它们表达的意思在代码中都是显而易见的;你不去阅读它们吧,又怕会漏掉什么细节。所以这种注释在写代码时应该是尽力避免的。
7. 可怕的噪音
基本同上条
8. 如果可以使用一个函数或者变量,就不要使用注释。
有些注释是为了让读者能更好地理解代码要表达的意义,那么这种情况就应该使用合适的函数名和变量名来表示这种意思,而不是写一个晦涩难懂的函数,然后再写一堆注释去解释你的意图。
9. 标识位置的注释
作者认为这类的注释如果合适的使用,是能给代码带来好处的,但是很容易被滥用。
10. 大括号末尾的注释
有些人习惯在一个大括号的末尾(往往是函数或者if
while
等代码块的结尾)添加一些注释,来方便自己快速定位函数或代码块的结尾。但是这种情况,往往是你的函数或者代码块设计的不合理,应该通过重构写出更小、更精简的函数。
11. 署名注释
有些人喜欢在完成某一个功能时添加一些注释来表示「谁在什么时候为什么修改了某些东西」,这种事情还是交给版本控制系统来完成吧。
12. 对于代码的注释
//代码3-6
InputStreamResponse response = new InputStreamResponse();
response.setBody(formatter.getResultStream(), formatter.getByteCount());
// InputStream resultsStream = formatter.getResultStream();
// StreamReader reader = new StreamReader(resultsStream);
// response.setContent(reader.read(formatter.getByteCount()));
在有版本控制软件的今天,这种对于代码的注释是完全没有必要的,并且对于阅读代码带来了很大的干扰。
13. HTML注释
在注释中使用HTML标签是令人厌恶的,应该完全禁止的行为。
14. 非本地的信息
要保证我们的注释只对于就近代码的解释。
15. 表达信息量太大的注释
有些注释会写的很长很长,把一些历史讨论和不相干的细节都描述下来,这种注释是非常不利于对于代码的阅读的。
16. 表述不清晰的注释
有些注释本身就很难理解,更加不适合拿来当注释。
17. 函数头
短函数不需要写任何的描述信息。如果你写的所有函数都很短,并且他们的名字起的都非常良好(像第一个笔记中讲的那样),那么完全不需要在这种函数的头部添加注释。
18. 非公共API的代码中加入javadoc
这种情况之前讨论过,完全没必要。