引言
代码规范是软件开发领域经久不衰的话题,几乎所有工程师在开发过程中都会遇到,并或多或少会思考过这一问题。随着前端应用的大型化和复杂化,越来越多的前端工程师和团队开始重视 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 代码规范,问题就会变得较为复杂。如果直接利用现有的开源配置方案,可能会使工作事倍功半。
技术场景更加广泛:对于大型团队,其开发场景一般不会局限在传统 Web 领域内,往往还会涉及 Node.js、React Native、小程序、桌面应用(例如 Electron)等更广泛的技术场景。
在团队层面,随着人员的增加和组织结构的复杂化:
- 人员风格差异性更大、沟通协调成本更高。
- 方案宣导更难触达,难以保证规范执行的落实。
- 执行状况和效果难以统计和分析。
因为存在诸多差异,我们在设计具体方案时,需要考虑和解决更多问题,以保证规范的落实。针对上述分析,我们梳理了以下需要解决的问题:
- 如何制定统一的代码规范和对应的 ESLint 配置?
- 场景支撑:如何实现对场景差异的支持?如何保证不同场景间一致部分(例如 JavaScript 基础语法)的规范一致性?
- 技术选型支撑:如何在支撑不同技术选型的前提下,保证基础规则(例如缩进)的一致性?
- 可维护性:具体到规则配置上,能否提升可复用性?在方案升级迭代时成本是否可控?
- 如何保证代码规范的执行?
- 人员的增加和组织结构的复杂化,会导致基于管理的执行把控失效,这种情况应该如何保证代码规范的执行质量?
- 如何降低应用成本?
- 在工程数量增加、工程方案离散化的情况,降低方案的接入、升级和执行成本能节约大量的人力,同时也有利于方案落地推进。
- 如何及时了解规范应用状况和效果?
解决方案
为了能在团队内实现 JavaScript 代码规范的统一,在分析和思考团队规模化应用存在的问题后,我们设计了一套完整的技术解决方案。该方案包括多场景统一的 ESLint 规则配置、代码集成交付检查、自动化接入工具、执行状况监测分析等四个模块。通过各个模块协调配合,共同解决上文提出的问题,在降低维护成本、提升执行效率的同时,也保障了代码规范的统一。
整体方案的设计如下图所示:
- 多场景统一的 JavaScript 规范:该模块是整个方案的核心,借助 ESLint 的特性,通过分层分类的结构设计,在保证基础规则一致性的同时,实现了对不同场景、技术选型的支撑。
- 代码集成交付检查:该模块是方案落地执行的保障,将代码静态检查集成到持续交付工作流中。具体设计实现上,在保证交付质量的同时,也通过定制集成检查工具降低了开发者的应用执行成本。
- 自动化接入和升级方案:通过命令行工具提供“一键”接入/升级能力,同时集成到团队脚手架中,大大降低了工程接入和维护的成本。
- 执行状况监测分析:通过对工具运行和代码集成交付检查过程进行埋点、检查结果收集和分析,了解方案的应用状态和效果。
方案实现
上文中提出的问题,通过各模块的协调配合能够得到有效地解决,但具体到各个模块的实现,仍然需要进一步深入思考,以设计出更加合理的实现方案。本章将对方案的四个核心模块进行详细介绍。
通用 ESLint 配置方案
这一模块主要借助 ESLint 的基础特性,采用分层分类的结构设计,提供多场景、多技术方案的通用配置方案,并使方案具备易维护、易扩展的特性。
ESLint 特性简介
在进行 ESLint 配置方案设计前,我们先看一下 ESLint 的一些特点。
1. 插件化
下图简单地描述了 ESLint 的工作过程:
ESLint 的能力更像一个引擎,通过提供的基础检测能力和模式约束,推动代码检测流程的运转。原始代码经过解析器的解析,在管道中逐一经过所有规则的检查,最终检测出所有不符合规范的代码,并输出为报告。借助插件化的设计,不但可以对所有的规则进行独立的控制,还可以定制和引入新的规则。ESLint 本身并未和解析器强绑定,我们可以使用不同的解析器进行原始代码解析,例如可以使用 babel-eslint 支持更新版本、不同阶段的 ES 语法,支持 JSX 等特殊语法,甚至可以借助 @typescript-eslint/parser 支持 TypeScript 语言的检查。
2. 配置能力全面、可层叠、可共享
ESLint 提供了全面、灵活的配置能力,可以对解析器、规则、环境、全局变量等进行配置;可以快速引入另一份配置,和当前配置层叠组合为新的配置;还可以将配置好的规则集发布为 npm 包,在工程内快速应用。
3. 社区生态较为成熟
开源社区中基于 ESLint 的项目非常多,既有针对各种场景、框架的插件,也有各种 ESLint 规则配置方案,基本可以涵盖前端开发的所有场景。
规范配置方案设计
基于 ESLint 的插件化、可层叠配置特性,以及面向各种场景、框架的开源方案,我们设计了如下图所示的 ESLint 配置架构:
该配置架构采用了分层、分类的结构,其中:
- 基础层:制定统一的基础语法和格式规范,提供通用的代码风格和语法规则配置,例如缩进、尾逗号等等。
- 框架支撑层(可选):提供对通用的一些技术场景、框架的支持,包括 Node.js、React、Vue、React Native 等;这一层借助开源社区的各种插件进行配置,并对各种框架的规则都进行了一定的调整。
- TypeScript 层(可选):这一层借助 typescript-eslint,提供对 TypeScript 的支持。
- 适配层(可选):提供对特殊场景的定制化支持,例如 MRN(美团内部的 React Native 定制化方案)、配合 prettier 使用、或者某些团队的特殊规则诉求。
具体的实际项目中,可以灵活的选择各层级、各类型的搭配,获得和项目匹配的 ESLint 规则集。例如,对于使用 TypeScript 语言的 React 项目,可以将基础层、框架层的 React 分支、以及 TypeScript 支撑层的 React 分支层叠到一起,最终形成适用于该项目的 ESLint 配置。如果项目不再使用 TypeScript 语言,只需要将 ts-react 这一层去掉即可。
基于上述方案最终形成了如下图所示的 ESLint 配置集:
考虑到维护、升级和应用成本,我们最终选择将所有配置放到一个 npm 包中,而不是每种类型分别设置。仍以使用 TypeScript 语言的 React 项目为例,只需在工程中进行如下配置:
这种通过分层、分类的结构设计,还有利于后期的维护:
- 对基础层的修改,只需修改一处即会全局生效。
- 对非基础层某一部分的调整不会产生关联性的影响。
- 如需扩展对某一类型的支持,只需关注这一类型的特殊规则配置。
众所周知,TypeScript 类型的项目使用 TSLint 进行代码检查,也是一种简单、便捷的方案。但在本方案中我们依旧选择了:eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin 的组合方案。主要有以下几点原因:
- ESLint 的规则配置更加详细全面,覆盖更加广泛。
- 采用了分层分类的架构,能够保证即使框架或语言不同,也能在基本语法、风格层面保持规则的一致性,这样有利于团队内不同技术选型项目的风格统一。
- @typescript-eslint 方案持续迭代,问题响应非常迅速,对 TSLint 相关的规则基本提供了对等的实现。
根据最新消息,TypeScript在 2019 路线图 中明确表明后续对 Lint 工具的支持和建设会以对 ESLint 进行适配的方式为主。
代码集成检查
基于团队对工程化基础设施的建设,将代码规范静态检查与开发工作流集成,保证代码规范的落实。
通常而言,工程接入 ESLint 后,可以在开发的同时借助编辑器集成的 ESLint 检查提示能力(例如 VSCode 的 ESLint 插件),实时发现和修改不符合规范的语法错误和风格问题。但这仍不能避免因一些主观因素或疏漏造成的规范执行不到位,所以我们考虑在开发工作流的特定节点自动执行代码静态检查,阻断不合规范代码的提交或交付。
集成静态检查的开发工作流节点有很多,我们主要参考以下两种方案:
- 代码提交检查:在代码 Commit 时,通过 githook 触发 ESLint 检查。其优点在于能实时响应开发者的动作,给出反馈,快速定位和修复问题;缺陷在于开发者可以主动跳过检查。
- 代码交付检查:在代码交付(借助 CI 系统的交付流程功能)时,在代码检测平台中对代码进行 ESLint 检查,检测不通过则阻断交付。其优点在于能够强制执行,可在线追踪检测报告;缺陷在于离开发者的开发环境太“远”,开发者响应处理成本较高。
如果将两者进行结合,可能会事半功倍,效果如下图所示:
常用的代码提交检查方法一般是 husky 与 lint-staged 结合,在代码 Commit 时,通过 githook 触发对 git 暂存区文件的检查。但考虑到团队现有工程数量庞大、存在大量行数较多的文件,虽然 lint-staged 策略能够降低部分成本,但仍稍显不足。为此,我们对该方法进行优化,定制了本地代码提交检查工具 precommit-eslint,其核心特点是:
- 将增量检查执行到代码行这一粒度,支持 Warn 和 Error 两个检查级别。
- 只需将工具安装为工程的依赖,无需任何配置。
- 减少了 pre-commit hook 中植入脚本的侵入性。
- 进行了执行状况埋点和采集。
使用效果如下图所示:
在美团,我们使用自主开发的 CI 系统,并在独立部署的 Sonar 系统上定制化实现了相应规则,基本可以满足诉求,这里就不再赘述。对于独立的团队,基于 ESLint 提供的工具,可以很容易的实现使用 Node 快速搭建一个代码检测服务或平台,大家有兴趣不妨一试。
自动化接入工具
这个模块主要通过 CLI 工具提供方案自动化接入的能力,降低工程接入和升级的成本。如果不借助自动化工具,在工程中接入上述方案还是有一定的工作量和复杂度的,大致步骤如下:
1. 安装 Eslint。
2. 根据项目类型安装对应的 ESLint 规则配置 npm 包。
3. 根据项目类型安装相关的插件、解析器等。
4. 根据项目类型配置 .eslintrc 文件。
5. 安装代码提交检查工具。
6. 配置 package.json。
7. 测试及修复问题。
在这个过程中,特别需要注意依赖的版本问题:依赖之间的版本兼容性,例如 typescript 和 @typescript-eslint/parser 之间的兼容性;依赖对规则的支持性,比如某个版本的插件中去除了对某个规则的支持,但规则配置中仍然配置了该规则,此时配置就会失效。对于 ESLint 不熟悉的开发者而言,在配置的过程中都会或多或少遇到兼容性、解析异常、规则无效等问题,反复排查和定位问题会浪费大量的精力。
因此,在设计开发自动化接入工具时,我们综合考虑了操作步骤、依赖版本、规则集和工程方案的兼容性,设计了如下的工作流程:
该工具流程简单,不管什么开发场景和框架选型,繁琐的接入流程都可以简化为一条命令,需要配合工程方案升级时同样如此。如下图所示,执行该命令后项目就完成了 ESLint 的接入,使用统一的规则规范编码,同时在代码提交时自动进行增量检查:
埋点与统计分析
统计分析的主要目的是掌握方案应用执行状况和效果,理论上应当支持工程和大盘两个视角,如下图所示:
执行情况分析其实并不复杂,核心是信息采集和分析。在本方案中,信息采集通过 precommit-eslint 工具实现:在 git commit 触发本地代码检查后,脚本会把检查结果(包括检查是否通过、错误或警告信息的数量级别等)上报;信息的统计分析借助日志上报分析平台实现,美团使用的是 CAT 平台(如果团队或公司没有专门的平台,可以在上文提到的代码检测服务平台中实现这部分功能)。为了便于数据的聚合分析,我们将一次代码提交检查中出现的问题数量进行了分级:
- 检查通过:检查无代码规范错误。
- 错误 1 级:检查出代码规范错误数量小于 10 个。
- 错误 2 级:检查出代码规范错误数量在 10 - 100 个之间。
- 错误 3 级:检查出代码规范错误数量在 100 - 1000 个之间。
- 错误 4 级:检查出代码规范错误数量大于 1000 个。
比如下图中,2019年3月第一周的代码提交检查结果统计(综合采样率 0.2),很明显,所有检查失败的提交中,错误数量在 10 个以内的占比最大,修复成本不高。
1. 提交检查异常分布(仅筛选检查未通过信息)
2. 提交检查警告信息分析
除此之外,还可以对单一工程,在更细的时间粒度上去观察提交检查的执行情况。
效果质量主要分析工程质量的变化:一方面可以通过代码检查执行通过率变化趋势、检查结果分布去看持续的生产流程中,代码质量是否有所提升;另一方面,由于代码检查采用增量模式,需要对工程代码进行整体分析,得到工程整体的不规范代码占比及变化趋势,从而从工程维度分析判断质量效果(涉及到权限相关问题,目前团队中未采用工程分析的方法)。具体的分析会在方案应用效果中一并进行介绍。
方案应用
除了上述整体方案外,为保证开发者使用更方便,我们还进行了一些配套工作:
- 持续维护升级:以每月一版的方式持续迭代升级,解决应用中的问题、规则争议,以及支持新的规则或方案。
- 工程化集成:整套方案可以无缝接入到各个团队的脚手架工具中,自动成为团队的默认方案,在工程初始化阶段即可完成接入。
- 官网建设:提供详细的使用文档,包括规则信息、接入方法,并且对每个版本提供规则、环境依赖、changeLog 等详细说明。
- 常见使用问题:更新维护FAQ,帮助后续接入者快速查找并解决问题。
目前,该套方案已经接入美团外卖、餐饮平台、闪购、榛果、金融等多个团队,基于埋点统计分析统计到的应用情况如下:
- 截止到2019年2月底,该方案已接入超过 200 个前端工程。
- 集成检查(增量)每天执行接近 1000 次。
- 集成检查(增量)平均每天检查出错误约 20000-25000 处。
- 集成检查代码质量:平均通过率为 75.562%,错误 1 级的比率为 15.644%,在所有未通过检查中占比 64.015%。
同时,我们持续统计上述数据的变化趋势,跟踪代码质量提升效果,以2018年12月到2019年3月的数据为例(截止2019年3月第一周,以周为时间统计尺度):
从图中可以看出,最近三个月检查通过率整体呈上升趋势,但 2019 年 1 月的第 2 周和第 3 周集成检查通过率有明显下降。分析项目信息发现,在 2019 年 1 月的第 2 周有一批新项目接入,代码检查规范检查出几十个错误。但整体来看,目前集成检查通过率基本稳定在 75% - 80%,从变化趋势看仍有上升空间。
方案实施之后,我们做了一个用户调研,发现整体方案的运营正在发挥着正向的作用。一方面,在一定程度上提升了多人协作的效率,无论是共同维护一个工程还是在多个工程间切换,避免了代码风格不一致带来的可读性成本和格式化风险;另一方面,会帮助大家发现和避免一些简单的语法错误。
规划和思考
该方案已经稳定应用,除了现有功能,我们还在思考是否可以更进一步的优化,提供更丰富的能力。由此规划了一些仍未落地的方向:
- 扩展支持 HTML 和 CSS 的代码风格检查:虽然近几年前端框架、组件库的建设一定程度上减少了业务开发中(尤其是中后台业务)对 HTML 和 CSS 的需求,但是规范 HTML 和 CSS 的代码风格仍是必要的。基于此,可以用同样的思路将 HTML 和 CSS 的代码静态检查方案集成到当前的方案中,不再局限于 JavaScript(或 TypeScript)。
- 进一步的封装:目前整体方案会将所有依赖和配置暴露在工程内,如果将其完全封装在一个工具内会更便于应用,但难点在于兼顾灵活性、对编辑器的支持等问题。
- 增加工程维度的代码质量趋势分析:目前代码检查策略是增量检查,可以对接入的工程定期全量检查,基于时间线分析工程的代码质量变化趋势。
- 进一步深入分析检查结果和统计数据,发现一些潜在问题,为推动开发质量提升提供辅助,如:
- 统计开发者在工程中关闭或调整的规则,分析占比较高的规则被关闭的原因,进而调整规则或推动规则的执行。
- 统计分布检查出错误的规则分布,梳理出最常出问题的代码规则,发布对应的最佳实践或手册。
以上是美团外卖团队在 ESLint 方案规模化应用过程中的一些实践,欢迎大家提出建议,一起沟通交流。
作者简介
宋鹏,美团外卖事业部终端研发工程师。
About 团队
美团外卖事业部终端团队,负责的多个终端和平台直接连接亿万用户、数百万商家和几万名运营与销售,目标是在保障业务高稳定、高可用的同时,持续提升用户体验和研发效率。
在用户方向上,构建了全链路的高可用体系,客户端、Web前端和小程序等多终端的可用性在99%左右;跨多端高复用的局部动态化框架在首页、广告、营销等核心路径的落地,提升了30%的研发效率;
在商家方向上,从提高进程优先级、VoIP Push拉活、doze等方面进行保活定制,并提供了Shark、短链和Push等多条触达通道,订单到达率提升至98%以上;
在运营方向上,通过标准化研发流程、建设组件库和Node服务以及前端应用的管理与页面配置等提升10%的研发效率。
招聘信息
美团外卖事业部终端团队是由一群活力四射,对技术饱含热情,平均年龄不超过26岁的人共同组成。在这里,你可以看到大家对技术的追求,对产品的雕琢,对团队的认同,一切都是那么自然;在这里,你可以做一个纯粹的FE,写写JS,打磨一下CSS;也可以做一个精致的猪猪女孩(男孩),优雅的调试 Android 和 iOS 代码。当然,你绝对可以做一个霸道总裁,肆意的拥抱跨端方案,Hybrid,Flutter,React Native 等,都是你的新战场。我们正在持续努力成为一个面向未来编程的团队,而这里还缺一个你。欢迎志同道合的同学发送简历到:tech@meituan.com。
,