正如我们在 代码结构 一章所了解到的那样,注释可以是以 // 开始的单行注释,也可以是 /* ... */ 结构的多行注释。
我们通常通过注释来描述代码怎样工作和为什么这样工作。
乍一看,写注释可能很简单,但初学者在编程的时候,经常错误地使用注释。
糟糕的注释
新手倾向于使用注释来解释“代码中发生了什么”。就像这样:
// 这里的代码会先做这件事(……)然后做那件事(……)
// ……谁知道还有什么……
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) {
...
}