前端代码质量管理(一)

29,425 阅读11分钟

导语: 随着业务的增长和开发团队的成员快速增加,其中很多新人来自于五湖四海各大门派,在编码的风格和习惯中也出现各异。 通常在相互 codereview 时发现很多代码上的问题,久而久之代码出现了代码难以维护的问题,甚至还会出现低级错误。 因此,我尝试在前端代码质量的管控上做了些探索,也总结了一些经验分享给大家。
作者:郑振波

本文大纲介绍

  • 编码规范
  • 冗余文件与代码

1. 编码规范

在一些老项目里我们常会遇到以下问题:

编码规范
相信编码规范对于大家来说不算是陌生,如果在 9102 年再次聊起这个话题,恐怕耳朵会起茧,但编码规范从制定到落地是一个艰难的旅程,特别是对于不同成员的编码习惯,还有棘手的祖传代码。无论你是老司机还是新手不妨了解一下。

1.1 编码规范的制定

如何制定编码规范?这是一个永恒的话题,甚至出现过开发者按照自己的习惯和想法不停的去修改 eslint rules,没错,主观性非常强的开发者就会这么干,最后发现 eslint rules 成了一锅粥。

如果客观些也许会在这三个方面去思考问题:

  • 兼顾习惯:尽可能兼顾团队各成员习惯,人是有个性的,要兼顾似乎不大可能。
  • 规则从严:规则越严格越好,也绝不让他松散无约束。
  • 投个票:投票似乎是最民主的决定,但往往最具争议的 rules 会出现票数差异不大。

如果你能从以上三个方法中取得成果,那说明你是老板,这一切就会变得太简单了。但无论如何,每一个被扩展的 rule 都能找到具支持点和反驳点,在制定规范时同学们通常往往会在几个点去发表他的意见:

  • 习惯:“我一直都这样干,没问题”
  • 业界标准:“你去看某某大公司的开源代码吧”
  • 必要性:“行尾不加分号我从没见过跑不起来的JS”
  • 耗时:“按一次 tab 比按两次 tab 省时省力;行尾加分号简直浪费生命”

1.2 在编码质量和编码效率之间取得平衡

有没有办法可以解决这些争论?怎样取得平衡?开源社区各团队开源出的 eslint rules 可以说是遍地开花,prettier,eslint-config-standard,airbnb 等等,在编码规范选型时应该考虑以下几点:

  • 适合项目的技术选型:比如我们的团队技术栈使用了 React 、Node.js,ES6,那么就可以很清晰地知道怎么选择方向了。
  • 社区认可度高:相信社区争论程度不亚于团队成员,如果一个规范得到 stars 超过 1w,已经是很了不起的事情了。
  • 是否有插件支持:能够支持 ESLint,JSCS 等。

基于以上标准,我们选择了超过 8w + 由 airbnb 前端团队维护的 eslint rules。 标准制定后, 如何让团队成员快速适应起来?

  • 配置编辑器:如果使用 VSCode,可以配置 "eslint.autoFixOnSave": true,这样保存代码时会自动根据 eslint rules 去 fix 代码,同学们就很舒服了。
  • 编辑器插件:如果使用 VSCode 可以安装 Eslint 插件,这样可以实时提示不规范的代码。

  • 构建时提示: 如果使用 webpack ,可以使用 eslint-loader,再配合 eslint-friendly-formatter 可以给到开发者很好的提示。可以参考该文章 webpack引入eslint详解

这样 eslint 规范就初步落地了。

1.3 整改祖传代码

定制好规范并落地后,如何对于祖传代码怎么办?于是我们又遇到不同的声音:

  • 这代码不是我写的:”谁写谁来 fix 啊“
  • 我不敢改: “改出问题要背锅怎么说”
  • 下次再改:“赶时间发布呢,要不就下次再改”
  • eslint-disable:“disable 大法好,没有东西可以拦住我了”

以下是我们对祖传代码的整改之路: 首先要看看问题有多严重:

npx eslint src

eslint errors
25W+ Eslint 报错,这不知道要改到什么猴年马月了。

1.3.1 添加 .eslintignore 文件,排除第三方 js 文件

但经过排查发现,这里面大部分报错是来自于第三方库,但又不是 npm 包,这些文件往往是不能满足当前的编码规范的,并且有部分是经过代码压缩,更不应该走 eslint 检查。屏蔽掉第三方文件的检测后,剩下的 eslint errors 还有 2w+,错误归类:

eslint errors
可以发现错误类型最多的是换行符。

1.3.2 关于CRLF

在文本处理中,各操作系统也是有自己的一套标准:

Dos 和 windows 采用“回车+换行,CR/LF”表示下一行; UNIX/Linux 采用“换行符,LF”表示下一行; 苹果机(MAC OS 系统)则采用“回车符,CR”表示下一行。

所以,如果团队中还有 windows 玩家那么创建文件的换行符就是 CRLF,这也谈不上是什么很大的缺点,但在如果遇上了 Vim 或 Emacs 玩家打开文件就会看到这样的情况:

每行后面会带上个 ^M。为了维护代码世界的和平,通常需要统一转换为 LF 换行符:

  • 设置 IDE 换行符,如 VSCode:

  • VSCode 默认配置:文件 - 首选项 - 设置 -搜索:默认行尾字符。修改为 \n
  • EditorConfig: 如果你的项目中有 EditorConfig 那就更好了:

#Unix-style newlines with a newline ending every file [*] end_of_line = lf insert_final_newline = true

  • 配置 git config:可以在 git commit 时把代码统一转为 LF,那么可以了解一下 git config core.autocrlf ,也许你会见过这个配置,但不一定正确使用,其实他有3个值可配置:

ture: git pull 时会把 LF 结尾转为 CRLF false: git pull 时不做任何转换 input: git pull 时会把 CRLF 转换为 LF

那么这里应该使用的是 git conifg core.autocrlf input 以上任何一条都可以解决你的换行符问题。

1.3.3 自动修复

解决好这一切之后,可以尝试让 ESLint 自动修复一波了: npx eslint src --fix

自动修复后还有 800+ ESLint 报错,于是打开这些代码 review 后,发现有很多问题,以为规范人人都懂,正如那句电影台词**“听过很多道理,却依然过不好这一生”**。
这些都是很低级的错误,如果你还没有使用上 ESLint 那最好别太相信自己的代码。

1.3.4 定期整顿

800+ 的错误分布在各个页面,可以给团队成员每人分配几个页面修复并分批上线,这样大规模的修复最重要的是发布后的监控:

发布后监控

1.4 曾经以为 ESLint 是万能的

也许你会认为 ESLint 没有报错那就 O**K了,其实坑往往没那么容易被发现,就像下面这个例子:

这里提示绝对路径的引入应写在相对路径引入的前面,于是快速改他一波:
这里就有问题了,你看出来了吗?应该怎么改?

1.5 守住规则

我们经过制定规范,代码整改,接下来需要守住规则,否则一切徒劳

  • git hook:可以使用 pre-commit,husky 等工具来配置 git 钩子,配合上 npm script 可以做到在提交代码前执行 eslint 命令来检测变更文件是否符合规范,否则 exit 非零值来终止 git commit。但在前端使用 git hook 缺点也是有的,比如每次提交代码都要等待 eslint 一遍,导致开发体验大大下降;也会出现有同学自行删除 pre-commit hook 文件来绕过检测的情况;也可以通过添加 --no-verify 来关闭检测。
  • web hook:好处是使用服务端来做校验,无法在前端绕过,缺点是需要使用服务器资源。关于如何搭建 web hook,可以参考这篇文章

1.6 关于前端代码规范的其他思考

以上提到的是关于 JS 代码规范的内容,对于 CSS 也可以使用 stylelint 来做规范检测,但这些更多的是对代码的格式做规范,如果想把代码写好 ESLint 之类的并不是全能的,比如代码的整洁之道,这里列举了不少可以参考的范式,恰又是 ESLint 无法帮你 hold 住的点,所以 ESLint 并不是万能的。

2.冗余文件与代码

2.1 关于冗余文件

也许你会认为冗余文件是一个问题,但他不是一个很关键的痛点,如果你的团队追求极致,有非常的代码洁癖,那么冗余文件也应该重视起来。即使你是一名老鸟,也有可能产生冗余文件。

2.2 为什么会产生冗余文件

在代码的迭代过程中,往往容易忽略删除相应的文件,大概有两种场景: 1、删除 JS 代码中的 require,却忘记删除相应的文件(img/css/js/etc.)。 2、删除 CSS 中的 background ,忘记删除相应的图片文件。 删除代码很痛快,只要页面刷新一波没有报错就觉得 o**k 了,日积月累的冗余文件慢慢让整个仓库越来越大。

2.3 清理冗余文件

如果使用 webpack 构建,那就可以很方便地分析出整个项目的依赖关系。 第一步:在根目录跑命令,把文件的依赖关系导到 stats.json 中,这个步骤耗时略长,我在项目中跑了一次,1420 个文件耗时 35s。

webpack --json > ./stats.json

第二步:使用 glob 获取目录下的所有文件路径:

glob('!(node_modules)/**/*.*')

结合第一步生成的 stats.json 可以过滤出未被引用的文件。

2.4 冗余代码

2.4.1 关于冗余的 CSS

要分析项目中冗余的CSS,其实是比较困难的,主要原因有 3 个方面: 1、页面的元素或组件的嵌套:导致无法只从静态分析的层面上判断样式是否有作用于对应的元素上。 2、样式的全局作域:a.css 中声明一个样式,他可以作用于页面上的任何一个 dom,这样分析起来要遍历项目中所有的 dom 和 css,一个样式的声明需要检查所有的 dom,如果有N个样式声明那将会有非常大的计算量。 3、css 没有约束: 多个 css 文件的样式也可以作用于一个 dom 元素上,因为往往会用到开源的 ui 库,会根据设计的要求对相应的 dom 元素做样式覆盖,所以这种过于灵活的特性带来的不可控造成难以管理。

2.4.2 解决冗余的 CSS

CSS Modules 可以把 css 作用域收敛,可以显式声明哪个 class 样式作用于 dom 上,可以保证某个组件的样式不会影响到其他组件:

样式与 dom 形成了强依赖关系,这样就可以便于做静态分析,可以借助 eslint-plugin-css-modules
如果 scss 里面存在未使用到的 className,会得到提示:
如果在代码中使用了未定义的 className,也会有提示:

2.5.1 关于冗余的 JS

在代码迭代中,很有可能删除掉一些方法的使用,那么会在 Class 留下一些多余的方法,这些也是难以通过 eslint 来检测到的,因为没办法判断这些方法在实例化后,是否会在某个时刻被使用到。如果要找出这些冗余的方法,也需要从整个项目开始分析所有 JS 的依赖关系,那么计算量就会非常大,并且很耗时。

2.5.2 尝试解决冗余的 JS

目前没有什么好的办法来解决这个问题,或许可以通过两种方式来分析出来: 1、命名约定: 比如以下划线开头的方法是私有方法,那么就可以只针对本文件所有的私有方法是否被使用来做分析了。 2、注释标记:如果不喜欢下划线的方式,那么也可以考虑添加注释来标记。

2.6 冗余文件/代码 小结

1、删除代码要留心 2、分析冗余 3、合理运用工具 4、发布后监控