前言
我们写注释,是为了给别人看,便于更好的理解代码从而能更好的维护代码。这样也是代码质量高的一个方面。
乍一看,注释很好写,但是注释不是所有的东西都集中到一起。这里有两个注释原则可以参考:
- 如无必要,勿增注释 ( As short as possible )
《Clean Code》这本书中,罗伯特 c. 马丁谈到这:注释不必要,因为代码应该是自说明的。如果代码不够清晰以至于需要一个注释,那么或许它应该被重写。
注释要避免过多过滥,不要为了注释而注释。我们应该追求【代码自注释】,代码本身能让阅读者快速判断。
// bad
// 如果数据已经准备好,则做...
if (data && data.status === 'ready') {
...
}
// good
const isDataReady = data && data.status === 'ready';
if (isDataReady) {
...
}
- 如有必要,尽量详尽 ( As long as necessary )
代码可以描述 how,但它不能解释 why。
需要注释的地方应该尽量详尽地去写,以让阅读者可以充分了解代码的逻辑和意图为标准。
坏的注释
- 注释对阅读代码无益
在上文的示例中,代码本身能够让阅读者快速判断,这样的注释应该删去。
- 没持续维护的注释
错误的文档可能比没有文档更糟糕。没有什么是比看到一段注释说的与下面代码表达的完全不同更令人沮丧的了。比浪费时间更糟糕的是误导。
一个解决方案是确保无论您正在更新的代码是什么,您同时都继续维护相关的注释。当然,以后只会有越来越少但却更有意义的注释,从而让维护更省力。
- 未遵循统一的风格规范:如单行注释长度、空格、空行等
// bad 注释过长,不利于阅读
/**
* Convert an input value to a number for persistence.If the conversion fails, return original string.
*/
function toNumber (val) {
...
}
// good
/**
* Convert an input value to a number for persistence.
* If the conversion fails, return original string.
*/
function toNumber (val) {
...
}
- 情绪性注释:如抱怨、歧视、搞怪等
/**
* 这个项目很差,bug很多,都给你留好了工作量,加油!
*/
好的注释
- 为什么
我们可以把当时写这段代码的动机意图,所思所想写下来,表达内在的逻辑算法。
/**
* Convert an input value to a number for persistence.
* If the conversion fails, return original string.
*/
function toNumber (val) {
...
}
- 特殊或不易理解写法的解释说明
比如一段长长的RegExp,你能理解这是怎么回事?或者能否在几年后也立刻理解?
// 当且仅当出现"|"的次数等于2时,对"|"之间的内容进行加粗、并加下划线
const reg = new RegExp("([^\\|]*)(\\|)([^\\|]*)(\\|)([^\\|]*)", "ig");
还有,我们的代码总有需要完善的地方,这里可以特殊注释TODO来提出这段的瑕疵。常用的特殊标记有两种:
- // FIXME: 说明问题是什么
- // TODO: 说明还要做什么或者问题的解决方案
- 对你来说清晰易懂的东西不一定对别人清楚
在很大的团队中,有初级工程师,甚至是来自所有类型背景的工程师,人们可能不会决绝告诉你他们需要你写注释,但是当你这样做的时候,很多人也会表达感谢。
- 像书的章节一样注释
注释部分或片断可以允许人们跳到与他们最相关的部分。即使较短的代码,也可能存在一点长的代码片段,快速的找出它们能加快效率。
- 文件注释
我们也可能在文件头部书写固定格式的注释,如注明作者、协议、更新时间等信息。
- 文档类注释
有时会约定 API、类、函数等使用文档类注释。
如何写好注释
注释规约
注: 这是个人习惯的规约,仅供参考。
- 单行注释
【推荐】声明变量位于声明之后(太长或者针对代码块的可以放置在上面)。
vm._vnode = null; // the root of the child tree
注释行的上方需要有一个空行(除非注释行上方是一个块的顶部),以增加可读性:
// bad
// the root of the child tree
vm._vnode = null;
// v-once cached trees
vm._staticTrees = null;
// good
// the root of the child tree
vm._vnode = null;
// v-once cached trees
vm._staticTrees = null;
// good
// 注释行上面是一个块的顶部时不需要空行
function setVNode(node) {
// the root of the child tree
vm._vnode = node;
}
- 多行注释 【推荐】多行注释使用 /** ... */,而不是多行的 //
// bad
// Convert an input value to a number for persistence.
// If the conversion fails, return original string.
function toNumber (val) {
...
}
// good
/**
* Convert an input value to a number for persistence.
* If the conversion fails, return original string.
*/
function toNumber (val) {
...
}
- 【强制】注释内容和注释符之间需要有一个空格,以增加可读性。eslint:
spaced-comment
// bad
//the root of the child tree
vm._vnode = null;
// good
// the root of the child tree
vm._vnode = null;
// bad
/**
*Convert an input value to a number for persistence.
*If the conversion fails, return original string.
*/
function toNumber (val) {
...
}
// good
/**
* Convert an input value to a number for persistence.
* If the conversion fails, return original string.
*/
function toNumber (val) {
...
}
- 使用文档类注释,如函数、类、文件、事件等,使用 jsdoc 规范
vs code中可以使用插件korofileheader、jsdoc来自动添加。
/**
* 设置bridge信息
* @param {*} bridge
*/
function setBridge(bridge) {
this.bridge=bridge;
}
工具
Eslint
ESLint 是当下最流行的 JS 代码检查工具,ESLint 中有一些注释相关的规则,用户可选择开启。
- valid-jsdoc
- require-jsdoc
- no-warning-comments
- capitalized-comments
- line-comment-position
- lines-around-comment
- multiline-comment-style
- no-inline-comments
- spaced-comment
Sonar
Sonar 是一个代码持续集成平台,它可以对代码进行静态扫描,得到项目的注释率数据。
注释率反应了注释行占总代码行的比例,注释率太低不好,但也不能盲目追求高注释率。