如何在JavaScript中谨慎使用代码注释

前言

最令程序员头痛的事情莫过于阅读别人的代码,但其实时间一久阅读自己的代码也会很痛苦。 问题不是出在『自己或别人』,而是在代码本身。

必要的注释可以阐明实现细节和设计意图,以此节约自己和别人的时间。 然而很多时候注释起的作用却适得其反,比如自动生成的过多的注释分散阅读者的注意力, 而过期的失效的注释更是误导阅读者。

自动生成的注释

代码注释的泛滥想必是从Eclipse,Visual Studio等IDE开始的。 这些IDE提供了很多快捷功能,生成类/接口的骨架,具有Getter/Setter的属性等等。 如果用过IDE,下面的代码你一定不会陌生:

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
}

上述6行代码中的4行注释包含的信息量是0,既没有阐释参数args是何物,也没有说明main的用途。 然而大量的项目中都充斥着这样的自动生成注释。

『建议』:如果有参数或机制需要说明,请补充这些信息。否则请删除自动生成注释。 当然,用于生成文档的注释除外。

过多的注释

总会有人不厌其烦地编写长篇累牍的注释,或无微不至,或语焉不详,或晦涩难懂,或文采飞扬。 总之没有帮助我更快阅读代码的注释都是失败的注释。

为了说明问题,Harttle克隆了4.x Linux Kernel源码, 来大致分析一下其注释行数。 我们知道内核代码95%以上是C语言,所以统计.c文件就足够说明问题了。

➜ linux git:(master) git clone git@github.com:torvalds/linux.git --depth=1
➜ linux git:(master) find . -name "*.c" -o -name "*.h" -exec grep -E '^\s*((\*)|(/[/*]))' {} \; | wc -l
724804
➜ linux git:(master) find . -name "*.c" -o -name "*.h" -exec cat {} \; | wc -l
4018961
➜ linux git:(master) node
> 724804/(4018961-724804)
0.22002715717556875

内核仓库中的代码大概是402万行(未移除空行),其中注释72万行,占比22%。 Linux内核使用低级的C语言编写,涉及到复杂的CPU调度、内存管理,驱动程序。 因此注释会偏多一些,一般的项目注释应小于这个数值。

『建议』:如果你的代码中注释超过了20%,那么显然你过度注释了。

文件头注释

很多编辑器/IDE都会生成默认的文件头,例如:

/**
* @file /tmp/xxx.js
* @author harttle(yangjvn@126.com)
* @date 2016-08-30 22:33
* @description A XXX Implementation for XXX.
*/

文件头注释清晰地列出了文件的作者、功能描述等信息,看起来很有用。 不过这样的文件头存在的问题在于其维护性:

  • 其他人做小的修改时未必会修改@author,甚至连@author都不知道现在该文件已经面目全非。
  • 每次移动该文件,是否还需要花功夫更新 @file 信息?
  • 谁会在每次代码修改后记得更新 @description,于是@description也总是误导读者

文件头注释意在维护代码文件的元信息,以便在分发和部署过程中维护作者版权等信息。 然而在拥有版本控制的代码仓库中,这些信息不再需要手动维护,甚至可以通过git blame查看每一行代码的作者和时间信息。

『建议』:使用版本控制工具,删除文件头注释。版权信息可在构建或分发时生成。

冗余的注释

意图非常清楚的代码原则上不需要注释,多余的注释反而会造成维护性问题。 尤其是非英语母语的作者常常会掉到这个坑里。比如变量和函数的注释:

/*
* 获取用户数目
*/
function getUserCount(){
// 用户的列表
var userList = [];
}

这不是废话么!冗余的注释问题仍然在于维护性,例如调整函数功能、调整参数顺序, 或者更换变量名时我们不得不更新这些注释。否则这些注释就会误导下一个读者。

【建议】:不说废话。

抽取注释到标识符

可能读者也会有这样的经验:当我们写了一大段代码时,往往需要把它们分为几块。 然后在每一块开头添加一段注释。例如:

function calcTotalCharge(movies, user){
// Calculate Movie Charge
var movieCharge = 0;
for(var i=0; i<movies.length; i++){
var charge = 0;
if(movie.type === 'discount'){
charge = movie.charge * 0.8;
}
else if(movie.type === 'short'){
charge = movie.charge * 2;
}
else if(movie.type === 'normal'){
charge = movie.charge;
}
movieCharge += charge;
}

// Calculate User Charge
var rentCharge = 0;
if(user.isVIP1){
rentCharge = 10;
}
if(user.isVIP2){
rentCharge = 200;
}
else if(user.isVIP3){
rentCharge = 300;
}
else if(user.isVIP4){
rentCharge = 500;
}
// Calculate Total Charge
return movieCharge + rentCharge;
}

上述代码中的三段注释确实加速了阅读代码的速度, 但每当代码需要注释才能读懂时就应该警醒:是不是结构设计有问题。 对于上述代码,我们可以通过更加可复用的结构来消除注释:

function calcTotalCharge(movies, user){
return calcMovieCharge(movies) + calcUserCharge(user);
}
function calcMovieCharge(movies){
var total = 0;
for(var i=0; i<movies.length; i++){
total += calcSingleMovieCharge(movie);
}
return total;
}
function calcSingleMovieCharge(movie){
if(movie.type === 'discount') return movie.charge * 0.8;
else if(movie.type === 'short') return movie.charge * 2;
else if(movie.type === 'normal') return movie.charge;
return 0;
}
function calcUserCharge(user){
if(user.isVIP1) return 10;
else if(user.isVIP2) return 200;
else if(user.isVIP3) return 300;
else if(user.isVIP4) return 500;
return 0;
}

代码重构之后原来的注释就变得毫无意义,代码意图都被清晰的表述在标识符的命名中。 通常重构会带来代码量的减小,因为封装了分支、每个单元的逻辑也更加明确。

【建议】:当我们发现不得不进行注释时,需要警醒是否结构设计发生了问题。

有用的注释

至此Harttle已描述了这么多反模式,并非为了说明代码注释不重要。 而是为了说明『代码注释存在的意义在于帮助理解代码本身』。 例如在编写一些Trick,Polyfill,临时代码,以及复杂算法时,注释变得相当重要。 例如:

  • Tricks and Polyfills。有时简单的Trick就可解决多数问题问题, 为没必要编写复杂的普适算法, 例如检测浏览器的DOM API支持,检测AMD/CommonJS环境等等。 这时我们需要清晰地说明这些Trick的意图,甚至可以将这些代码抽离为polyfill模块。
  • 复杂算法。有时我们会编写数学性非常强的算法,一眼望去不知所云。 在开始这些算法前清晰地说明其意图何在,读者也就不必花大功夫读懂这些数学了。
  • 公有接口。模块的对外接口从逻辑上定义了模块类型,公有接口代码也更容易被人读到。 尤其是JavaScript接口:如果不注释options中到底是什么,谁晓得接口如何使用。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • javascript匹配js中注释的正则表达式代码

    有时候我们需要将js的注释去掉,减少代码中的冗余,有时候注释太多导致页面体积大. 注释图示如下: 一.匹配多行注释正则表达式: /(?:^|\n|\r)\s*\/\*[\s\S]*?\*\/\s*(?:\r|\n|$)/g 二.单行注释正则表达式: /(?:^|\n|\r)\s*\/\/.*(?:\r|\n|$)/g 以上内容介绍到这了,希望大家以后多多支持我们.

  • javascript 注释代码的几种方法总结

    javascript注释代码一般有两种方法: 单行注释 多行注释 javascript单行注释 单行注释以"//"开头,到改行的末尾结束.下面是javascript单行注释实例: <html> <head> <title>javascript单行注释</title> <script language="javascript"> <!-- // The first alert is below aler

  • javascript三种代码注释方法

    javascript语言里面的注释方法有三种. 第一种是多行注释"/**/",一般js文件开头,介绍作者,函数等信息. 复制代码 代码如下: /* *author:xxx *day:2008-08-10 */ 第二种注释方法是最常见的"//",在程序间随处可见,只能注释单行. 复制代码 代码如下: //这是一行注释,只能注释单行. //另一行注释 第三种注释不是很常见,会和html内的注释混淆,不推荐使用. 复制代码 代码如下: <!-这是一行注释,只能注释单

  • 如何在JavaScript中谨慎使用代码注释

    前言 最令程序员头痛的事情莫过于阅读别人的代码,但其实时间一久阅读自己的代码也会很痛苦. 问题不是出在『自己或别人』,而是在代码本身. 必要的注释可以阐明实现细节和设计意图,以此节约自己和别人的时间. 然而很多时候注释起的作用却适得其反,比如自动生成的过多的注释分散阅读者的注意力, 而过期的失效的注释更是误导阅读者. 自动生成的注释 代码注释的泛滥想必是从Eclipse,Visual Studio等IDE开始的. 这些IDE提供了很多快捷功能,生成类/接口的骨架,具有Getter/Setter的

  • 如何在JavaScript中运行.NET Core代码详情

    目录 一.前言 二.DotNetJS 三.Demo 1. 创建项目 2. 实现C#代码 3. 实现JS代码 4. 运行效果 四.结论 一.前言 在.NET Core中运行JavaScript代码,目前已经有很多实现方案. 但是,如果你希望在纯JavaScript环境中运行.NET Core代码呢? 那么,DotNetJS可能对你有所帮助. 二.DotNetJS DotNetJS可以将C#项目编译为与任何环境兼容的单文件JavaScript库,无论是Web浏览器,Node.js还是自定义限制空间,

  • 如何在JavaScript中正确处理变量

    变量无处不在.即便我们写一个小函数或一个小工具,也要声明.赋值和读取变量.增强对变量的重视,可以提高代码的可读性和可维护性. 1.建议使用 const,要么使用 let 用 const 或 let 声明自己的 JavaScript 变量.两者之间的主要区别是 const 变量在声明时需要初始化,并且一旦初始化就无法再重新赋值. // const 需要初始化 const pi = 3.14; // const 不能被重新赋值 pi = 4.89; // throws "TypeError: Ass

  • 如何在JavaScript中使用localStorage详情

    如果你是一名开发人员,想要进入到.NET的世界,你需要知道都有哪些可能.由于.NET Framework是.NET生态系统中最流行的技术,你可以用它来构建各种各样的应用程序,但是最近,出现了一些新的东西,比如 .NET Core 和.NET Standard library.我们可以在项目或构建中使用它吗? localStorage对象是web编程中应用最广泛的对象之一.它提供了在用户计算机上本地存储键值对的简单解决方案. 大多数web开发人员都喜欢localStorage API,因为它具有简

  • 如何在JavaScript中创建具有多个空格的字符串?

    通过创建变量 var a = 'something' + '                         ' + 'something' 我得到这个值:'something something'. 如何在JavaScript中创建一个包含多个空格的字符串? 使用\xa0- 它是一个NO-BREAK SPACE char. 从UTF-8编码表和Unicode字符引用,可以写成如下: var a = 'something' + '\xa0\xa0\xa0\xa0\xa0\xa0\xa0' + '

  • 详解如何在Javascript中使用Object.freeze()

    Object.freeze() Object.freeze() 方法可以冻结一个对象.一个被冻结的对象再也不能被修改:冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性.可配置性.可写性,以及不能修改已有属性的值.此外,冻结一个对象后该对象的原型也不能被修改.freeze() 返回和传入的参数相同的对象 用法 const objectExample = { prop1: 20, prop2: "羊先生" }; // 默认情况下,我们可以根据需

  • 如何在JavaScript中等分数组的实现

    最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了. github 地址:https://github.com/qq449245884/vue-okr-tree 在本教程中,我们来学习一下如何使用Array.splice()方法将数组等分,还会讲一下,Array.splice() 和 Array.slice() 它们之间的不同之处. 1. 将数组分为两个相等的部分 我们可以分两步将数组分成两半: 使用length/2和Math.cei

  • 如何在JavaScript中比较日期详解

    目录 前言 如何在 JavaScript 中与日期对象进行日期比较 如何使用 JavaScript 进行等式比较 如何执行特定日期比较 总结 前言 日期是开发人员在创建实际应用程序时最常用的数据类型之一. 但通常,开发人员会在这种数据类型上苦苦挣扎,最终使用像 Moment.js 这样的日期库来完成简单的任务,这些任务不值得安装整个包所带来的大包大小. 当我们想到 JavaScript 中的日期比较时,我们会想到使用 Date 对象 ( Date()),当然,它确实有效. date 对象允许我们

  • 详解如何在JavaScript中使用装饰器

    目录 安装 vite配置 webpack配置 使用 语法: @+函数名 类装饰器 带参数的修饰器 类成员装饰器 多个装饰器的执行顺序 应用 延迟 节流 防抖 Decorator装饰器是ES7的时候提案的特性,目前处于Stage 3候选阶段(2022年10月). 装饰器简单来说就是修改类和类方法的语法糖,很多面向对象语言都有装饰器这一特性. 为了使用装饰器特性,我们需要用进行babel转义.这里需要用到的是@babel/plugin-proposal-decorators. 安装 npm inst

  • 如何在JavaScript中实现私有属性的写类方式(一)

    之前讨论过JavaScript中的写类方式.但没有讨论私有的实现.这篇看下. 我们知道JS中私有属性的实现本质就是 var + closure.如下 复制代码 代码如下: function Person(n, a){     // public     this.name = n;     // private     var age = a;     this.getName = function(){         return this.name;     }     this.getA

随机推荐