本节书摘来自异步社区《编写可维护的JavaScript》一书中的第2章,第2.3节,作者: 【美】Nicholas C. Zakas 译者: 李晶 , 郭凯 , 张散集 更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.3 使用注释
何时添加注释是程序员经常争论的一个话题。一种通行的指导原则是,当代码不够清晰时添加注释,而当代码很明了时不应当添加注释。比如这个例子中,注释是画蛇添足。
// 不好的写法
// 初始化count
var count = 10;
因为代码中初始化count的操作是显而易见的。注释并没有提供其他有价值的信息。换个角度讲,如果这个值10具有一些特殊的含义,而且无法直接从代码中看出来,这时就有必要添加注释了。
// 好的写法
// 改变这个值可能会让它变成青蛙
var count = 10;
当然不可能因为修改了count的值它就变成了青蛙,但这的确是一个好的注释写法的例子,因为注释中给出了必要的信息,如果没有注释,你不可能获得这些信息。想象一下如果你修改了count的值它真的变成了青蛙,实在是让人困惑不解,一切都源于你没有写这句注释。
因此,添加注释的一般原则是,在需要让代码变得更清晰时添加注释。
2.3.1 难于理解的代码
难于理解的代码通常都应当加注释。根据代码的用途,你可以用单行注释、多行注释,或是混用这两种注释。关键是让其他人更容易读懂这段代码。比如,这段示例代码摘自YUI类库中的Y.mix()方法。
// 好的写法
if (mode) {
/*
* 当mode为2时(原型到原型,对象到对象),这里只递归执行一次
* 用来执行原型到原型的合并操作。对象到对象的合并操作
* 将会被挂起,在合适的时机执行
*/
if (mode === 2) {
Y.mix(receiver.prototype, supplier.prototype, overwrite,
whitelist, 0, merge);
}
/*
* 根据指定的模式类型,我们可能会从源对象拷贝至原型中,
* 或是从原型拷贝至接收对象中
*/
from = mode === 1 || mode === 3 ? supplier.prototype : supplier;
to = mode === 1 || mode === 4 ? receiver.prototype : receiver;
/*
* 如果supplier或receiver不含有原型属性时,
* 则逻辑结束,并返回undefined。如果有原型属性,
* 则逻辑结束并返回receiver
*/
if (!from || !to) {
return receiver;
}
} else {
from = supplier;
to = receiver;
}
Y.mix()方法使用常量来决定如何处理。mode参数就表示这些常量其中之一,但仅仅通过这些数值无法理解它们各自代表的含义。这里的注释非常棒,因为它及时地解释了这里复杂的决策逻辑。
2.3.2 可能被误认为错误的代码
另一个适合添加注释的好时机是当代码看上去有错误时。在团队开发中,总是会有一些好心的开发者在编辑代码时发现他人的代码错误,就立即将它修复。有时这段代码并不是错误的源头,所以“修复”这个错误往往会制造其他错误,因此本次修改应当是可追踪的。当你写的代码有可能会被别的开发者认为有错误时,则需要添加注释。这里是另一段来自YUI源码的例子。
while (element &&(element = element[axis])) { // 提示: 赋值操作 if ( (all || element[TAG_NAME]) && (!fn || fn(element)) ) { return element; } } 在这个例子中,开发者在while循环控制条件中使用了一个赋值运算符。这不是一种标准用法,并常常被检测工具认为是有问题的。如果你对这段代码不熟悉,读到这段没有注释的代码时,很可能误以为这是一个错误,猜想作者的本意是使用比较运算符==而不是赋值运算符=。这行末尾的注释说明作者是有意为之,即赋值而非比较。这样,其他开发者在读到这段代码时就不会将它“修复”。
2.3.3 浏览器特性hack
JavaScript程序员常常会编写一些低效的、不雅的、彻头彻尾的肮脏代码,用来让低级浏览器正常工作。实际上这种情形是一种特殊的“可能被误认为错误的代码”:这种不明显的做浏览器特性Hack的代码可能隐含一些错误。这里有个例子,是摘自YUI类库的Y.DOM.contains()方法。
var ret = false;
if ( !needle || !element || !needle[NODE_TYPE] || !element[NODE_TYPE]) {
ret = false;
} else if (element[CONTAINS]) {
// 如果needle不是ELEMENT_NODE时,IE和Safari下会有错误
if (Y.UA.opera || needle[NODE_TYPE] === 1) {
ret = element[CONTAINS](needle);
} else {
ret = Y_DOM._bruteContains(element, needle);
}
} else if (element[COMPARE_DOCUMENT_POSITION]) { // gecko
if (element === needle || !!(element[COMPARE_DOCUMENT_POSITION](needle) & 16)) {
ret = true;
}
}
return ret;
这段代码的第6行包含一条重要的注释。尽管IE和Safari中都有内置方法contains(),但如果needle不是一个元素时,这个方法会报错。所以只有当浏览器是Opera时才能用这个方法,其他浏览器中needle必须是一个元素(nodeType是1)。这里关于浏览器的说明同样解释了为什么需要一个if语句,这个注释不仅确保将来不会被其他人误改动,而且在代码编写者回过头阅读自己的这段代码时,也会适时地针对新版本的IE和Safari的兼容情况做出调整。