整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

如何写出优秀的代码注释?

释是软件开发过程中的性价比极高的一个工具,它只需要花费20%的时间,即可获取80%的价值。代码注释最重要的作用就是让读者可以在不读源码的情况下,快速了解一段代码的主要功能。

前言

相信大家都会遇到这种情况:一周前自己写的代码,现在再拿出来看,发现读不懂了,“ 这代码是我写的???”。这时候,代码注释就可以发挥它的作用了——提高晦涩难懂的代码的可读性;注释可以起到隐藏代码复杂细节的作用,比如接口注释可以帮助开发者在没有阅读代码的情况下快速了解该接口的功能和用法;如果写的好,注释还可以改善系统的设计

既然注释这么多好处,为什么我们程序员还是不愿意写注释?

“代码都写不完了,哪有时间写注释,以后再补吧“

时间不够论。这是最常见的原因,在交付速度飞快的今天,“代码写不完”是一个再常见不过的情况了,但写注释真的会导致需求延迟吗?绝不!相对于写一个接口的实现,写接口注释的时间可能只需要花费前者的5%。但不写注释,后面使用接口的人必须要多花费**50%**的时间去读懂代码!而且对于大部分程序员而言,“以后再补“大概要到2910年才能落实。

“好的代码就是最好的注释,我的代码可读性很好,没必要写注释”

好代码胜过注释论。不少程序员认为,好的代码就是最好的注释,只要代码可读性好,注释就可以省去。然而一个软件系统很多信息是无法通过代码呈现出来的,比如系统的设计思路、函数执行的预置条件等等。此外,代码可读性也不是绝对的,对于一个没有使用过Java 8 的 Lambda表达式的开发者而言,通篇的箭头“->“简直就是一场噩梦。

”过期的注释容易误导人“

过期注释论。不可否认,过期的注释很容易误导读者,但是这并不能成为否认注释的借口。除非是重大的重构或重写,对注释进行大改动的情况很少出现。通常,在更改代码之后,只需花费极少的时间去更新注释,就可以避免过期注释这种情况了。

注释的分类

注释大致可以分成四类:接口注释、数据成员注释、实现注释和模块依赖注释。

接口注释

平时我们所说的接口,通常指的是一个类(包括interface、class、enum)和方法。对类而言,接口注释主要描述该类提供的功能;对方法而言,除了描述方法功能之外,方法的入参和返回值都要进行说明。当然,使用类/方法的一些预置条件和副作用等信息都需要在接口注释中提到。

/**
     * Returns the length of this string.
     * The length is equal to the number of <a href="Character.html#unicode">Unicode
     * code units</a> in the string.
     *
     * @return  the length of the sequence of characters represented by this
     *          object.
     */
    public int length() {
        return value.length;
    }

典型的接口注释(选自JDK 1.8中的String类)

数据成员注释

数据成员注释和接口注释在大多数情况下都是必须的,这对于让读者快速读懂代码有很大的帮助。数据成员包括类的普通成员变量和静态成员变量,数据成员注释除了描述数据成员的本身用途之外,成员的默认值、副作用等信息都需要提及。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

    /**
     * Class String is special cased within the Serialization Stream Protocol.
     *
     * A String instance is written into an ObjectOutputStream according to
     * <a href="{@docRoot}/../platform/serialization/spec/output.html">
     * Object Serialization Specification, Section 6.2, "Stream Elements"</a>
     */
    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];
    ....
}

典型的数据成员注释(选自JDK 1.8中的String类)

实现注释

对于一段代码,如果无法一眼就看出其含义而需要深入分析其实现才能读懂,就需要添加实现注释对这段代码的含义进行说明。代码的实现注释通常不是必须的,如果一段代码每一行都需要注释,那就要重新审视一下这段代码的设计是否有合理了。此外,实现注释描述还需要描述一些代码无法体现,但是对于读者了解这段代码很有帮助的信息,比如代码的设计思路等。

public final class String {
    ...
    public char[] toCharArray() {
        // Cannot use Arrays.copyOf because of class initialization order issues
        char result[] = new char[value.length];
        System.arraycopy(value, 0, result, 0, value.length);
        return result;
    }
    ...
}

典型的实现注释(选自JDK 1.8中的String类)

模块依赖注释

模块依赖注释相对少见一些,主要描述两个相互依赖的模块的一些依赖信息,因为它并不属于单独某个模块,所以在哪里写这个注释需要认真衡量一下。实在找不到好的位置时,可以写一个类似README的文件,专门用于描述模块之间的依赖关系。

typedef enum Status {
    STATUS_OK = 0,
    STATUS_UNKNOWN_TABLET                = 1,
    STATUS_WRONG_VERSION                 = 2,
    ...
    STATUS_INDEX_DOESNT_EXIST            = 29,
    STATUS_INVALID_PARAMETER             = 30,
    STATUS_MAX_VALUE                     = 30,
    // Note: if you add a new status value you must make the following
    // additional updates:
    // (1)  Modify STATUS_MAX_VALUE to have a value equal to the
    //      largest defined status value, and make sure its definition
    //      is the last one in the list. STATUS_MAX_VALUE is used
    //      primarily for testing.
    // (2)  Add new entries in the tables "messages" and "symbols" in
    //      Status.cc.
    // (3)  Add a new exception class to ClientException.h
    // (4)  Add a new "case" to ClientException::throwException to map
    //      from the status value to a status-specific ClientException
    //      subclass.
    // (5)  In the Java bindings, add a static class for the exception
    //      to ClientException.java
    // (6)  Add a case for the status of the exception to throw the
    //      exception in ClientException.java
    // (7)  Add the exception to the Status enum in Status.java, making
    //      sure the status is in the correct position corresponding to
    //      its status code.
}

典型的模块依赖注释(选自《A Philosophy of Software Design》中的例子)

如何写好代码注释

利用好注释模板

注释模板为注释写作提供了极大的便利,我们常用的开发工具如IDEA、VS Code都对注释模板有很好的支持。在配置好注释模板之后(一般情况下默认模板即可),只需简单的操作,就能生成模板,我们只需往模板里填上内容即可。对于方法的接口注释,注释模板尤为方便,方法的入参和返回值标识注释模板都已经提供好。

/**
* 
* @param user
* @param password
* @return
*/
private boolean login(String user, String password) {
    ...
}

IntelliJ IDEA默认的注释模板

不要重复代码

注释与代码重复是开发者最容易犯的一个错误,这也是很多开发者认为注释是冗余的原因。确实,对于那些通过读代码可以很容易就推断出来的信息,注释就是多余的,更甚者,出现过期注释还容易误导读者。

// Add a horizontal scroll bar
hScrollBar = new JScrollBar(JScrollBar.HORIZONTAL);
add(hScrollBar, BorderLayout.SOUTH);
// Add a vertical scroll bar
vScrollBar = new JScrollBar(JScrollBar.VERTICAL);
add(vScrollBar, BorderLayout.EAST);
// Initialize the caret-position related values
caretX     = 0;
caretY     = 0;
caretMemX  = null;

典型的重复代码的注释(选自《A Philosophy of Software Design》中的例子)

某些程序员喜欢在注释中使用代码中的变量名或其中的单词,这种情况也是注释重复代码的一种体现。像下面的这个例子,注释完全就是冗余的,可以猜测开发者纯粹是为了注释而注释。

/**
 * 用户登录
 * @param user User对象
 * @param password Password对象
 * @return
 */
private boolean login(User user, Password password) {
    ...
}

使用代码变量名的注释

注释也要分层

在进行系统设计时,我们常常会采用分层架构,每一层负责不同功能。系统的顶层往往会更抽象一点,为功能调用者隐藏了很多细节(high-level);底层往往会更细节一点,实现系统的具体功能(low-level)。在进行注释写作时,我们也要学会对注释进行分层。high-level的注释要提供比代码更抽象的信息,比如代码的设计思路;low-level的注释要提供比代码更细节的信息,比如表示一个范围的两个参数是左闭右开还是左闭右闭;我们需要避免写出与代码同一level的注释,因为这往往就是上一节所提到的注释重复代码

low-level注释写作指南

对于需要通过阅读这一段代码才能获取的信息,就适合以low-level注释写在这段代码前面,一些常见的例子有:

  • 变量的单位是什么?
  • 表示范围的一组参数是左闭右开还是左闭右闭?
  • 某个变量为null时代表什么含义?
  • 某个资源应该由谁来负责释放,接口调用者还是接口提供者?

low-level注释不能太过抽象,否则就失去了其应有的作用:

// Current offset in resp Buffer
uint32_t offset;
// Contains all line-widths inside the document and
// number of appearances.
private TreeMap<Integer, Integer> lineWidths;

太过抽象的low-level注释(选自《A Philosophy of Software Design》中的例子)

好的low-level注释应当详细介绍代码的意图及其需要注意的点:

//  Position in this buffer of the first object that hasn't
//  been returned to the client.
uint32_t offset;
//  Holds statistics about line lengths of the form <length, count>
//  where length is the number of characters in a line (including
//  the newline), and count is the number of lines with
//  exactly that many characters. If there are no lines with
//  a particular length, then there is no entry for that length.
private TreeMap<Integer, Integer> numLinesWithLength;

典型的low-level注释(选自《A Philosophy of Software Design》中的例子)

high-level注释写作指南

high-level注释的作用是隐藏具体实现细节,快速让读者对整一段代码有一个全局的了解。high-level注释除了描述对代码进行抽象的概括(What)之外,还经常描述代码的设计思路(Why),这有助于帮助读者更容易、深入地了解整个系统。high-level注释不应该描述具体代码实现的信息,更不能犯注释重复代码的错误。在写high-level注释之前,先问一下自己:

  • 这段代码要做什么事情?
  • 这段代码最核心的功能是什么?
  • 怎样才能以最简单的描述表达这段代码的核心功能?

太过细节的high-level注释其实就是在重复代码,另一方面该注释既不能解释此代码的总体目的,也不能解释其如何适合包含此代码的方法,导致其失去了其应有作用:

// If there is a LOADING readRpc using the same session
// as PKHash pointed to by assignPos, and the last PKHash
// in that readRPC is smaller than current assigning
// PKHash, then we put assigning PKHash into that readRPC.
int readActiveRpcId = RPC_ID_NOT_ASSIGNED;
for (int i = 0; i < NUM_READ_RPC; i++) {
    if (session == readRpc[i].session
            && readRpc[i].status == LOADING
            && readRpc[i].maxPos < assignPos
            && readRpc[i].numHashes < MAX_PKHASHES_PERRPC) {
        readActiveRpcId = i;
        break;
    }
}

太过细节的high-level注释(选自《A Philosophy of Software Design》中的例子)

更好的high-level注释应该在更高级别上描述代码的整体功能:

// Try to append the current key hash onto an existing
// RPC to the desired server that hasn't been sent yet.
int readActiveRpcId = RPC_ID_NOT_ASSIGNED;
for (int i = 0; i < NUM_READ_RPC; i++) {
    if (session == readRpc[i].session
            && readRpc[i].status == LOADING
            && readRpc[i].maxPos < assignPos
            && readRpc[i].numHashes < MAX_PKHASHES_PERRPC) {
        readActiveRpcId = i;
        break;
    }
}

典型的high-level注释(选自《A Philosophy of Software Design》中的例子)

规范接口注释

接口注释是用的最多的一种注释,它可以让读者快速了解整个模块的功能并知道如何使用该接口,而不必花费大量时间去阅读源码。

对于一个的接口注释,其通常是high-level的注释,主要描述这个类提供的一些功能;

对于一个方法的接口注释,则同时需要包括high-level和low-level的注释,常见的有以下几点:

  • 整个方法提供的主要功能
  • 方法的所有参数和返回值含义
  • 调用该方法的副作用(比如改变了系统的某个状态)
  • 该方法可能抛出的所有异常
  • 调用该方法的预置条件(比如调用该方法前,系统必须处于某个状态)

实现注释描述what、why,而不是how

写实现注释时需要记住的最重要的一点就是,描述代码是做什么的(what)和为什么这么做(why),而不是描述怎么做(how)。因为实现代码本身就是how,如果还添加描述how的注释,那就犯了“注释重复代码”的错误了。

前文中的这个注释就是典型的描述how的注释,该注释除了用文字重复一遍代码的逻辑,并没有其他更好的用处:

// If there is a LOADING readRpc using the same session
// as PKHash pointed to by assignPos, and the last PKHash
// in that readRPC is smaller than current assigning
// PKHash, then we put assigning PKHash into that readRPC.

典型的描写how的实现注释(选自《A Philosophy of Software Design》中的例子)

更好的方法是描述what的注释,通过文字表达该段代码的用途,置于该用途是如何实现的,那就交给代码去解释吧

// Try to append the current key hash onto an existing
// RPC to the desired server that hasn't been sent yet.

典型的描写what的实现注释(选自《A Philosophy of Software Design》中的例子)

需要注意的是,实现注释通常不是必须的,对于一些简短的、功能单一的函数,一个好的函数命名即可替代实现注释。

总结

注释是软件开发过程中的性价比极高的一个工具,它只需要花费20%的时间,即可获取80%的价值。代码注释最重要的作用就是让读者可以在不读源码的情况下,快速了解一段代码的主要功能。注释的作用还远远不止这些,John Ousterhout在《A Philosophy of Software Design》一书中提到,写注释最好的时机是在写代码之前。这样,注释就相当于一种设计工具:在编码前通过注释描述这段代码的功能,然后在通过编码实现这个功能,如果这个过程有偏差,则再调整注释。写注释时要切记两点,不要重复代码更新代码后也要更新注释!前者可以减少冗余的注释,后者则可以避免因为过期的注释而误导读者。

当然,注释也不是越多越好,如果一个系统写的注释太多,很有可能就是该系统设计得过于复杂,只能通过注释来帮助读者理解整个系统。

总而言之,学会怎么写好代码注释,是一个程序员的必备技能,这即是为了开发团队的其他成员,也是为了未来的自己。

我是一名Java程序员,辞职后全职在做线上一对一Java辅导学习,另外一直在学习如何写文章,说实在的,每次在后台看到一些读者的回应都觉得很欣慰,为了感谢读者们,我想把我收藏的一些Java干货贡献给大家,回馈每一个读者,希望能帮到你们。
干货主要有:
① Java电子书(主流和经典的书籍应该都有了)
② Java基础视频教程(适合小白学习)
③ Java项目练习
④ Java开发工具,经验文章
⑤ Java学习路线图

如果你用得到的话可以直接拿走,私信发送“编程”即可!

avaScript 注释可用于提高代码的可读性。

JavaScript 注释

JavaScript 不会执行注释。

我们可以添加注释来对 JavaScript 进行解释,或者提高代码的可读性。

单行注释以 // 开头。

代码练习:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JavaScript 注释</title>
</head>
<body>
<h1 id="q"></h1>
<p id="w"></p>
<script>
    // 输出标题:
    document.getElementById("q").innerHTML="Welcome to my Homepage";
    // 输出段落:
    document.getElementById("w").innerHTML="This is my first paragraph.";
</script>
</body>
</html>

运行结果:

为前端开发的基石,JavaScript编程语言不仅易学易用,还具有强大的跨平台特性。本文将为你详细介绍JavaScript的引入方式、注释以及输入输出,帮助你轻松掌握JavaScript编程的精髓,成为一名炙手可热的前端开发高手!

引入方式:轻松踏入JavaScript的大门

作为JavaScript编程语言的初学者,了解如何引入JavaScript代码是你进入这个神奇世界的第一步。有三种常见的引入方式:

  • 内联方式:将JavaScript代码直接写入HTML文件中的<script></script>标签中。这种方式简单快速,适合小型项目或简单功能的实现。
<!DOCTYPE html>
<html>
  <head>
    <title>内联方式</title>
  </head>
  <body>
    <h1>你好,谧夜星球!</h1>

    <script>
      // 在这里编写你的JavaScript代码
    </script>
  </body>
</html>
  • 内部文件方式:将JavaScript代码保存为外部文件,通过<script>标签的src属性引入。这种方式适用于多个HTML文件共享同一段JavaScript代码,提高了代码的复用性和维护性。
<!DOCTYPE html>
<html>
  <head>
    <title>内部文件引入方式</title>
    <script src="script.js"></script>
  </head>
  <body>
    <h1>你好,谧夜星球!</h1>
  </body>
</html>
  • 外部文件方式:将JavaScript代码保存为独立的外部文件,通过HTML文件的<script>标签的src属性引入。这种方式适合于大型项目,利于代码的模块化管理和团队协作。
<!DOCTYPE html>
<html>
  <head>
    <title>外部文件引入方式</title>
    <script src="script.js"></script>
  </head>
  <body>
    <h1>你好,谧夜星球!</h1>
  </body>
</html>

注释:注解代码,提升开发效率

在编程过程中,合理使用注释可以方便自己和他人理解代码的逻辑和用途,是良好编程风格的重要一环。在JavaScript中,有两种注释方式:

  • 单行注释:使用//符号进行注释,注释内容从//后开始,直到该行结束。
// 这是一个单行注释
  • 多行注释:使用/* */符号进行注释,注释内容位于/**/之间。
/* 
  这是一个多行注释,
  可以跨越多行
*/

通过合理注释,你不仅能提高自己的代码理解和维护效率,还能方便团队协作,使代码更加易于管理。

输入输出:与用户进行互动

在前端开发中,与用户进行互动是至关重要的。JavaScript提供了多种输入输出的方式,使得与用户的交互变得简单而灵活。

  • 弹窗输出:使用alert()函数可以在页面上弹出一个警告框,向用户显示一条消息。
alert("你好,谧夜星球!");
  • 弹窗输入:使用prompt()函数可以在页面上弹出一个对话框,允许用户输入文本。
var name = prompt("请输入用户名:");
alert("你好, " + name + "!");
  • 控制台输出:使用console.log()函数可以将信息输出到浏览器的控制台,方便开发过程中的调试和日志记录。
console.log("你好,谧夜星球!");
  • 页面输出:通过操作HTML元素,可以将信息直接显示在页面上。
document.getElementById("output").innerHTML = "你好,谧夜星球!";

通过合理运用输入输出的方式,你可以实现与用户的互动,接收用户的输入并输出相应的结果,增加页面的交互性和用户体验。

总结:通过本文的介绍,我们已经了解了JavaScript编程语言的引入方式、注释以及输入输出。作为前端开发的基石,JavaScript在网页开发中扮演着重要的角色。希望通过学习JavaScript,能够轻松掌握前端开发的精髓,成为一名炙手可热的前端开发高手!加油!