WEB资源网

最新鲜的WEB程序员技术文档及相关资源 – Happy Life, Happy Coding!

JavaScript注释没用吗?

我应该在代码中写注释吗?或者是不惜一切代价避免呢?抑或是有节制的使用它们呢?在开发软件的时候,所有的开发者对何时何地使用注释都有他们自己的见解。这篇文章只是阐述我的观点,而不是什么真理。

我的这个观点仅限于JavaScript面向过程编程或者面向对象编程。

注释和可维护性

这篇博客是关于编写可维护的代码,即:

  • 容易理解
  • 易于扩展
  • 易于调试
  • 方便测试

可维护的代码就需要大量的注释吗?如果代码写的好,那我觉得就不是必须的。代码本身就能说明问题。

可维护的代码不需要任何的注释吗?下面我们通过一些例子来说明。

注释的演化

方法1:JavaScript diluted with BS

假设一个昵称为 ByTheBook 的初级开发者下了下面的代码:

/**
 *  getWinner
 *
 *  Gets the name of the winner who has the best score from the
 *  participants array.
 *
 *  @param {Array} participants
 *  @return {string} the name
 *  @throws {Error} wrong participant list
 */
function getWinner( participants ) {

    var maxScore = 0;  // temporary variable to keep maximum score
               // initially -1 to always find a participant
    var name = null;   // name with maximum score
    // debugger; // uncomment for debugging
    // participants is an array, so it has a length property
    for( var i = 0; i < participants.length; ++i ) {  // i: loop variable
        var currentParticipant = participants[i]; // we examine this participant
        if( currentParticipant.score > maxScore ) {  // initially any number > null
            // we found a new candidate to return
            name = currentParticipant.name;
            // DON'T FORGET TO UPDATE THE MAX SCORE!!!
            maxScore = currentParticipant.score;
        }
    }

    // console.log( name, typeof name );

    // Correct result has to be a string
    if( typeof name === 'string' ) {
        return name;
    } else { // technically an else is not needed here
        throw new Error( 'Wrong participants' ); // Wrong participants
    }
    // unreachable
}

这段代码有什么问题吗?

首先,一个原则是,不应该在你的应用中留下任何注释掉的代码,这显得非常的不专业。如果注释的代码只是用于调试的目的,那将更糟。清理掉你的提交,如果已经晚了的话,以后要养成本地提交的习惯,然后使用 rebase 来清理掉提交。

保留注释的代码只是很大问题中的一小部分:从技术上讲,90%的这些注释的代码只会增加麻烦,它们没有任何的价值,而且还会分散读者的注意力。例如,在读完上面的代码后,你是否考虑到如果有不止一个的参与者有相同的最高分,我们的程序应该如何处理呢。

方法2:Self-documenting code

Flash 是另一个初级开发者,假设他写了下面这段没有任何注释的代码:

function getWinner( participants ) {
    if( participants.length === 0 ) return null;
    var lastIndex = participants.length - 1;
    var topParticipant = _.sortBy( participants, 'score' )[ lastIndex ];
    return topParticipant.name;
}

从技术上讲,该解决方案是有效的。并且,在没有参与者的时候会返回 null 。然而,该代码并没有对处理并列第一名的问题给出任何的注释。

另外,在没有读完所有代码的时候,你能知道 participants 对象的格式吗?你必须从代码中反向获得信息:participants 是一个数组对象,它包含 namescore 两个元素,类型分别为 stringinteger

方法3:The Startup code

Startupper 注意到了该问题,然而他有很多其他的事情要做,所以他只是放了一个 TODO 的注释:

// TODO: sort out ties when two participants have max score
function getWinner( participants ) {
    if( participants.length === 0 ) return null;
    var lastIndex = participants.length - 1;
    var topParticipant = _.sortBy( participants, 'score' )[ lastIndex ];
    return topParticipant.name;
}

使用 TODO 的问题是它们可能一直保留在代码中。一些 TODO 可能放在另一些 TODO 前面。一些TODO可能会与另外的有冲突。另一些可能描述的很不清楚,没人能看懂他的初衷是什么。

在这个例子中 TODO 描述了实质的问题,虽然还没有给出解决方案。

另外, participants 参数的类型仍然不清楚。

方法4:Declarative head comments

Declarator 有个写头部声明注释的习惯:

  • 简短而精确的功能描述
  • 参数类型列表以及返回值
  • 异常
/**
 *  getWinner returns the name of the first participant in participants 
 *  with the highest score. 
 *
 *  @param {Array of Objects} participants
 *            Elements: {string}  name
 *                      {integer} score
 *  @return {Mixed(string|null)} name
 */
function getWinner( participants ) {
    if( participants.length === 0 ) return null;
    var lastIndex = participants.length - 1;
    var topParticipant = _.sortBy( participants, 'score' )[ lastIndex ];
    return topParticipant.name;
}

该解决方案清楚的记录了 getWinner 的作用。大部分代码都非常有帮助,并且做到了与代码分离,不存在行内注释。

方法5:修复问题

PickyPerfectionist 是一个不会妥协的开发者。他以编写可维护的代码为荣。他的口头禅是:“只有烂代码才需要注释”。他的第二个信条是,从不发布不够好的代码。这意味着他从来都不写注释。

在写功能之前,PickyPerfectionist 都会先明确需求。在有相同最高分的时候,他会把所有的参与者姓名都打印出来,以逗号分隔。

function getWinners( participants ) {
    var winnerNames = [];
    var winnerScore = null;
 
    for( var i = 0; i < participants.length; ++i ) {
        var currentName = participants[i].name;
        var currentScore = participants[i].score;
        if( currentScore > winnerScore ) {
            winnerScore = currentScore;
            winnerNames = [ currentName ];
        } else if ( currentScore === winnerScore ) {
            winnerNames.push( currentName );
        }
    }
 
    var result = winnerNames.join( ', ' );
 
    return result.length > 0 ? result : null;
}

这段代码非常易读。并且解决了前面存在的问题,不需要做任何的注释。虽然对 participants 的结构没有给出定义,但你能从代码中了解到所有你需要的信息。即使函数名改变了,那也不会有任何的问题。

假设利益相关者决定不是列举所有赢家,他们改变了游戏规则,只有一个赢家。这个你刚才看到过的注释非常少的代码将可以解决问题:

function getWinner( participants ) {
    if( participants.length === 0 ) return null;
    var lastIndex = participants.length - 1;
    var topParticipant = _.sortBy( participants, 'score' )[ lastIndex ];
    return topParticipant.name;
}

假如有一天,有个开发者发现了问题,这个方法只返回第一个得分最高的参与者。基于发现这个问题的开发者类型的不同,有可能会出现不同的解决方案。解决方案可能是一个 TODO 注释或者是与相关利益者要求不符的方法。所有的解决方案都有下面两个共同点:

  • 都会需要时间
  • 在某种程度上增加麻烦

你会考虑使用注释吗?你有其他避免浪费时间的替代解决方案吗?如果是的话,假设你看文档,这也是需要时间的。

从技术上讲,自动化测试可以确保只有一个胜利者。单元测试可以避免开发者与要求不符的提交。但提前测试并不能指出问题,甚至在特殊情况下,现有的测试可能会让你修改 getWinner 方法,而返回多个赢家。

常识性的注释

我的观点是,只要是有价值的注释,都是有用的。没有最终的对与错。

禁止代码的注释就是计算你饮食的卡路里,在一定程度上,它们是有帮助的,如每天摄入2000卡路里而不是8000或者300。吃沙拉的好处远比喝汽水要多。相似的,禁止代码的注释可以促使你写更好的代码。然而,在代码确实需要注释的时候,禁用注释在长远上看是非常有害的,这最终会导致代码混乱,可维护性差。

什么时候注释是有用的呢?首先,JavaScript不允许指定参数和返回值的类型。因此,关于类型定义的信息是有用的。这些类型定义在许多其他语言的函数中都有。如果你知道 == 和 === 的区别,并且想使用 === ,那么变量类型是很重要的。

比如下面的例子:

/**
 *  listenTo
 *
 *  Registers callback so that it's immediately called once emitter emits
 *  the event eventName.
 *
 *  @param  {Object}    emitter
 *  @param  {string}    eventName
 *  @param  {function}  callback
 *
 *  @return {void}
 */
listenTo : function( emitter, eventName, callback ) { /* ... */ }

类型定义非常明确。

当涉及到类或者函数定义的时候,我更偏向于头部声明。一句话就可以说明类的作用,它里面的方法也一样。面向过程的编程,头部声明也是很有用的。

头部注释声明也可以包含下面这些:

  • 可选参数:有时候最后一个参数可能是未定义的
  • 混合类型
  • 符合类型:参见方法4
  • 异常信息
  • 调用该方法的前提条件
  • 副作用

决定在注释中使用以及避免哪些信息,非常简单,却很重要。一个周末项目或者原型的注释与一个需要长期开发和维护的网络项目的注释应该是不同的。

编写优秀的头部注释是一门艺术。但写很好的头部注释是需要常识的。常识告诉我,头部注释应该是包含信息的。下面是一个不好的例子:

/**
 *  onRender
 *
 *  Callback on render
 *
 *  @return {void}
 */
onRender : function() {
    this.removeAllBindings();
    this.cleanOpenedWidgets();
}

而这个会好一些:

/**
 *  onRender: called before rendering the component for the purpose
 *  of cleaning up model-view bindings and removing nodes left by
 *  opened widgets.
 *
 *  @return {void}
 */
onRender : function() {
    this.removeAllBindings();
    this.cleanOpenedWidgets();
}

一个好的头部注释使得99.99%的行内注释变得没有意义。我基本上同意以下观点:不管方法多么的复杂,行内注释只会分散开发者的注意力,而不会对他们有任何帮助。

如果一个头部注释很难写,那么这恰恰说明你应该好好考虑下是否应该把方法分解成多个。因此,头部注释可以帮你写出更好的代码。

从代码维护的角度来看,头部注释至少有以下两个好处:

  1. 当审核人只需要了解方法的作用的时候,头部注释可以节省很多时间;
  2. 对于错误的方法,审核人能够方便的了解到该方法的原始意图

为了这些,头部注释都是必须的。这并不是一项多么艰难的工作,因为开发者知道他们自己正在做什么。

我是否应该写注释呢?

这取决于你,看哪样对你更有帮助。有些项目不需要任何的注释,而有些则必须包含头部注释。不像其他强类型语言,JavaScript的注释给出了参数以及返回值的变量类型。紧凑的头部注释可以使你的代码更容易理解。此外,头部注释会使你思考你的代码,这有时候会让你的代码更容易维护。

综上所述,我不认为所有的注释都是没用的。世界也不是绝对的黑的或者白的。极端的意见通常会引起更强烈的争论,而折中的方法在长远来看却更加有帮助。

via:zsoltnagy,本文由 Specs 翻译整理,发布在 WEB资源网,转载请注明来源。

作者
主站点:http://9iphp.com/ 个人简介:http://me.9iphp.com