gitbook/代码精进之路/docs/78086.md
2022-09-03 22:05:03 +08:00

12 KiB
Raw Permalink Blame History

07 | 写好注释,真的是小菜一碟吗?

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

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

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

当然了不同的语言注释的语法差别很大。为方便起见我们统一使用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("Java")) {
    ...   
 } */

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

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

注释的三项原则

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

  1. 准确,错误的注释比没有注释更糟糕。

  2. 必要,多余的注释浪费阅读者的时间。

  3. 清晰,混乱的注释会把代码搞得更乱。

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

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


如果注释和代码不能从视觉上清晰地分割,注释就会破坏代码的可读性。


另外不要在代码里标注你想要做的工作和已经做过的工作。比如使用TODO记录代码更改记录等等。这些信息会干扰代码的阅读者。

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

注释用英文还是汉字呢?

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

为什么呢?

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

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

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

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

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

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

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

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

一起来动手

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

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<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < 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("No two sum solution");
    }
}

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