阅读 11631

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

这是第 71 篇不掺水的原创,想获取更多原创好文,请搜索公众号关注我们吧~ 本文首发于政采云前端博客:编写高质量可维护的代码:一目了然的注释

前言

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

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

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

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

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

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

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

基础篇

快捷键 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    此处有 Bug
const arr = []
复制代码

Tips:

  • 为什么 // 注释可以在 .less 或 .scss 文件中使用,但是在 .html 和 .css 文件中不生效?
    • MDN 中关于 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) {}
复制代码

了解更多可查看 JSDoc

拓展篇

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 在vscode中用于生成文件头部注释和函数注释的插件
  • 文件头部添加注释
    • 在文件开头添加注释,记录文件信息/文件的传参/出参等
    • 支持用户高度自定义注释选项, 适配各种需求和注释。
    • 保存文件的时候,自动更新最后的编辑时间和编辑人
    • 快捷键:windowctrl+alt+imacctrl+cmd+ilinuxctrl+meta+i

  • 在光标处添加函数注释
    • 在光标处自动生成一个注释模板
    • 支持用户高度自定义注释选项
    • 快捷键:windowctrl+alt+tmacctrl+cmd+tlinuxctrl+meta+t
    • 快捷键不可用很可能是被占用了,参考这里
    • 可自定义默认参数

  • Better Comments 通过使用警报,信息,TODO 等进行注释来改善代码注释。使用此扩展,您将能够将注释分类为:
    • 快讯
    • 查询
    • 待办事项
    • 强调
    • 注释掉的代码也可以设置样式,以使代码不应该存在
    • 可自定义指定其他所需的注释样式

  • 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,查找出没有库存的itemId
const 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,查找出没有库存的itemId
const 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(',')}”库存信息未完善,请完善库存信息`,
    });
}
复制代码

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

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

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

结语

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

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

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

    有任何意见欢迎下方评论区留言讨论~

参考文献

为什么要写注释 js/javascript代码注释规范与示例 代码中的特殊注解 -- TODO、FIXME、XXX的作用 注释的作用, 以及如何写注释 你确定Java注释不会被执行吗?80%的人都不知道IE 浏览器条件注释详解 关于CSS中对IE条件注释的问题

推荐阅读

我的前端职业进阶之路

浅谈 React 中的 XSS 攻击

招贤纳士

政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 40 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推动并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。

如果你想改变一直被事折腾,希望开始能折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变既定的节奏,将会是“5 年工作时间 3 年工作经验”;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 ZooTeam@cai-inc.com