sundehui0 2019-11-03
代码规范是软件开发领域经久不衰的话题,几乎所有工程师在开发过程中都会遇到,并或多或少会思考过这一问题。随着前端应用的大型化和复杂化,越来越多的前端工程师和团队开始重视 JavaScript代码规范。得益于前端开源社区的繁盛,当下已经有几种较为成熟的 JavaScript 代码规范检查工具,包括 JSLint、JSHint、ESLint、FECS 等等。本文主要介绍目前较为通用的方案——ESLint,它是一款插件化的 JavaScript 代码静态检查工具,其核心是通过对代码解析得到的 AST(Abstract Syntax Tree,抽象语法树)进行模式匹配,定位不符合约定规范的代码。
ESLint 的使用并不复杂。依照 ESLint 的文档安装相关依赖,可以根据个人/团队的代码风格进行配置,即可通过命令行工具或借助编辑器集成的 ESLint 功能对工程代码进行静态检查,发现和修复不符合规范的代码。如果想降低配置成本,也可以直接使用开源配置方案,例如eslint-config-airbnb或eslint-config-standard。
对于独立开发者,或者执行力较强、技术场景较为单一的小型团队而言,直接使用 ESLint 及其生态提供的一些标准方案,可以用较低成本来实现 JavaScript 代码规范的落地。如果再搭配一些辅助工具(例如 husky 和 lint-staged),整个流程会更加顺畅。但对于数十人的大型前端团队来说,面向数百个前端工程,规模化地应用统一的 JavaScript 代码规范,问题就会变得较为复杂。如果直接利用现有的开源配置方案,可能会使工作事倍功半。
规模化应用统一的 ESLint 代码规范,会涌现各类问题,根源在于大型团队和小团队(或独立开发者)的差异性:
技术层面上:
在团队层面,随着人员的增加和组织结构的复杂化:
因为存在诸多差异,我们在设计具体方案时,需要考虑和解决更多问题,以保证规范的落实。针对上述分析,我们梳理了以下需要解决的问题:
如何制定统一的代码规范和对应的 ESLint 配置?
如何保证代码规范的执行?
如何降低应用成本?
为了能在团队内实现 JavaScript 代码规范的统一,在分析和思考团队规模化应用存在的问题后,我们设计了一套完整的技术解决方案。该方案包括多场景统一的 ESLint 规则配置、代码集成交付检查、自动化接入工具、执行状况监测分析等四个模块。通过各个模块协调配合,共同解决上文提出的问题,在降低维护成本、提升执行效率的同时,也保障了代码规范的统一。
上文中提出的问题,通过各模块的协调配合能够得到有效地解决,但具体到各个模块的实现,仍然需要进一步深入思考,以设计出更加合理的实现方案。本章将对方案的四个核心模块进行详细介绍。
这一模块主要借助 ESLint 的基础特性,采用分层分类的结构设计,提供多场景、多技术方案的通用配置方案,并使方案具备易维护、易扩展的特性。
在进行 ESLint 配置方案设计前,我们先看一下 ESLint 的一些特点。
1.插件化
下图简单地描述了 ESLint 的工作过程:
ESLint 的能力更像一个引擎,通过提供的基础检测能力和模式约束,推动代码检测流程的运转。原始代码经过解析器的解析,在管道中逐一经过所有规则的检查,最终检测出所有不符合规范的代码,并输出为报告。借助插件化的设计,不但可以对所有的规则进行独立的控制,还可以定制和引入新的规则。ESLint 本身并未和解析器强绑定,我们可以使用不同的解析器进行原始代码解析,例如可以使用 babel-eslint 支持更新版本、不同阶段的 ES 语法,支持 JSX 等特殊语法,甚至可以借助 @typescript-eslint/parser 支持 TypeScript 语言的检查。
2.配置能力全面、可层叠、可共享
ESLint 提供了全面、灵活的配置能力,可以对解析器、规则、环境、全局变量等进行配置;可以快速引入另一份配置,和当前配置层叠组合为新的配置;还可以将配置好的规则集发布为 npm 包,在工程内快速应用。
3.社区生态较为成熟
开源社区中基于 ESLint 的项目非常多,既有针对各种场景、框架的插件,也有各种 ESLint 规则配置方案,基本可以涵盖前端开发的所有场景。
基于 ESLint 的插件化、可层叠配置特性,以及面向各种场景、框架的开源方案,我们设计了如下图所示的 ESLint 配置架构:
该配置架构采用了分层、分类的结构,其中:
具体的实际项目中,可以灵活的选择各层级、各类型的搭配,获得和项目匹配的 ESLint 规则集。例如,对于使用 TypeScript 语言的 React 项目,可以将基础层、框架层的 React 分支、以及 TypeScript 支撑层的 React 分支层叠到一起,最终形成适用于该项目的 ESLint 配置。如果项目不再使用 TypeScript 语言,只需要将 ts-react 这一层去掉即可。
最终,形成了如下所示的 ESLint 配置集:
考虑到维护、升级和应用成本,我们最终选择将所有配置放到一个 npm 包中,而不是每种类型分别设置。仍以使用 TypeScript 语言的 React 项目为例,只需在工程中进行如下配置:
// 需要安装 typescript、eslint-plugin-react、@typescript-eslint 等插件 module.exports = { root: true, extends: [ // 因为基础层是必备的,所以框架层默认引入了对应的基础层,不需再单独引入 eslintrc.base.js 'eslint-config-xxx/eslintrc.react.js', 'eslint-config-xxx/eslintrc.typescript-react.js' ] }
这种通过分层、分类的结构设计,还有利于后期的维护:
众所周知,TypeScript 类型的项目使用 TSLint 进行代码检查,也是一种简单、便捷的方案。但在本方案中我们依旧选择了:eslint + @typescript-eslint/parser + @typescript-eslint/eslint-plugin 的组合方案。主要有以下几点原因:
根据最新消息,TypeScript在 2019 路线图 中明确表明后续对 Lint 工具的支持和建设会以对 ESLint 进行适配的方式为主。
基于团队对工程化基础设施的建设,将代码规范静态检查与开发工作流集成,保证代码规范的落实。
通常而言,工程接入 ESLint 后,可以在开发的同时借助编辑器集成的 ESLint 检查提示能力(例如 VSCode 的 ESLint 插件),实时发现和修改不符合规范的语法错误和风格问题。但这仍不能避免因一些主观因素或疏漏造成的规范执行不到位,所以我们考虑在开发工作流的特定节点自动执行代码静态检查,阻断不合规范代码的提交或交付。
集成静态检查的开发工作流节点有很多,我们主要参考以下两种方案:
如果将两者进行结合,可能会事半功倍,效果如下图所示:
常用的代码提交检查方法一般是 husky 与 lint-staged 结合,在代码 Commit 时,通过 githook 触发对 git 暂存区文件的检查。但考虑到团队现有工程数量庞大、存在大量行数较多的文件,虽然 lint-staged 策略能够降低部分成本,但仍稍显不足。为此,我们对该方法进行优化,定制了本地代码提交检查工具 precommit-eslint,其核心特点是:
使用效果如下图所示:
在美团,我们使用自主开发的 CI 系统,并在独立部署的 Sonar 系统上定制化实现了相应规则,基本可以满足诉求,这里就不再赘述。对于独立的团队,基于 ESLint 提供的工具,可以很容易的实现使用 Node 快速搭建一个代码检测服务或平台,大家有兴趣不妨一试。
这个模块主要通过 CLI 工具提供方案自动化接入的能力,降低工程接入和升级的成本。如果不借助自动化工具,在工程中接入上述方案还是有一定的工作量和复杂度的,大致步骤如下:
在这个过程中,特别需要注意依赖的版本问题:依赖之间的版本兼容性,例如 typescript 和 @typescript-eslint/parser 之间的兼容性;依赖对规则的支持性,比如某个版本的插件中去除了对某个规则的支持,但规则配置中仍然配置了该规则,此时配置就会失效。对于 ESLint 不熟悉的开发者而言,在配置的过程中都会或多或少遇到兼容性、解析异常、规则无效等问题,反复排查和定位问题会浪费大量的精力。
因此,在设计开发自动化接入工具时,我们综合考虑了操作步骤、依赖版本、规则集和工程方案的兼容性,设计了如下的工作流程:
该工具流程简单,不管什么开发场景和框架选型,繁琐的接入流程都可以简化为一条命令,需要配合工程方案升级时同样如此。如下图所示,执行该命令后项目就完成了 ESLint 的接入,使用统一的规则规范编码,同是在代码提交时自动进行增量检查:
统计分析的主要目的是掌握方案应用执行状况和效果,理论上应当支持工程和大盘两个视角,如下图所示:
执行情况分析其实并不复杂,核心是信息采集和分析。在本方案中,信息采集通过 precommit-eslint 工具实现:在 git commit 触发本地代码检查后,脚本会把检查结果(包括检查是否通过、错误或警告信息的数量级别等)上报;信息的统计分析借助日志上报分析平台实现,美团使用的是 CAT 平台(如果团队或公司没有专门的平台,可以在上文提到的代码检测服务平台中实现这部分功能)。为了便于数据的聚合分析,我们将一次代码提交检查中出现的问题数量进行了分级:
比如下图中,201903 第一周的代码提交检查结果统计(综合采样率 0.2),很明显,所有检查失败的提交中,错误数量在 10 个以内的占比最大,修复成本不高。
1.提交检查异常分布(仅筛选检查未通过信息)
2.提交检查警告信息分析
除此之外,还可以对单一工程,在更细的时间粒度上去观察提交检查的执行情况。
效果质量主要分析工程质量的变化:一方面可以通过代码检查执行通过率变化趋势、检查结果分布去看持续的生产流程中,代码质量是否有所提升;另一方面,由于代码检查采用增量模式,需要对工程代码进行整体分析,得到工程整体的不规范代码占比及变化趋势,从而从工程维度分析判断质量效果(涉及到权限相关问题,目前团队中未采用工程分析的方法)。具体的分析会在方案应用效果中一并进行介绍。
除了上述整体方案外,为保证开发者使用更方便,我们还进行了一些配套工作:
目前,该套方案已经接入美团外卖、餐饮平台、闪购、榛果、金融等多个团队,基于埋点统计分析,我们(基于2019年2月份最后一周统计数据分析,综合采样率0.2)得到了如下数据:
同时,我们持续统计上述数据的变化趋势,跟踪代码质量提升效果,以2018年12月到2019年3月的数据为例(截止2019年3月第一周,以周为时间统计尺度):
从图中可以看出,最近三个月检查通过率整体呈上升趋势,但 2019 年 1 月的第 2 周和第 3 周集成检查通过率有明显下降。分析项目信息发现,在 2019 年 1 月的第 2 周有一批新项目接入,代码检查规范检查出几十个错误。但整体来看,目前集成检查通过率基本稳定在 75% - 80%,从变化趋势看仍有上升空间。
方案实施之后,我们做了一个用户调研,发现整体方案的运营正在发挥着正向的作用。一方面,在一定程度上提升了多人协作的效率,无论是共同维护一个工程还是在多个工程间切换,避免了代码风格不一致带来的可读性成本和格式化风险;另一方面,会帮助大家发现和避免一些简单的语法错误。
该方案已经稳定应用,除了现有功能,我们还在思考是否可以更进一步的优化,提供更丰富的能力。由此规划了一些仍未落地的方向:
进一步深入分析检查结果和统计数据,发现一些潜在问题,为推动开发质量提升提供辅助,如:
以上是美团外卖团队在 ESLint 方案规模化应用过程中的一些实践,欢迎大家提出建议,一起沟通交流。
宋鹏,美团外卖事业部终端研发工程师。
美团外卖事业部终端团队,负责的多个终端和平台直接连接亿万用户、数百万商家和几万名运营与销售,目标是在保障业务高稳定、高可用的同时,持续提升用户体验和研发效率。
在用户方向上,构建了全链路的高可用体系,客户端、Web前端和小程序等多终端的可用性在99%左右;跨多端高复用的局部动态化框架在首页、广告、营销等核心路径的落地,提升了30%的研发效率;
在商家方向上,从提高进程优先级、VoIP Push拉活、doze等方面进行保活定制,并提供了Shark、短链和Push等多条触达通道,订单到达率提升至98%以上;
在运营方向上,通过标准化研发流程、建设组件库和Node服务以及前端应用的管理与页面配置等提升10%的研发效率。
团队有多个岗位正在招聘,欢迎加入我们,联系邮箱 [email protected] ,注明 “外卖终端团队”。