<audio id="audio" title="07 | 写好注释，真的是小菜一碟吗？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/42/23/42889ad5d5bfbdf640c65b6497528b23.mp3"></audio>

上一讲中我们讲了如何整理代码，但有些时候，即便我们取好了名字，编排好格式，但代码还是让我们抓狂，不明出处，不好理解。这时候，就需要注释登场了。

顾名思义，注释就是对代码的解释。注释不需要运行，它是用来提高代码的可读性和可维护性的。不好的注释会使代码变得更糟糕，使人更抓狂。

理想虽丰满，现实很骨感。注释虽小，写好不易。那写注释有哪些注意事项？有没有什么技巧呢？今天我们就来聊聊写注释这个话题。

当然了，不同的语言，注释的语法差别很大。为方便起见，我们统一使用Java语言的注释语法，来解释说明写好注释的基础原则。

## 注释是无奈的妥协

那你是不是有这样一个问题，源代码一定需要解释吗？

其实在理想状况下，代码不需要注释。理想的代码，命名恰当，结构清晰，逻辑顺畅，含义显而易见。但正如一个作家无法预料他的读者能否清晰地理解他的文字一样，一个程序员也不能判断他的读者能否清晰地理解他写的代码。所以，写注释其实是下巧功夫。

可是，注释也是一个麻烦鬼，可能会给我们带来三个麻烦。

首先，**因为注释不需要运行，所以没有常规的办法来测试它**。 注释对不对？有没有随着代码变更？这些问题都是写注释需要注意的地方。**注释难以维护，这是使用注释带来的最大的麻烦**。

另一个麻烦是，**注释为我们提供了一个借口**。使用注释来解释代码，是注释的本意。但是，我们有时候会过度依赖解释，从而放弃了潜在的替代方案，比如更准确的命名，更清晰的结构，更顺畅的逻辑等等。 **注释，被我们用成万能的狗皮膏药，有时会让代码更糟糕**。

比如，下面的代码和注释，看起来没毛病，但读起来很吃力。

```
String name1;  // first name
String name2;  // last name

```

如果使用准确、有意义的命名，我们就可以去掉没有意义的注释了。

```
String firstName;
String lastName;

```

还有一个麻烦，就是**注释的滥用**。 由于注释部分不被执行，那么就可以被用来注释掉一些不需要的东西。比如，在正式的产品代码中，注释掉调试信息、代码块、俏皮话等等。

比如说，看到下面的注释，你是不是立即就转移注意力了？ 我理解这个注释的初衷是幽默一下，但是众口难调，这样的注释有些人感觉到的不是幽默，而是散漫和业余。

```
// 哈哈，有没有人姓好，叫“好名字”？
String firstName;
String lastName;

```

讲了这么多，总结一下，注释是代码的一部分，是需要阅读的内容，目的是让其他人能更好地理解我们的代码，写注释需要我们有“用户思维”。虽然也有过度依赖注释的情况，但是，对于大部分程序员来说，问题还是注释太少，而不是太多。

## 几种常见注释类型

接下来，我们就聊聊几种常见的注释类型。一个典型的源代码文件，一般包含不同类型的注释。不同类型的注释，有着不相同的要求，适用于不同的注释风格和原则。

**第一种类型，是记录源代码版权和授权的**，一般放在每一个源文件的开头，说明源代码的版权所有者，以及授权使用的许可方式，或者其他的公共信息。比如，如果是个人的代码，版权信息可以写成：

```
/*
 * Copyright (c) 2018, FirstName LastName. All rights reserved.
 */

```

一般来说，版权和授权信息是固定的。版权和授权信息是法律条款，除了年份，一个字都不能更改。对于每个源代码文件，我们记得复制粘贴在文件开头就行。**需要注意的是，如果文件有变更，记得更改版权信息的年份（比如上例中的2018）**。

**第二种类型，是用来生成用户文档的**，比如Java Doc。 这部分的作用，是用来生成独立的、不包含源代码的文档。 这些文档帮助使用者了解软件的功能和细节，主要面向的是该软件的**使用者**，而不是该软件的开发者。 比如Java的API规范的文档。

**第三种类型，是用来解释源代码的**。换句话说，就是帮助代码的阅读者理解代码。这是大家默认的注释类型，也是我们今天讨论的重点。

## 简化注释的风格

上面我们介绍了三种常见的注释类型，下面就针对这三种注释类型，再给你介绍**三种风格**的注释。

**针对第一种注释类型，也就是固定的版权和授权信息，使用一般的星号注释符（/**-**/）**。注释块的首行和尾行只使用星号注释符，中间行以缩进一个空格的星号开始，文字和星号之间使用一个空格。注释的每行长度限制，和代码块的每行长度限制保持一致。

比如：

```
/*
 * Copyright (c) 2018, FirstName LastName. All rights reserved.
 */

```

**针对第二种注释类型，即生成用户文档的注释，使用Javadoc要求的格式，文档注释符（/**-*/）**。 除了首行使用特殊的文档注释符（/**），其他的格式和第一种风格保持一致。

比如：

```
/**
 * A {@code Readable} is a source of characters. Characters from
 * a {@code Readable} are made available to callers of the read
 * method via a {@link java.nio.CharBuffer CharBuffer}.
 *
 * @since 1.5
 */
public interface Readable {
   ...
}

```

**针对第三种注释类型，也就是代码解释注释，只使用行注释符（//）**。 每行长度限制，和代码块的每行长度限制保持一致。

比如：

```
// Verify that the buffer has sufficient remaining
private static void verifyLength(
        ByteBuffer buffer, int requiredLength) {
    ...
}

String myString;  // using end-to-line comment

// This is a multiple line comment.  This is a multiple
// line comment. 
if (!myString.isEmpty()) {
   ...
}

```

写代码注释时，我一般只用这三种风格。它们各自有固定的使用范围，简单直观，规避不必要的代码错误。也不会让别人混淆注释的类型。

我不会使用如下的注释，因为这种注释风格可能和有效的代码混淆在一起。 注释越长，错误越容易隐藏起来。

```
/*
 * This is a multiple line comment.  This is a multiple
 * line comment.
 
 if (programingLanguage.equals(&quot;Java&quot;)) {
    ...   
 } */

```

当然了，肯定有人会喜欢上述的风格，因为这种风格可以注释掉不用的代码。这一点，方便我们调试分段代码。我自己在调试的时候也喜欢使用这种注释方式，但是一旦调试结束，我就会清理掉这些注释。

从我自己的经验来看，养成这样的习惯很有帮助：**如果一段代码不再需要，我会清理掉代码，而不会保留这个注释掉的代码块。**不要在源代码里记录代码历史，那是代码版本管理系统该干的事情。

## 注释的三项原则

那么，用来解释源代码的注释有什么需要注意的地方吗？为了规避注释的种种麻烦，有没有什么原则我们必需要遵守呢？我总结了以下三点。

<li>
**准确**，错误的注释比没有注释更糟糕。
</li>
<li>
**必要**，多余的注释浪费阅读者的时间。
</li>
<li>
**清晰**，混乱的注释会把代码搞得更乱。
</li>

比如，当我们说编程语言时，一定不要省略“编程”这两个字。否则，就可能会被误解为大家日常说话用的语言。这就是准确性的要求。

<img src="https://static001.geekbang.org/resource/image/b3/64/b3ed2cdcf1fc5f1c381e6db887390664.png" alt="">

如果代码已经能够清晰、简单地表达自己的语义和逻辑，这时候重复代码语义的注释就是多余的注释。注释的维护是耗费时间和精力的，所以，不要保留多余的、不必要的注释。

<img src="https://static001.geekbang.org/resource/image/2a/74/2a407359a04137abad0bb2c4b45adc74.png" alt=""><br>
如果注释和代码不能从视觉上清晰地分割，注释就会破坏代码的可读性。

<img src="https://static001.geekbang.org/resource/image/44/38/445897c1f024f87b510e3ec3c84d9b38.png" alt=""><br>
另外，不要在代码里标注你想要做的工作和已经做过的工作。比如使用TODO，记录代码更改记录等等。这些信息会干扰代码的阅读者。

特别需要注意的是，我们可以使用临时的调试语句，但是，不要把代码的调试语句保留在提交的代码里。这些调试语句，既不容易维护，也不容易阅读。

<img src="https://static001.geekbang.org/resource/image/4b/06/4b9a766c98b2441f04711b7062cffa06.png" alt="">

## 注释用英文还是汉字呢？

你会注意到，上面的代码案例中，我基本使用的是英文注释，在这里我也建议你使用英文注释。

为什么呢？

因为使用中文注释，不是一个所有人都能接受的风格。一部分人，一部分公司，并不接受中文注释。特别是国际化的项目，比如说贡献给Apache的项目，就没有办法使用中文注释了。而且，如果是去面试，我也会尽最大的努力不使用中文注释，以免踩到坑。

除了接受度之外，汉字带来的真正困扰是，它会影响到编码风格的偏好。比如命名的问题，到底是应该使用拼音还是英文？ 由于代码命名只能使用ASCII字符，注释里的拼音、英文、汉字混杂的问题该怎么处理？代码编辑时，字符的切换也是一个麻烦事。比如，空格使用汉字全角，编译器会报错，但是肉眼看不到，问题排查起来也很心累。

那么什么情况下使用汉字呢？

面对国内的需求文档的时候。因为很多项目的需求文档一般是汉字书写的。程序的编码，当然需要按照需求来。如果需求的引用还要翻译成英文，那就太麻烦了。

还有一种状况，就是团队的英文水平不太好。与其使用难以读懂的蹩脚英文，不如使用大家更熟悉的中文注释来的便捷。不过，我对这种状况的担心越来越少，现在大部分年轻软件工程师的英语水平是可以让人放心的。

试着对比下面的几种注释，你喜欢哪一种呢？

<img src="https://static001.geekbang.org/resource/image/31/d2/31f138eb37d91b78c4e44c03e0b4c2d2.jpg" alt="">

上面的五种不同的风格，我个人比较喜欢第一种和第二种，第三种也可以接受。 但是我会尽量避免第四种和第五种风格。

总结一下，今天我们讨论了怎么写好注释这个话题，希望你能理解一个基本的原则：注释是用来提高代码的可读性和可维护性的。 不要让注释偏离了这个原则，破坏了代码的逻辑和可读性。你也可以实践一下我们讨论的“三项原则”和“三种风格”，看看能不能让你的代码变得更友好？

## 一起来动手

还记得我们上一节的练习题吗？前面，我们改了名字，改了编排。这一次，我们来修改注释。认真读一下这段代码，看看有需要增加或者修改注释的地方吗？欢迎你把优化的代码公布在讨论区，我们一起来感受修改后的代码是不是更好阅读，更好维护。

```
import java.util.HashMap;
import java.util.Map;

class Solution {
    /**
     * Given an array of integers, return indices of the two numbers
     * such that they add up to a specific target.
     */
    public int[] twoSum(int[] nums, int target) {
        Map&lt;Integer, Integer&gt; map = new HashMap&lt;&gt;();
        for (int i = 0; i &lt; nums.length; i++) {
            int complement = target - nums[i];
            if (map.containsKey(complement)) {
                return new int[] { map.get(complement), i };
            }
            map.put(nums[i], i);
        }
        throw new IllegalArgumentException(&quot;No two sum solution&quot;);
    }
}

```

欢迎点击“请朋友读”，把这篇文章分享给你的朋友或者同事，一起来探讨吧！
