《代码整洁之道》读书笔记(三)之写好注释很重要

别给糟糕的代码添加注释——重新写吧。

若编程语言足够有表达力,或者我们长于用这些语言来表达意图,就不那么需要注释——也许根本不需要。

注释的恰当用法是弥补我们在用代码表达意图时遭遇的失败。如果你发现自己需要写注释,再想想看是否有办法通过代码来表达。

本文不提倡写太多注释,能用代码表达就不要写注释。

1. 注释不能美化糟糕的代码

写注释的常见动机之一是糟糕的代码的存在。

带有少量注释的整洁而有表达力的代码,比带有大量注释的零碎而复杂的代码像样的多。与其花时间编写解释你搞出的糟糕的代码的注释,不如花时间清洁那堆糟糕的代码。

2. 用代码来阐述

有时候,代码本身不足以解释其行为。幸运的是,很多时候我们都可以把代码写地能解释自己的行为。


// Check to see if the employee is eligible for full benefits
if ((employee.flags & HOURLY_DAY) && 
    (employee.age > 65))

改成这样怎么样?

if (employee.isEligibleForFullBenefits())

3. 好注释

有些注释是必须的,也是有利的。不过要记住,唯一真正的好注释是你想办法不去写的注释。

3.1 法律信息

版本及著作权声明就是有理由在每个源文件开头注释处放置的内容

3.2 提供信息的注释

// format matched kk:mm:ss EEE, MMM dd, yyyy
Pattern timeMatcher = Pattern.compile("\\d*:\\d*:\\d* \\w*, \\w* \\d*, \\d*");

注释说明,该正则表达式意在匹配一个经由SimpleDateFormat.format函数利用特定格式字符串格式化的时间和日期。

这类注释有时管用,但更好的方式是尽量利用函数名称表达信息。

3.3 对意图的解释

有时,注释不仅提供了有关实现的有用信息,而且还提供了某个决定后面的意图。

下面的例子,你也许不同意程序员给这个问题提供的解决办法,但至少你知道他想干什么。

public void testConcurrentAddWidgets() throws Exception {
    WidgetBuilder widgetBuilder = 
        new WidgetBuilder(new Class[] {BoldWidget.class});
    String text = "'''bold text'";
    ParentWidget parent = 
        new BoldWidget(new MockWidgetRoot(), text);
    
    AtomicBoolean failFlag = new AtomicBoolean();
    failFlag.set(false);
    
    // This is our best attempt to get a race condition 
    // by creating large number of threads
    for (itn  i = 0; i < 25000; i++) {
        WidgetBuilderThread widgetBuilderThread = 
            new WidgetBuilderThread(widgetBuilder, text, parent, failFlag);
        new Thread(widgetBuilderThread).start();
    }

    assertEquals(false, failFlag.get());
}

3.4 阐释

有时候参数或者返回值晦涩难懂的时候,用注释来帮助阐释其含义就会有用。但前提是,你考虑一下是否有更好的办法,在小心的加上注释。

3.5 警示

有时,用于警示其他程序员会出现某种后果的注释也是有用的。

public static SimpleDateFormat makeStandardHttpDateFormat() {
    // SimpleDateFormat is not thread safe
    // so we need to create each instance independently.
    SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
    df.setTimeZone(TimeZone.getTimeZone("GMT"));
    return df;
}

上面例子也许有更好的解决办法,不过这个注释却是非常有必要的。它能阻止某个急切的程序员以效率之名使用静态初始器。

3.6 TODO注释

TODO是一种程序员认为应该做,但由于某些原因目前还没做的工作。它可能是要提醒删除某个不必要的特性,或者要求他人注意某个问题,它可能是恳请别人取个好名字等等。

如今大多数IDE都提供了定位TODO注释,定期查看这些注释,删除不再需要的,让代码整洁。

3.7 放大

注释可以用来放大某种看起来不合理之物的重要性。

String listItemContent = match.group(3).trim();
// the trim is real important. It removes the starting
// spaces that could cause the item to be recognized 
// as another list
new ListItemWidget(this, listItemContent, this.level + 1);
return buildList(text.substring(match.end()));

3.8 公共API中的Javadoc

如果你在编写公共API,就该为它编写良好的Javadoc。

4. 坏注释

大多数注释都属此类。通常,坏注释都是糟糕代码的支撑或借口,或者对错误决策的修正,基本上等于程序员自说自话。

4.1 喃喃自语

写一些只有作者自己知道含义的注释,就是喃喃自语。

4.2 多余的注释

相比代码本身无法提供更过的信息就是多余的注释。比如没有证明代码的意义,也没有给出代码的意图或逻辑。读这种注释还不如读代码。

4.3 误导性注释

注释不够精确,和代码本身表达的含义不一致,容易误导程序员。

4.4 循规式注释

所谓每个函数都要有Javadoc或每个变量都要有注释的规矩全然是愚蠢可笑的。这类注释突然让代码变得散乱,满口胡言,令人迷惑不解。


/**
 * @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 = durationInMinutes;
    cdList.add(cd);
}

4.5 日志式注释

有人会在每次编辑代码时,在模块开始处添加一条注释。这类注释就像是一种记录每次修改的日志。

很久以前,在模块开始处创建并维护这些记录还算有道理。那时,我们还没有源代码控制系统可用。如今,这种冗长的记录只会让模块变得凌乱不堪,应当全部删除。

4.6 废话注释

对显然之事喋喋不休,毫无新意。

/** 
 * Default constructor
 */
protected AnnualDateRule() {
    
}

/** The day of the month. */
private int dayOfMonth;

/**
 * Returns the day of the month
 * @return the day of the month
 */
public int getDayOfMonth() {
    return dayOfMonth;
}

4.7 可怕的废话

Javadoc也可能是废话。下列Javadoc(摘自某知名开源库)的目的是什么? 答案:无。

/** The name. */
private String name;

/** The version */
private String version;

/** The licenseName */
private String licenseName;

/** The version. */
private String info;

再仔细读读这些注释。你是否发现了剪切-粘贴错误? 如果作者在写(粘贴)注释的时候都没花心思,怎么能指望读者从中受益呢?

4.8 能用函数或变量时就别用注释

看看以下代码概要:

// does the module from the global list <mod> depend on the 
// subsystem we are part of ?
if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem()))

可以改成以下没有注释的版本:

ArrayList moduleDependees = smodule.getDependSubsystems();
String ourSubSystem = subSysMod.getSubSystem();
if (moduleDependees.contains(ourSubSystem))

4.9 位置标记

有时,程序员喜欢在源代码中标记某个特别位置。例如:

// Actions //////////////////////////////////////////

把特定函数放在这种标记下面,多数时候实属无理。鸡零狗碎,理当删除——特别是尾部那一长串无用的斜杠。

这么说吧,如果标记栏不多,就会显而易见。尽量少用这中标记栏。如果滥用,代码就会沉没在背景噪音中,被忽略掉。

4.10 括号后的注释

有时,程序员会在括号后面放置特殊的注释,尽管这对于含有深度嵌套结构的长函数可能有意义,但只会给我们更愿意编写的短小、封装的函数带来混乱。如果你发现自己想标记右括号,其实应该做的是缩短函数。

4.11 归属于署名

/* Added by Rick */

版本控制系统非常善于记住是谁在何时添加了什么。没必要用小小的签名搞脏代码。

4.12 注释掉的代码

直接把代码注释掉是讨厌的做法。别这么干!

InputStreamResponse response = new InputStreamResponse();
resonpse.setBody(formatter.getResultStream(), formatter.getByteCount());
// InputStream resultsStream = formatter.getResultStream();
// StreamReader reader = new StreamReader(resultStream);
// response.setContent(reader.read(formatter.getByteCount()));

其他人不敢删除注释掉的代码。他们会想,代码依然放在那儿,一定有其原因,而且这段代码很重要,不能删除。注释掉的代码堆积在一起,就像破旧瓶底的渣滓一般。

曾经有一段时间,注释掉代码可能有用。但我们已经拥有优秀的源代码控制系统,这些系统可以为我们记住不要的代码。我们无需再用注释来标记,删掉即可,它们丢不了。我担保。

4.13 HTML注释

源码中包含HTML注释让人讨厌,难以阅读。

4.14 非本地信息

假如你一定要写注释,请确保它描述了离它最近的代码。别在本地注释的上下文环境中包含出系统级的信息。
下面的例子,除了可怕的冗余之外,还包含了系统级的默认端口信息。但是这个函数完全没控制那个所谓的默认值。假如那个值更改了,无法担保这个注释也会跟着修改。

/**
 * port on which fitnesse would run. Default to <b>8082</b>
 * @param fitnessePort
 */
public void setFitnessePort(int fitnessPort) {
    this.fitnessePort = fitnessePort;
}

4.15 信息过多

别在注释中添加有趣的历史性话题或者无关的细节描述。

4.16 不明显的联系

注释及其描述的代码之间的联系应该是显而易见。如果你不嫌麻烦要写注释,至少让读者能看着注释和代码,并且理解注释所谈何物。

以来自Apache公共库的这段注释为例

/**
 * start with an array that is big enough to hold all the pixels
 * (plus filter bytes), and an extra 200 bytes for header info
 */
this.pngBytes = new byte[((this.width + 1) * this.height * 3) + 200];

过滤字节是什么鬼? 与那个+1有关系吗?或与*3有关系? 还是与二者皆有关系? 注释的作用是解释未能自行解释的代码。如果注释本身还需要解释,就太遗憾了。

4.17 函数头

短函数不需要太多描述。为只做一件事的短函数取个好名字,通常要比写函数头注释要好。

4.18 非公共代码中的Javadoc

Javadoc对于公共API非常有用,但对于不打算做公共用途的代码就令人厌恶了。为系统中的类和函数生成Javadoc页并非总有用,而Javadoc注释额外的形式要求几乎等同于八股文章。

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

推荐阅读更多精彩内容