自动化代码规范工具

2020-02-16

1 技术选型

1.1 痛点

在软件研发过程中,bug 越早发现,成本越低。代码扫描和单元测试,就是在早期帮我们发现程序中问题的有效手段。代码扫描不仅能帮我们发现程序的漏洞,也能督促开发人员更规范优雅地写代码。 推动代码规范,提高代码质量,从源头把控软件产品质量,已经在研发体系形成共识。

在梳理公司前端应用时发现很多代码不规范的地方,包括简单的 js 问题以及代码格式问题,造成了代码可读性下降,可维护性降低,甚至对页面功能的实现和性能产生不好的影响。 例如线上出现的一个问题,其关键代码如下:

if (isValid) {
  function setAccount() {
    // code...
  }
}

在非函数的代码块内部声明函数,这在“strict”模式下是被禁止的,导致页面在低版本安卓和 IOS 系统下解析错误,无法正常渲染,影响客户使用业务。这一类的错误理应在开发阶段就得到问题提示,却遗漏到生产环境。

由于团队人数众多,合作开发,每个人风格各异,为了提高代码可读性、可维护性以及性能和兼容性,同时为了回避错误、小纠结和反模式,保证代码质量,便于团队协作,推行前端规范来对代码质量进行统一把控。主要针对的痛点如下:

1)代码规范落地难

部门曾采取过的方式有:

  1. 编写文档类代码规范,比如 word、wiki 等形式,组织培训宣传,召集开发者进行学习;
  2. 设置 code review 机制,合并代码时需要多人审查。

这两种方式,存在成本高、收益低、难度大等问题,代码规范审查容易慢慢流于形式,约束力逐渐降低。归根结底在于需要工具去强行保证代码必须经过开发规范的扫描。

2)低质量代码容易遗漏到线上

在实际开发过程中,开发人员可以很轻易地执行 git push 操作将本地代码推送到远程仓库。如果代码质量低下,就很容易对线上应用质量埋下隐患。在合并的时候再进行 code review,问题积压较多,时间不够充分,反馈链路太长。 最好的方式是,本地进行 commit 的时候,保证当前代码能够满足团队制定的开发规范,如果不通过,commit 则无法成功,这样可从源头保证代码质量。

3)代码格式难统一

各个开发成员的代码格式都有很大的不同,有人字符串喜欢用双引号,有人喜欢使用单引号等等。代码格式不统一,将会影响代码可读性,无疑也增加了团队内的沟通成本。仅靠开发者手动修改和开发时注意,效率不够高也容易有遗漏。需要有自动化工具来高效完成格式统一。

4)代码质量文化培养难

通过引入代码质量工具,在开发过程中能够时刻对自身代码质量进行约束,逐渐培养自身对代码质量有“洁癖”的开发观念,同时也会成为团队的质量文化。

1.2 技术栈选型

针对上述痛点,选择 eslint + stylelint + prettier + yorkie + lint-staged 这几个工具进行组合。以及搭配 DevOps 平台,对代码进行线下和线上双重检查。 要想防患于未然,防止将存在潜在问题的代码带到线上环境,最好的办法是在本地提交代码时就能够扫描出潜在的错误,并强制将其修改后才能提交,这样就能保证线上代码至少不会存在低级的程序错误。 其中 yorkie 就是通过 git hooks 实现强制在 pre-commit 阶段执行 lint 指令;lint-stage 则保证 git 暂存区的代码,即本次修改的代码可得到扫描检查;eslint 和 stylelint 则是实际负责代码规范校验的工具;最后由 prettier 自动统一代码风格。

当违反所配置的规则,严重等级为 error 时,就不能提交成功,必须将其修改为符合规范的方可提交。确保最终提交到线上的代码符合代码规范、风格统一。

DevOps 平台进行线上检查,还可以提供相关页面报告。

2 规则介绍

2.1 基础规则

eslint 和 stylelint 的推荐规则,能够报告一些常见的问题,这些规范是许多前辈大师经过项目的洗礼留下的宝贵财富,可以帮助我们在开发中少走很多的弯路。比如在第一节中提到的 function 声明问题,对应于推荐规则里的 no-inner-declarations;但是以前没有好的自动检测方案,需要依赖工程师进行 code review 或者手动执行扫描工具,容易有遗漏,也不够方便直观。

此外,为了处理好新旧规则之间的关系,也参考了目前公司各个项目使用的框架、语言语法情况,对少部分规则的内容和优先级进行了微调。

2.2 自定义规则

基础推荐规则仍然不够满足我们所有的代码规范需求,一些贴合项目需求的比如私调 api 问题。公共库能够减少代码重复率,统一接口方便后续扩展,但是当开发人员没有调用公共库,而是调用自己的 api 时,eslint 的现有规则是无法约束到的。之前只能通过 code review 方式进行约束,现在通过自定义 lint 规则,我们可以堵上这个缺口。

下图展示了应用的规则详情:

rules.png

2.3 自定义规则开发方法

如上介绍,除了有前辈大师经验沉淀的规则,我们还需要和项目具体实践相结合的自定义规则。

这里我们以“不允许用户直接调用$.ajax”为例,开发一个自定义规则。

首先,我们在项目中创建一个文件夹 rules,然后创建文件 no-$ajax.js,注意这里的文件名 no-$ajax 即作为该规则的名字,代码如下:

module.exports = {
  meta: {
    type: 'problem'
  },
  create(context) {
    return {
      MemberExpression(node) {
        if (node.object.name && node.object.name === '$') {
          if (node.property && node.property.name === 'ajax') {
            context.report({
              node: node,
              message: '禁止调用方法,请使用ajaxRun方法',
              data: {
                identifier: node.object.name + '.' + node.property.name
              }
            });
          }
        }
      }
    };
  }
};

这样我们就定义好了一个规则,在这个规则中:

  1. meta: 用于定义这个规则的一些元数据(metadata)比如示例中的 type,这里定义为 problem,还可以定义其他的一些属性,比如 message.
  2. create: 带有一个 context 参数,它提供了一些非常有用的方法来帮助我们实现规则,在遍历代码抽象语法树(AST)时可以调用。
  3. 选择器:用来过滤我们想要的语法树节点。比如我们这里禁止使用的方法 $.ajax 就是属于 MemberExpression。
  4. node: 遍历语法树时获取的每个节点,它有很多属性可供读取使用,比如这里的 property、name 等。
  5. context.report: 用来向用户报告错误信息。

写好自定义规则后如何执行?使用–rulesdir 参数指定刚刚的 rules 路径,并在 rc 文件里配置相应的规则及其问题等级(规则名即为刚刚创建的规则文件的名字)。

写一个违背这个规则的代码测试一下,运行脚本命令后,控制台输出:

D:\lint-demo\src\request\api.js
10:5 warning 禁止调用$.ajax方法 no-$ajax

可以看到我们自定义的规则已经生效了。

2.4 代码风格

代码风格主要由 prettier 进行自动化处理,无需手动修改。在编辑时,编辑器会作出提示,可使用编辑器能力自动处理、整个文件处理。在提交时也会进行批量处理。

配置为 prettierrc.js,其中配置及其意义如下:

module.exports = {
  printWidth: 80, // 超过最大值换行
  tabWidth: 2, // 缩进字节数
  useTabs: false, // 缩进不使用tab,使用空格
  semi: false, // 句尾不添加分号
  singleQuote: true, // 使用单引号代替双引号
  trailingComma: 'es5', // 在对象或数组最后一个元素后面是否加逗号(在ES5中加尾逗号)
  bracketSpacing: true, // 在对象,数组括号与文字之间加空格 "{ foo: bar }"
  jsxBracketSameLine: false, // 在jsx中把'>' 是否单独放一行
  arrowParens: 'always', // (x) => {} 箭头函数参数只有一个时要有小括号
  proseWrap: 'never', // markdown文本样式不折行
  htmlWhitespaceSensitivity: 'ignore', //html空白区域敏感度
  endOfLine: 'lf' // 结尾是\n
};

2.5 规则等级说明

规则分为 2 种等级,error 和 warn。

error 表示强制的,需要严格执行。这些规则会帮助开发者规避错误,要求大家学习并接受。如果没有遵循,将会阻止提交代码、终止自动化构建部署流程。

warn 表示建议的,这些规则能够在绝大多数工程中改善可读性和开发体验。如果没有遵循,代码还是能够照常运行,但是并不表明这些规则不重要。为了保持一致性以及可读性和开发体验的考虑,还是要求尽可能少且有合理理由的情况下才可例外。

2.6 处理规则扫描结果

扫描出的每一个问题都有详细的说明,包括文件路径、问题位置、规则描述和修改建议等。

在实际的使用中,如果大家发现配置的规则和问题等级不符合预期,可以:

  1. 发现某一个规则误报,请记录下来及时和架构组联系,将维护更新这个规则。
  2. 发现某一个规则本身合理,但觉得问题等级不太合理:

a) 首先建议不要轻易更改问题等级,等级的设定有其经验基础。

b) 若仍然强烈希望更改,可提交需求到架构组,将在讨论后决定是否进行统一更改。

3 具体实践

上文介绍了技术选型和规则设定,那么如何在日常开发中落地实践呢?下面将介绍具体使用方法。 开发者可在本地进行 lint,采取方式有编辑器配置 lint 插件、本地执行 lint 指令。当开发者将代码提交时,将自动运行 lint 脚本,对暂存区的代码进行扫描,扫描成功的才可顺利提交。

线上 lint 环节,可以搭配 sonarQube 平台等配置规则。

部门内采用架构如下所示: structure.png

3.1 本地 lint

  1. 约定团队开发采用 vscode 编辑器,并安装 eslint 及 stylelint 插件辅助开发;
  2. 安装 eslint + stylelint + prettier + yokie + lint-staged,并配置相应的配置文件。可将相应配置集成在脚手架中,更方便生成项目。

3.2 提交阶段

提交阶段自动执行的流程如下图所示: progress.png

3.4 逐步提高代码质量

获得代码分析结果后,我们推荐的改进建议是:

  1. 分优先级逐步改进,对于 error 级别的告警,需要第一时间进行确认和修改,因为这一般意味着相应的代码块存在着潜在的 bug,对于 warning 级别的告警,采用“小步慢跑”的策略,在后续的开发工作中一步步进行修正。
  2. 相对于遗留代码,新增代码要有更严格的要求。(可根据时间线查看问题数量)
  3. 使用编辑器 lint 插件辅助开发,以及本地 lint 指令进行快速检测,不让代码问题遗留到最后一步。
  4. 阅读问题的解释,不仅是改进,更是学习。
  5. 定期发送横向统计报告,各团队间加强交流,共同提高代码质量。

4 总结

本文介绍了前端代码规范推进过程中遇到的问题和挑战,以及相应的解决方案,最终通过搭建自动化扫描框架,提高效率,便于团队协作,保证代码质量。

个人 blog