邪恶的注释(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

这种情况之前讨论过,完全没必要。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,566评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,284评论 25 707
  • 前晚去水果店买水果,我们在那个店里办了会员卡,所以一般结算的时候只需要报手机号与密码,然后会给你一张结算小票。当时...
    七夕FFF阅读 240评论 0 4
  • 2016年有一段时间在武汉。 当时经常和合作单位开会,对方派来的是总监和他的助理。 在第三次碰头的时候,助理没有出...
    曹将阅读 711评论 1 6
  • 文/陌路ZL 2016-11-24 感谢我的父母 带我来到这个美丽世界...
    陌路zl阅读 242评论 0 1