1 技术选型
1.1 痛点
在软件研发过程中,bug 越早发现,成本越低。代码扫描和单元测试,就是在早期帮我们发现程序中问题的有效手段。代码扫描不仅能帮我们发现程序的漏洞,也能督促开发人员更规范优雅地写代码。 推动代码规范,提高代码质量,从源头把控软件产品质量,已经在研发体系形成共识。
在梳理公司前端应用时发现很多代码不规范的地方,包括简单的 js 问题以及代码格式问题,造成了代码可读性下降,可维护性降低,甚至对页面功能的实现和性能产生不好的影响。 例如线上出现的一个问题,其关键代码如下:
if (isValid) {
function setAccount() {
// code...
}
}
在非函数的代码块内部声明函数,这在“strict”模式下是被禁止的,导致页面在低版本安卓和 IOS 系统下解析错误,无法正常渲染,影响客户使用业务。这一类的错误理应在开发阶段就得到问题提示,却遗漏到生产环境。
由于团队人数众多,合作开发,每个人风格各异,为了提高代码可读性、可维护性以及性能和兼容性,同时为了回避错误、小纠结和反模式,保证代码质量,便于团队协作,推行前端规范来对代码质量进行统一把控。主要针对的痛点如下:
1)代码规范落地难
部门曾采取过的方式有:
- 编写文档类代码规范,比如 word、wiki 等形式,组织培训宣传,召集开发者进行学习;
- 设置 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 规则,我们可以堵上这个缺口。
下图展示了应用的规则详情:
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
}
});
}
}
}
};
}
};
这样我们就定义好了一个规则,在这个规则中:
- meta: 用于定义这个规则的一些元数据(metadata)比如示例中的 type,这里定义为 problem,还可以定义其他的一些属性,比如 message.
- create: 带有一个 context 参数,它提供了一些非常有用的方法来帮助我们实现规则,在遍历代码抽象语法树(AST)时可以调用。
- 选择器:用来过滤我们想要的语法树节点。比如我们这里禁止使用的方法 $.ajax 就是属于 MemberExpression。
- node: 遍历语法树时获取的每个节点,它有很多属性可供读取使用,比如这里的 property、name 等。
- 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 处理规则扫描结果
扫描出的每一个问题都有详细的说明,包括文件路径、问题位置、规则描述和修改建议等。
在实际的使用中,如果大家发现配置的规则和问题等级不符合预期,可以:
- 发现某一个规则误报,请记录下来及时和架构组联系,将维护更新这个规则。
- 发现某一个规则本身合理,但觉得问题等级不太合理:
a) 首先建议不要轻易更改问题等级,等级的设定有其经验基础。
b) 若仍然强烈希望更改,可提交需求到架构组,将在讨论后决定是否进行统一更改。
3 具体实践
上文介绍了技术选型和规则设定,那么如何在日常开发中落地实践呢?下面将介绍具体使用方法。 开发者可在本地进行 lint,采取方式有编辑器配置 lint 插件、本地执行 lint 指令。当开发者将代码提交时,将自动运行 lint 脚本,对暂存区的代码进行扫描,扫描成功的才可顺利提交。
线上 lint 环节,可以搭配 sonarQube 平台等配置规则。
部门内采用架构如下所示:
3.1 本地 lint
- 约定团队开发采用 vscode 编辑器,并安装 eslint 及 stylelint 插件辅助开发;
- 安装 eslint + stylelint + prettier + yokie + lint-staged,并配置相应的配置文件。可将相应配置集成在脚手架中,更方便生成项目。
3.2 提交阶段
提交阶段自动执行的流程如下图所示:
3.4 逐步提高代码质量
获得代码分析结果后,我们推荐的改进建议是:
- 分优先级逐步改进,对于 error 级别的告警,需要第一时间进行确认和修改,因为这一般意味着相应的代码块存在着潜在的 bug,对于 warning 级别的告警,采用“小步慢跑”的策略,在后续的开发工作中一步步进行修正。
- 相对于遗留代码,新增代码要有更严格的要求。(可根据时间线查看问题数量)
- 使用编辑器 lint 插件辅助开发,以及本地 lint 指令进行快速检测,不让代码问题遗留到最后一步。
- 阅读问题的解释,不仅是改进,更是学习。
- 定期发送横向统计报告,各团队间加强交流,共同提高代码质量。
4 总结
本文介绍了前端代码规范推进过程中遇到的问题和挑战,以及相应的解决方案,最终通过搭建自动化扫描框架,提高效率,便于团队协作,保证代码质量。