QCon 全球软件开发大会(北京站)门票 9 折倒计时 4 天,点击立减 ¥880 了解详情
写点什么

编写高质量可维护的代码:一目了然的注释

2021 年 5 月 04 日

编写高质量可维护的代码:一目了然的注释

前言


有一些人认为,好的代码是自我解释的。合适的命名和优秀的代码的确可以减轻开发人员阅读代码的工作量,对于不是特别复杂的代码可能确实可以做到自我解释。但并不是所有场景都可以做到这一点,我们一起来了解一下“注释”吧。


编程语言中对“注释”的解释


注释就是对代码的解释和说明。注释是开发人员在编写程序时,给一段代码的解释或提示,有助于提高程序代码的可读性。注释不会被计算机编译。


要不要加注释?为什么要加注释?


注释的存在就是为了方便自己的二次阅读和代码维护以及项目交接。可以更好的理解代码,有助于提高协作效率,加快开发进程。


试想,你添加了一段逻辑较为复杂的代码,几个月后再看,还能不能迅速看懂?你刚刚接手一个老项目,项目里基本没有注释且逻辑复杂,你能高效率的看懂代码和了解业务吗?


所以添加注释还是有一定必要滴。


基础篇


快捷键  windows:ctrl+/   mac: command+/


注释的分类


一、 HTML 中的注释

<div>  这是一行文字  <!-- 这是一行被注释的文字 --></div>
复制代码

二、CSS 中的注释

  • 在 .html 文件中

<style>  div {      /* color: #fff;  */   }</style>
复制代码
  • 在 .css 文件中

div { /* color: #fff;  */}
复制代码
  • 在 .less 或 .scss 文件中

div { /* color: #fff;*/  /* 多行注释*/ // font-size: 14px; // 单行注释 background: #000;}
复制代码

三、JS 中的注释

  • 用法

  • 可用于解释 JavaScript 代码,增强其可读性。

  • 也可以用于阻止代码执行。

  • 单行注释(行注释)—— 以 // 开头。任何位于 // 之后的文本都会被注释

// 定义一个空数组var ary = [];var ary2 = []; // 又定义一个空数组
复制代码
  • 多行注释(块注释)—— 以 /* 开头,以 */ 结尾。任何位于 /* 和 */ 之间的文本都会被注释

/* 这是多行注释 定义一个数组 */var ary = [];
复制代码
  • 用注释来阻止代码执行  —— 被注释的 JS 代码将不被执行

//alert("123")  // 执行时未弹出该信息alert("456")  // 执行时弹出该信息
复制代码
  • 函数注释

  • 一般以 /** 开头,以 */ 结尾。任何位于 /** 和 */ 之间的文本都会被注释

/** * 提交 * * @method onSubmit * @param {[Object]} 提交数据 * @return  {[Bollean]}  [返回是否提交成功 ] */const onSubmit = (params = {}) => {  const result = false;    if (params) {   result = true;  } return result;};
复制代码


四、特殊标记注释


  • TODO 在该注释处有功能代码待编写,待实现的功能在说明中会简略说明

  • FIXME 在该注释处代码需要修正,甚至代码是错误的,不能工作,需要修复,如何修正会在说明中简略说明

  • XXX 在该注释处代码虽然实现了功能,但是实现的方法有待商榷,希望将来能改进,要改进的地方会在说明中简略说明

  • NOTE 在该注释处说明代码如何工作

  • HACK 在该注释处编写得不好或格式错误,需要根据自己的需求去调整程序代码

  • BUG 在该注释处有 Bug


// TODO功能未完成,待完善// FIXME  待修复// XXX    实现方法待确认// NOTE   代码功能说明// HACK   此处写法有待优化// BUG    此处有 Bugconst arr = []
复制代码


Tips:

  • 为什么 // 注释可以在 .less 或 .scss 文件中使用,但是在 .html 和 .css 文件中不生效?

  • 在 MDN (https://developer.mozilla.org/zh-CN/docs/Web/CSS/Comments) 中关于 CSS 注释只有 /* */ 一种语法。但是在 LESS 和 SCSS 中支持注释的语法和 JS 中保持一致,有单行注释 // 和多行注释 /* */ 两种。单行注释编译之后不会被保留。

  • 单行注释为什么有时候写在代码上方,有时候写在代码后方?

  • 注释可以书写在代码中的任意位置。个人理解,一般写在代码上方的时候意为对后面一段代码的注释,而写在代码后方的时候意为对本行代码的注释。


注释写法规范


  • 文件注释

  • 位于文件头部,一般包含概要、作者、版本改动信息以及修改时间等内容

  /*   * 简述当前文件功能   * @author 作者名称   * @version 版本号 最近编辑时间   * @description 该版本改动信息   */
复制代码
  • 单行注释

  • 总是在 // 后留一个空格

  // 这是一行注释
复制代码
  • 多行注释

  • 总是保持星号纵向对齐(结束符前留一个空格)

  • 不要在开始符、结束符所在行写注释

  • 尽量使用单行注释代替多行注释

  • 注释函数时,推荐使用多行注释

  /*    这里有一行注释    这里有一行注释    这里有一行注释   */
复制代码
  • 函数注释

  • 其间每一行都以 * 开头,且与第一行第一个 * 对齐

  • 注释内容与 * 间留一个空格

  • 必须包含标签注释。例:

/*** 方法说明* @method 方法名* @for 所属类名* @param {参数类型} 参数名 参数说明* @return {返回值类型} 返回值说明*/
复制代码


注释常用标签用法


  • @type {typeName}

  • * 表示任何类型

  • ? 表示可以为 null

  • ! 表示不能为 null

  • [] 表示数组

/*** @type {number}*/var foo1;
/*** @type {*}* @desc 任何类型*/var foo2;
/*** @type {?string}* @desc string或者null*/var foo3;
复制代码


  • @param {} name - some description

  • 非必传参数需给参数名加上 []

  • 参数如有默认值需用 = 表示

  • 如果参数是 Object,可继续用 @param 对其属性进行详细说明

  • 若干个参数用 ... 表示


/** * @func * @desc 一个带参数的函数 * @param {string} a - 参数a * @param {number} b=1 - 参数b默认值为1 * @param {string} c=1 - 参数c有两种支持的取值  1—表示x  2—表示xx * @param {object} d - 参数d为一个对象 * @param {string} d.e - 参数d的e属性 * @param {object[]} g - 参数g为一个对象数组 * @param {string} g.h - 参数g数组中一项的h属性 * @param {string} [j] - 参数j是一个可选参数 */ function foo(a, b, c, d, g, j) {}
/** * @func * @desc 一个带若干参数的函数 * @param {...string} a - 参数a */function bar(a) {}
复制代码


拓展篇


IE 条件注释(IE5+)


IE 条件注释分为以下几种情况:


  • 只允许 IE 解释执行 <!--[if IE]><![endif]-->

  • 只允许 IE 特定版本解释执行 <!--[if IE 7]><![endif]-->

  • 只允许非 IE 特定版本执行注释 <!--[if !IE 7]><![endif]-->

  • 只允许高于或低于 IE 特定版本执行注释 <!--[if gt IE 7]><![endif]-->


<head>   <title>IE 条件注释</title>     <!-- 是 IE 时 -->    <!--[if IE]>         <link href="style.css" rel="stylesheet" type="text/css" />    <![endif]-->      <!-- 是 IE 7 时 -->    <!--[if IE 7]>       <link href="style.css" rel="stylesheet" type="text/css" />    <![endif]-->     <!-- 不是 IE 7 时 -->   <!--[if !IE 7]>        <link href="style.css" rel="stylesheet" type="text/css" />    <![endif]-->     <!-- 大于 IE 7 时 -->   <!--[if gt IE 7]>       <link href="style.css" rel="stylesheet" type="text/css" />    <![endif]-->    <!-- 小于 IE 7 时 -->    <!--[if lt IE 7]>       <link href="style.css" rel="stylesheet" type="text/css" />    <![endif]--></head>
复制代码


# (井号)注释 和  ''' (三引号)注释


  • # 一般出现在各种脚本配置文件中,用法与 JS 单行注释 // 基本相同。Python 中也常常用到

  • ''' 是 Python 中的多行注释语法,用两个 ''' 包含被注释的段落


# python 的单行注释一 print("I could have code like this.") # python 的单行注释二
# print("This won't run.") # 被注释的代码
''' 被三引号包裹的段落 可以随意折行 也可以注释代码 print("This won't run.")'''
复制代码


注释 “被执行” 了?


众所周知,注释的代码是不会被执行的。但是小编在查资料时看到了一段比较有意思的代码, Java 中的一行注释“被执行”了?


public class Test { public static void main(String[] args) {  String name = "赵大";  // \u000dname="钱二";  System.out.println(name); }}
复制代码


这段代码执行后的结果为钱二,也就是说在这段代码中,“被注释”的那行代码生效了!


这段代码的问题出在 \u000d 这串特殊字符上。\u000d 是一串 Unicode 字符,代表换行符。Java 编译器不仅会编译代码,还会解析 Unicode 字符。在上面这段代码把 \u000d 给解析了,后面的代码就到了下面一行,超出了被注释的范围(单行注释的注释范围仅在当前行),所以执行结果为 钱二 而非 赵大。(如下)


public class Test { public static void main(String[] args) {  String name = "赵大";  //  name="钱二";  System.out.println(name); }}
复制代码


所以本质上在代码执行的时候 name="钱二" 并没有被注释,而是被换了行(奇怪的知识增加了)。所以切记,注释确实是不会被执行的哦!


注释相关插件


在这里推荐几个个人认为比较好用的注释相关的 Vscode 插件,可在 setting.json 文件下自定义设置(可通过 '文件—首选项—设置',打开 Vscode 文件 settings.json )


  • koroFileHeader: https://marketplace.visualstudio.com/items?itemName=OBKoro1.korofileheader 在 Vscode 中用于生成文件头部注释和函数注释的插件

  • 文件头部添加注释

  • 在文件开头添加注释,记录文件信息/文件的传参/出参等

  • 支持用户高度自定义注释选项, 适配各种需求和注释。

  • 保存文件的时候,自动更新最后的编辑时间和编辑人

  • 快捷键:windowctrl+alt+imacctrl+cmd+ilinuxctrl+meta+i

  • 在光标处添加函数注释

  • 在光标处自动生成一个注释模板

  • 支持用户高度自定义注释选项

  • 快捷键:windowctrl+alt+tmacctrl+cmd+tlinuxctrl+meta+t

  • 快捷键不可用很可能是被占用了,参考这里 https://github.com/OBKoro1/koro1FileHeader/issues/5

  • 可自定义默认参数


  • Better Comments: https://marketplace.visualstudio.com/items?itemName=aaron-bond.better-comments 通过使用警报,信息,TODO 等进行注释来改善代码注释。使用此扩展,您将能够将注释分类为:

  • 快讯

  • 查询

  • 待办事项

  • 强调

  • 注释掉的代码也可以设置样式,以使代码不应该存在

  • 可自定义指定其他所需的注释样式


  • TODO Highlight: https://marketplace.visualstudio.com/items?itemName=wayou.vscode-todo-highlight 突出显示 TODO,FIXME 和任何关键字

  • 高亮内置关键字,可通过自定义设置覆盖外观

  • 也可自定义关键字


用事实说话


口说无凭,眼见为实。下面我们看下实际开发中的具体情况:


  • 没有注释

const noWarehousetemIds = beSelectSkucontainer.reduce((arr, itemId) => {    const res = Object.keys(selectRowskey[itemId]).every((skuId) => {      const sku = selectRowskey[itemId][skuId];      return !!sku.warehouseCode || lodashGet(warehouses, '[0].code');    });    if (!res) {      arr.push(itemId);    }    return arr;  }, []);  if (noWarehousetemIds.length > 0 || noStockItemIds.length > 0) {    const itemIds = Array.from(new Set([...noWarehousetemIds, ...noStockItemIds]));    const itemNames = itemIds.map(i => this.itemNameMap[i].itemName);    return Modal.warning({      title: '错误提示',      content: `“${itemNames.join(',')}”库存信息未完善,请完善库存信息`,    });  }
复制代码
  • 一般般的注释

// 遍历当前所有选中的sku,查找出没有库存的itemIdconst noStockItemIds = beSelectSkucontainer.reduce((arr, itemId) => {  const res = Object.keys(selectRowskey[itemId]).every((skuId) => {    const sku = selectRowskey[itemId][skuId];    return !!sku.stockQuantity;  });  if (!res) {    arr.push(itemId);  }  return arr;}, []);// 有一条sku的库存为空时进入校验if (noStockItemIds.length > 0) {  const itemNames = itemIds.map(i => this.itemNameMap[i].itemName);  return Modal.warning({    title: '错误提示',    content: `“${itemNames.join(',')}”库存信息未完善,请完善库存信息`,  });}
复制代码
  • 更好的注释

// 遍历当前所有选中的sku,查找出没有库存的itemIdconst noStockItemIds = beSelectSkucontainer.reduce((arr, itemId) => {    // selectRowskey是一个对象,以itemId为key,sku对象作为value,sku对象以skuId作为key,sku作为value,只有selectRowskey下所有itemId下的sku都有库存才算校验通过    /*        数据格式:        selectRowskey: {          12345678: { // itemId              123456: { // skuId              name: 'sku',              }          }        }      */    const res = Object.keys(selectRowskey[itemId]).every((skuId) => {        const sku = selectRowskey[itemId][skuId];        return !!sku.stockQuantity;    });    // 只要有一条sku没有库存时,就塞到arr中,返回给noStockItemIds数组    if (!res) {        arr.push(itemId);    }    return arr;}, []);// 有一条sku的库存为空时进入校验if (noStockItemIds.length > 0) {    // 根据id查找商品名称    const itemNames = itemIds.map(i => this.itemNameMap[i].itemName);    Modal.warning({        title: '错误提示',        content: `“${itemNames.join(',')}”库存信息未完善,请完善库存信息`,    });}
复制代码


看到上面这段代码可以很明显的体会到有没有注释以及注释写的清不清楚的重要性。若是写了注释但仍然看不懂,那还不如不写。


所以注释也不是随便写一写就可以的,要描述某段代码的功能,注明逻辑,让开发者可以”无脑“浏览。


之前在工作群中看到有人发过这样一张图(如下图),个人认为是一个很好的代码注释的范例:



结语


看到这里,对于注释的重要性各位已经有自己的认知。还有几点是我们写注释时需要注意的:


  • 注释内容要简洁、清楚明了。注释简述功能或实现逻辑即可,无需每行代码都添加注释

  • 代码若有修改,切记同步修改对应的注释。不要出现过期的注释,否则会起到反作用



头图:Unsplash

作者:亚格

原文:https://mp.weixin.qq.com/s/4Qq3-QFdyurL2ajbi4N0oQ

原文:编写高质量可维护的代码:一目了然的注释

来源:政采云前端团队 - 微信公众号 [ID:Zoo-Team]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021 年 5 月 04 日 00:005098

评论 9 条评论

发布
用户头像
作者你是初级生吗?多写几年代码吧,你还没有get到注释的本质。估计很多工作10+年的开发者,也没有get到
15 小时前
回复
用户头像
“汉学家称这个空白字元为「盘古之白」,因为它劈开了全形字和半形字之间的混沌。另有研究显示,打字的时候不喜欢在中文和英文之间加空格的人,感情路都走得很辛苦,有七成的比例会在 34 岁的时候跟自己不爱的人结婚,而其余三成的人最后只能把遗产留给自己的猫。毕竟爱情跟书写都需要适时地留白。”
2021 年 05 月 09 日 22:15
回复
用户头像
看这整篇没有逻辑段空行的代码,加上注释怎会“一目了然”?
2021 年 05 月 09 日 22:12
回复
用户头像
只有一目了然的代码,没有一目了然的注释。
2021 年 05 月 09 日 22:10
回复
用户头像
很基础且实用的文章,点赞
2021 年 05 月 08 日 09:31
回复
用户头像
建议别发大杂烩,感觉有点类似那种初学者博客的集合
2021 年 05 月 06 日 19:58
回复
用户头像
这标题真是好笑,一目了然的注释,请问好的代码需要注释吗?
2021 年 05 月 06 日 09:52
回复
这个作者在最初就说了,不是什么时候都可以「自注释」,作者讨论之前已排除自注释情况。
2021 年 05 月 06 日 19:59
回复
我以十三年的编程经验告诉你,注释如果做不到和代码逻辑同步联动,对于非自解释的代码最好的方式就是画逻辑图,这个文章中讲的内容太初级,而且感觉只属于前端范围,对于后端和不同语种场景根本不适用。
2021 年 05 月 07 日 14:24
回复
没有更多了
发现更多内容

这套JVM核心知识你要全都会,月薪还不过18K可以直接跳槽了

小Q

Java 学习 架构 面试 JVM

从红黑树的本质出发,彻底理解红黑树!

996小迁

Java 架构 面试 程序人生

多线程问的太深入不知道怎么回答,从volatile开始给你讲清楚

小Q

Java 学习 面试 volatile 多线程

亿级大表分库分表实战总结(万字干货,实战复盘)

云流

学习 编程 架构 计算机网络

断供,危机or契机?开源商业化or社区化?后疫情下的开源路这样走 | 大咖对话

易观大数据

#不吐不快# IT职场里的奇葩经历

InfoQ写作平台官方

职场搞笑 活动专区 奇葩的经历

程序员求助:腾讯面试题,64匹马8个跑道,多少轮选出最快的四匹

Java架构师迁哥

揭秘双11:前端技术体系

阿里云情报局

前端 前端进阶

【乘风破浪的开发者】丁一超:从AI实战营出发探索未知的AI世界

华为云开发者社区

华为 AI modelarts

《精通lambda表达式:Java多核编程》.pdf

田维常

Lambda

数字货币助力支付体系高效运行

CECBC区块链专委会

金融

报告显示国际区块链监管呈三大趋势

CECBC区块链专委会

区块链 货币 监管

云算力挖矿模式系统开发,云算力平台搭建

13530558032

“3+3”看华为云FusionInsight如何引领“数据新基建”持续发展

华为云开发者社区

数据库 新基建 华为云

那个小白还没搞懂内存溢出,只能用案例说给他听了

田维常

内存溢出

两句话给面试官讲清楚IOC

执墨

spring ioc 依赖倒置原则 springioc 控制反转

MySQL全面瓦解—子查询和组合查询

比伯

Java 编程 程序员 架构 计算机

SQL数据库:子查询和关联子查询

正向成长

SQL子查询 SQL关联查询

干货!直观地解释和可视化每个复杂的DataFrame操作

计算机与AI

Python pandas 数据处理

数字货币钱包开发费用,区块链钱包开发优势

13530558032

覆盖全网的阿里微服务架构有多牛:K8S+实战+笔记+项目教程

Java~~~

Java 程序员 微服务 Spring Cloud 阿里云 K8S

牛批!2w字的Java集合框架面试题精华集(2020最新版),赶紧收藏。

Java架构之路

Java 程序员 架构 面试 编程语言

Alibaba首发的《Java技术成长笔记》,渴望提升自己的程序员的必备宝典!

Java架构之路

Java 程序员 架构 面试 编程语言

SQL数据库:CASE表达式

正向成长

CASE表达式

React Fiber 是什么?

局外人

react.js 前端 React

数字货币交易所功能,场外OTC交易所开发公司

13530558032

USDT币支付系统开发搭建,区块链承兑商支付平台

13530558032

不要拿区块链做挡箭牌

CECBC区块链专委会

区块链

关于linux操作系统中的buff/cache

程序员架构进阶

Linux cache buffer

企业工作流设计原则及注意事项

力软.net/java开发平台

工作流

「Spring Boot 2.4 新特性」一键构建Docker镜像

AI乔治

Java Docker 架构

边缘计算隔离技术的挑战与实践

边缘计算隔离技术的挑战与实践

编写高质量可维护的代码:一目了然的注释-InfoQ