恰到好处的注释可能非常有帮助,但轻率的教条式注释可能会让模块显得更加混乱。过时的注释传播谎言和错误信息可能造成极大的损害。

如果我们的编程语言足够表达力,或者我们有技巧地运用这些语言来表达我们的意图,我们可能就不需要太多的注释——甚至根本不需要。

注释不能弥补糟糕的代码

清晰、富有表现力的代码,即使只有少量注释,也远远优于充斥着大量注释的混乱复杂的代码。与其花时间写注释来解释你制造的混乱,不如花时间清理这个混乱。

在代码中解释自己

  1. // 检查员工是否有资格享受全部福利
  2. if ((employee.flags & HOURLY_FLAG) && (employee.age > 65))

  1. if (employee.isEligibleForFullBenefits())

好的注释

有些注释是必要或有益的。然而,唯一真正好的注释是你找到方法不去写的注释。

法律注释

有时我们的公司编码标准迫使我们出于法律原因写某些注释。例如,版权和作者声明是每个源文件开头放入注释中的必要和合理的事情。

信息性注释

有时用注释提供基本信息是有用的。例如,考虑这个注释解释了一个抽象方法的返回值:

  1. // 返回正在测试的响应器的实例。
  2. protected abstract Responder responderInstance();

这样的注释有时可能有用,但如果可能的话,最好使用函数名称来传达信息。例如,在这种情况下,通过重命名函数可以使注释变得多余:responderBeingTested

意图解释

有时,注释不仅仅提供有关实现的有用信息,还提供了决策背后的意图。例如:

  1. public int compareTo(Object o)
  2. {
  3. if(o instanceof WikiPagePath)
  4. {
  5. WikiPagePath p = (WikiPagePath) o;
  6. String compressedName = StringUtil.join(names, "");
  7. String compressedArgumentName = StringUtil.join(p.names, "");
  8. return compressedName.compareTo(compressedArgumentName);
  9. }
  10. return 1; // 因为我们是正确的类型,所以我们更大。
  11. }

澄清

有时将一些晦涩难懂的参数或返回值的含义翻译成可读的东西是有帮助的。通常,最好找到一种方法使该参数或返回值本身清晰;但当它是标准库的一部分,或在你无法更改的代码中时,一个有助澄清的注释可能是有用的。

警告后果

有时警告其他程序员某些后果是有用的。

  1. // 除非你有一些时间可以消磨,否则不要运行。
  2. public void _testWithReallyBigFile() {
  3. writeLinesToFile(10000000);
  4. response.setBody(testFile);
  5. response.readyToSend(this);
  6. String responseString = output.toString();
  7. assertSubString("Content-Length: 1000000000", responseString);
  8. assertTrue(bytesSent > 1000000000);
  9. }

TODO 注释

//TODO注释的形式留下“待办”笔记是有时合理的。在以下情况下,TODO注释解释了为什么该函数有一个退化的实现以及该函数的未来应该是什么。

  1. //TODO-MdM 这些不需要
  2. // 我们期望在进行结账模型时这些会消失
  3. protected VersionInfo makeVersion() throws Exception {
  4. return null;
  5. }

TODO是程序员认为应该完成的工作,但由于某种原因目前无法完成。它可能是一个删除已弃用功能的提醒,或者是请求其他人查看问题的请求。它可能是请求其他人想出一个更好的名称,或者是提醒进行依赖于计划事件的更改。无论TODO是什么,它都不是在系统中保留糟糕代码的借口。

放大

有时,注释可以用来放大某些事情的重要性,否则这些事情可能看起来无关紧要。

  1. String listItemContent = match.group(3).trim();
  2. // 这里的trim非常重要。它移除了可能导致项被识别为
  3. // 另一个列表的起始空格。
  4. new ListItemWidget(this, listItemContent, this.level + 1);
  5. return buildList(text.substring(match.end()));

公共 API 中的 Javadoc

没有什么比一个描述得很好的公共 API 更有帮助和令人满意的了。标准 Java 库的 javadoc 就是一个很好的例子。如果没有它们,编写 Java 程序将非常困难。

坏注释

大多数注释都属于这一类。通常它们是糟糕代码的拐杖或借口,或者是对不充分决策的辩解,只不过是程序员自言自语。

喃喃自语

仅仅因为你觉得你“应该”或者因为流程要求,就随意添加注释,这是一种 hack。如果你决定写一个注释,那么花必要的时间确保它是你能做的最好的注释。例如:

  1. public void loadProperties() {
  2. try {
  3. String propertiesPath = propertiesLocation + "/" + PROPERTIES_FILE;
  4. FileInputStream propertiesStream = new FileInputStream(propertiesPath);
  5. loadedProperties.load(propertiesStream);
  6. }
  7. catch(IOException e) {
  8. // 没有属性文件意味着加载所有默认值
  9. }
  10. }

catch 块中的注释是什么意思?对作者来说显然意味着一些东西,但意义并没有很好地传达。显然,如果我们得到一个IOException,就意味着没有属性文件;在这种情况下,所有默认值都被加载了。但是谁加载了所有默认值?

多余的注释

  1. // 这个实用方法在this.closed为true时返回。如果达到超时,则抛出异常
  2. public synchronized void waitForClose(final long timeoutMillis) throws Exception {
  3. if(!closed) {
  4. wait(timeoutMillis);
  5. if(!closed)
  6. throw new Exception("MockResponseSender could not be closed");
  7. }
  8. }

这个注释有什么目的?它当然没有比代码更有信息量。它没有证明代码的正确性,或提供意图或理由。它没有比代码更容易阅读。实际上,它比代码更不精确,并诱使读者接受这种不精确性,而不是真正的理解。

误导性注释

有时,出于所有良好意图,程序员在他的注释中做出的陈述不够精确,无法准确。再考虑上一节的例子。该方法并不是在 this.closed 变为 true 时返回。如果 this.closedtrue,它就返回;否则,它等待一个盲目的超时,然后如果 this.closed 仍然不是 true,就抛出一个异常。

强制注释

有一个规则说每个函数都必须有一个 javadoc,或者每个变量都必须有一个注释,这简直是愚蠢的。像这样的注释只会弄乱代码,传播谎言,并导致一般混乱和无组织。

  1. /**
  2. *
  3. * @param title CD的标题
  4. * @param author CD的作者
  5. * @param tracks CD上的曲目数量
  6. * @param durationInMinutes CD的持续时间(分钟)
  7. */
  8. public void addCD(String title, String author, int tracks, int durationInMinutes) {
  9. CD cd = new CD();
  10. cd.title = title;
  11. cd.author = author;
  12. cd.tracks = tracks;
  13. cd.duration = duration;
  14. cdList.add(cd);
  15. }

日志注释

有时人们会在每次编辑模块时在模块开头添加注释。例如:

  1. * 变更(从20011011日)
  2. * --------------------------
  3. * 20011011日:重新组织类并将其移动到新包com.jrefinery.dateDG);
  4. * 2001115日:添加了getDescription()方法,并淘汰了NotableDate类(DG);
  5. * 20011112日:由于NotableDate类已经消失,IBD需要setDescription()方法(DG);更改getPreviousDayOfWeek(),getFollowingDayOfWeek()和getNearestDayOfWeek()以纠正错误(DG);
  6. * 2001125日:修复了SpreadsheetDate类中的错误(DG);

今天我们有源代码控制系统,我们不需要这种日志。

噪音注释

以下示例中的注释没有提供新信息。

  1. /**
  2. * 默认构造函数。
  3. */
  4. protected AnnualDateRule() {
  5. }
  1. /** 月份中的一天。 */
  2. private int dayOfMonth;

Javadoc注释可能属于这一类。很多时候,它们只是出于某种错误的愿望提供文档而写成的多余的嘈杂注释。

当你可以使用函数或变量时,不要使用注释

例如:

  1. // 来自全局列表<mod>的模块是否依赖于我们所属的子系统?
  2. if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem()))

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

位置标记

这类注释是噪音

  1. // 行为 //////////////////////////////////

闭括号注释

例如:

  1. public class wc {
  2. public static void main(String[] args) {
  3. BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
  4. String line;
  5. int lineCount = 0;
  6. int charCount = 0;
  7. int wordCount = 0;
  8. try {
  9. while ((line = in.readLine()) != null) {
  10. lineCount++;
  11. charCount += line.length();
  12. String words[] = line.split("\\W");
  13. wordCount += words.length;
  14. } //while
  15. System.out.println("wordCount = " + wordCount);
  16. System.out.println("lineCount = " + lineCount);
  17. System.out.println("charCount = " + charCount);
  18. } // try
  19. catch (IOException e) {
  20. System.err.println("Error:" + e.getMessage());
  21. } //catch
  22. } //main

你可以将代码拆分成小函数,而不是使用这种注释。

归属和作者

例如:

/* Added by Rick */

版本控制系统可以管理这些信息。

被注释掉的代码

  1. InputStreamResponse response = new InputStreamResponse();
  2. response.setBody(formatter.getResultStream(), formatter.getByteCount());
  3. // InputStream resultsStream = formatter.getResultStream();
  4. // StreamReader reader = new StreamReader(resultsStream);
  5. // response.setContent(reader.read(formatter.getByteCount()));

如果你不再需要它,请删除它,如果需要,你可以稍后使用你的版本控制系统找回它。

HTML注释

源代码注释中的HTML是一种令人厌恶的做法,你可以通过阅读下面的代码来了解。

  1. /**
  2. * 运行fit测试的任务。
  3. * 此任务运行fitnesse测试并发布结果。
  4. * <p/>
  5. * <pre>
  6. * 使用:
  7. * &lt;taskdef name=&quot;execute-fitnesse-tests&quot;
  8. * classname=&quot;fitnesse.ant.ExecuteFitnesseTestsTask&quot;
  9. * classpathref=&quot;classpath&quot; /&gt;
  10. * OR
  11. * &lt;taskdef classpathref=&quot;classpath&quot;
  12. * resource=&quot;tasks.properties&quot; /&gt;
  13. * <p/>
  14. * &lt;execute-fitnesse-tests
  15. * suitepage=&quot;FitNesse.SuiteAcceptanceTests&quot;
  16. * fitnesseport=&quot;8082&quot;
  17. * resultsdir=&quot;${results.dir}&quot;
  18. * resultshtmlpage=&quot;fit-results.html&quot;
  19. * classpathref=&quot;classpath&quot; /&gt;
  20. * </pre>
  21. */

非本地信息

如果你必须写注释,那么确保它描述它附近的代码。不要在局部注释的上下文中提供系统范围的信息。

太多信息

不要将有趣的历史讨论或不相关的细节描述放入你的注释中。

不明显的联系

注释和它描述的代码之间的联系应该是显而易见的。如果你要费心写注释,那么你至少希望读者能够看到注释和代码,并理解注释在谈论什么。

函数头

短函数不需要太多描述。一个适当选择的做一件事的小函数名称通常比注释头要好。

非公共代码中的Javadoc

Javadoc是为公共API准备的,在非公共代码中可能是干扰而不是帮助。