正如我们在 代码结构 这一章所了解到的那样,注释可以是以 // 开始的单行注释,或是 /* ... */ 结构的多行注释。

我们通常使用它们来描述代码怎样工作和为什么工作。

从第一眼看,注释代表的东西可能是显而易见的,但编程中的新手通常会弄错。

糟糕的注释

新手倾向于使用注释来解释“代码中发生了什么”。就像这样:

// 这里的代码会先做这件事(...) 然后做那件事 (...)
// ...谁知道还有什么...
very;
complex;
code;

但在良好的代码中,这种“解释性”注释的数量应该是最小的。严肃的说,就算没有它们,代码也应该很容易理解。

关于这一点有一个很棒的原则:“如果代码不够清晰以至于需要一个注释,那么或许它应该被重写。”

配方:分解函数

有时候,用一个函数来代替一个代码片段是更好的,就像这样:

function showPrimes(n) {
  nextPrime:
  for (let i = 2; i < n; i++) {

    // 检测 i 是否是一个质数(素数)
    for (let j = 2; j < i; j++) {
      if (i % j == 0) continue nextPrime;
    }

    alert(i);
  }
}

更好的变体,使用一个分解出来的函数 isPrime

function showPrimes(n) {

  for (let i = 2; i < n; i++) {
    if (!isPrime(i)) continue;

    alert(i);
  }
}

function isPrime(n) {
  for (let i = 2; i < n; i++) {
    if (n % i == 0) return false;
  }

  return true;
}

现在我们可以很容易的理解代码了。函数自己变成了注释。这种代码被称之为自我描述型代码。

配方:创建函数

如果我们有一个像下面这样很长的代码块:

// 我们在这里添加威士忌(译者注:国外的一种酒)
for(let i = 0; i < 10; i++) {
  let drop = getWhiskey();
  smell(drop);
  add(drop, glass);
}

// 我们在这里添加果汁
for(let t = 0; t < 3; t++) {
  let tomato = getTomato();
  examine(tomato);
  let juice = press(tomato);
  add(juice, glass);
}

// ...

我们像下面这样将它重构成函数可能会是一个更好的变体:

addWhiskey(glass);
addJuice(glass);

function addWhiskey(container) {
  for(let i = 0; i < 10; i++) {
    let drop = getWhiskey();
    //...
  }
}

function addJuice(container) {
  for(let t = 0; t < 3; t++) {
    let tomato = getTomato();
    //...
  }
}

再一次,函数本身就可以告诉我们发生了什么。没有什么地方需要注释。并且分割了之后代码的结构更好了。每一个函数做什么、需要什么和返回什么都非常的清晰。

实际上,我们不能完全避免“解释型”注释。在那些复杂的算法中,会有一些处于优化的目的而机智的“调整”。但是通常情况下,我们应该尽可能地保持代码的简单和“自我描述”。

好的注释

因此,解释性注释通常来说都是不好的,那么哪一种注释才是好的呢?

描述架构

提供组件的一个高层次的概况,它们如何相互作用、各种情况下的控制流程是什么样的… 简而言之 —— 『代码的鸟瞰图』。有一个专门为高层次架构图而设计的图表语言 UML,绝对值得学习。

记录函数的用法

有一个专门的语法 JSDoc 来记录函数: 用法、参数和返回值。

例如:

/**
 * 返回 x 提高到 n 次幂之后的值。
 *
 * @param {number} x 要改变的值。
 * @param {number} n 幂数,必须是一个自然数。
 * @return {number} x 提高到 n 次幂之后的值。
 */
function pow(x, n) {
  ...
}

这种注释允许你理解函数的目的,并在不需要看它的代码的情况下正确的使用它。

顺便说一句,很多诸如 WebStorm 这样的编辑器都可以很好的理解和使用这些注释来提供自动完成和一些自动化代码检查工作。

当然了,也有一些像是 JSDoc 3 这样的工具可以从注释中直接生成 HTML 文档。你可以在 http://usejsdoc.org/ 阅读关于 JSDoc 的更多信息。

为什么任务以这种方式解决?

写了什么代码很重要。但是什么代码写对于理解发生了什么或许更重要。为什么任务恰好用这种方式解决了?代码并没有给出答案。

如果有很多种方法都可以解决这个问题,为什么偏偏是这一种?尤其当它不是最显而易见的时候。

没有这样的注释的话,就可能会发生下面的情况:

  1. 你(或者你的同事)打开了前一段时间写的代码,看到它不是最理想的实现。
  2. 你认为:“我当时是有多蠢啊,现在我多么的聪明”,然后用“更显而易见而且正确的”方式重写了一遍。
  3. 重写的这股冲动劲是好的。但是在重写的过程中你发现“更显而易见”的解决方案实际上是不足的。你甚至依稀地想起了为什么会这样,因为你很久之前就已经尝试这样做了。于是你还原到了那个正确的实现。但是时间已经浪费了。

解释解决方案的注释非常的重要。它们可以帮助你以正确的方式继续开发。

代码有哪些巧妙的特性?它们被用在什么地方?

如果代码存在任何巧妙和不显而易见的方法,那绝对需要注释。

总结

一个好的开发者的标志之一就是他的注释:它们的存在甚至它们的缺席。

好的注释可以使我们更好的维护代码,并且在很长时间之后依然可以更高效地回到代码中和使用其功能。

注释这些内容

  • 整体架构,高层次的观点。
  • 函数的用法。
  • 重要的解决方案,特别是在不是很明显时。

避免注释

  • 阐述“代码如何工作”或“它做了什么”。
  • 只有在没有这些就不可能使代码变得如此简单和自我描述的情况下才可以使用它们。

注释也被用于一些自动文档工具(比如 JSDoc3):他们读取注释然后构建出 HTML 文档(或者其他格式的文档)。

教程路线图

评论

在评论之前先阅读本内容…
  • 欢迎你在文章下添加补充内容、提出你的问题或回答提出的问题。
  • 使用 <code> 标签插入几行代码,对于多行代码 — 可以使用 <pre>,对于超过十行的代码 — 建议使用沙箱(plnkrJSBincodepen 等)。
  • 如果你无法理解文章中的内容 — 请详细说明。