无论是开源项目的源码还是用各类脚手架创建项目的模版,我们一定会在配置中看到Lint工具,比如ESLint和Prettier。如果没有深入理解Prettier和ESLint的作用和区别,我们很容易陷入Lint工具麻烦且无用的错觉中。因此本篇文章将深入梳理它们的关系并且利用这两个工具作为切入点讲述一下代码规范以及它们对前端实现clean-code的重要性。
一般提起代码规范大家比较容易想到的是代码呈现出的格式(风格),对于前端来说比如:每行代码空2格/4格、要不要写分号、箭头函数单个参数要不要括号和一行最长多少字母等等格式问题,这个可以对应于Prettier - Code for Formatter。
Prettier官网的定义是:
opinionated 和 few options 就得出Prettier的特点是:用户自定义不多的 样式美化 工具。Prettier的英文名也是「更漂亮的」。也就是说它只是美化 代码样式 并不会检查 代码质量 。这就是它与ESLint最大的区别。
目前基本开源的大型前端项目都会用Prettier,比如:Ant Design Vue、elementUI、Vue3和React等等。因为他们每天需要处理来自世界各地的pull requests,如果没有Prettier应该天天都脑充血。。。。Prettier官网给到的 Why Prettier 理由:
Lint工具是一种软件质量保证工具,早在零几年微软就开始启用Lint来检查程序,而ESLint是属于Lint的一种 前端代码质量保证工具 。感兴趣的可以看看Linter进化史。现在的ESLint除了对代码质量的控制,比如:不使用var变量、 switch
必须要设置 default
等等之外,也有一些代码风格的控制,比如:是否使用分号和使用tab还是空格等等。
代码检查是一种静态分析来寻找有问题的代码,不依赖于具体的编码风格。对于大多数强类型语言,在编译时就会通过内置的检查工具对代码进行检查。但是JavaScript是一个动态的弱类型语言,没有编译过程,需要一个检查低质量代码的工具,这就是ESLint出现的初衷。ESLint的所有规则与Prettier相比都是可选择的 unopinionated 。你可以使用默认规则或者自定义规则,也可以选择将规则开启/关闭。
1.基本的使用方法,在项目中安装:
npm install eslint --save-dev
2.然后使用eslint创建一个配置文件
./node_modules/.bin/eslint --init
3.然后在配置文件 .eslintrc.js
中 extends:"eslint:recommended"
就可以使用ESLint官方推荐的规则,也是非常方便的。也可以自定义需要的规则:
{
"rules": {
"semi": ["error", "always"],
"quotes": ["error", "double"]
}
}
最后,就可以使用eslint去校验文件:
./node_modules/.bin/eslint 文件路径.js
在ESLint没有 --fix
来自动修复未通过规则问题的功能时,需要和Prettier结合使用才方便修改,现在也可以不需要Prettier。不过 --fix
只会修复 AST语法树 层面所感知的问题,比如:将 const b = data.a
转换为解构形式 const { b } = data
、修改代码的缩进、增加/减少分号等,其他的动态层面比如:虽然强制使用 ===
但是fix时并不会将 ==
转换为 ===
,因为这两个意图是有可能不一样的。
所以在平时项目开发中有两种方式去快速修复ESLint问题:
--fix
.eslintrc
,然后点击 allow
表示同意eslint作为默认的formate代码的配置。最后就可以愉快的使用 formate on save
或者 option+shift+F
自动格式化了。因为JS语言并没有编译过程,在没有使用ESLint之前并没有发现原来自己的代码有很多隐藏的bug。
下面举一个我在校招面试时被问到的一个🌰:
for in
和 for of
的区别是什么?
当时的回答:for in
遍历键名key;for of
遍历键值value。(当时觉得没毛病)
后来在项目中碰到了ESLint对 for in
的报错:The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype(for in里应该用if语言过滤掉原型上不需要的属性)。后来好好研究了一番 for in
和 for of
:
for in 更适合遍历对象而不是数组(遍历键名key)
hasOwnProperty
阻止遍历到原型)for of 更适合数组(遍历键值value)
不得不说,ESLint让我涨知识了!所以eslint的每一条都是有一定的考量的,有时候我们觉得不解的时候一般是我们欠缺了相关的知识。下面截图是一些eslint对JS的规则:
每一句看似简短的规则可能有很多知识,避免了低级错误也将代码向clean-code方向靠拢。这个规则的原理也是值得学习的东西。不过很多时候代码的质量是很主观的,ESLint并不能解决,所以这里推荐一个已经有53K star的针对JavaScript整理的clean-code-javascript,可以给我们带来ESLint无法限制的东西。
Clean-code-javascript是根据Robert C. Martin的《代码整洁之道》总结出适用于JavaScript的软件质量原则。
《代码整洁之道》的观念是:代码质量与其整洁度成正比。干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好基础。
但是书中的规则是作者作为多年编程人员的实践经验,不必严格遵守所有的规则,有时候一些规则太固化反而会不好,衍生出的clean-code-javascript也是一样,反正具体应该根据实际情况决定。
clean-code-javascript的规则一共分为九个章节,分别是:变量、函数、对象和数据结构、类、测试、异步、错误处理、格式化和注释。在每一个章节中又分为具体的规则。
举个🌰:函数这一章有一条规则是尽量采用函数式编程,因为函数式的编程具有更干净且便于测试的特点。
Bad:
const programmerOutput = [
{
name: 'Uncle Bobby',
linesOfCode: 500
}, {
name: 'Suzie Q',
linesOfCode: 1500
}
......
];
// 使用for循环计算programmerOutput对象里linesOfCode的总数
var totalOutput = 0;
for (var i = 0; i < programmerOutput.length; i++) {
totalOutput += programmerOutput[i].linesOfCode;
}
Good:
// 使用高阶函数计算总数
var totalOutput = programmerOutput
.map((programmer) => programmer.linesOfCode)
.reduce((acc, linesOfCode) => acc + linesOfCode, 0);
但是很多时候并不是上述这样一定要将循环转变为函数式编程的形式,我们以Vue的源码为例:
// pacakages/template-explorer
monaco.editor.setModelMarkers(
editor.getModel()!,
`@vue/compiler-dom`,
// 传入的参数使用链式调用直接计算
errors.filter(e => e.loc).map(formatError)
)
// packages/complier-core
const flagNames = Object.keys(PatchFlagNames) //使用Object.keys遍历对象
// 高阶函数进行链式调用
.map(Number)
.filter(n => n > 0 && patchFlag & n)
.map(n => PatchFlagNames[n])
.join(`, `)
相对于「for
循环」,上述的函数式编程方式其实在vue源码的占比很小很小。因为很多时候在循环的逻辑比较复杂的情况下,都会采用for
循环的方式去写,而函数式编程就用在可以一条语句完成需要的功能的时候。
当然这个是比较主观的,对于new coder来说还是需要good coder在不同场景下的code review来提高代码质量。这是视情况而定的。
但是对于for-in
迭代器,ESLint有两条规则no-iterator
和no-restricted-syntax
用来限制使用for-in
迭代器,推荐使用map/reduce/find/some/every/forEach...
等高阶函数完成数组操作,Object.keys/Object.values/ Object.entries
完成遍历对象生成所需数组的操作。
鲁道夫·克劳修斯说过:The entropy of the universe tends to a maximum(宇宙的熵趋向一个最大值)。
他提出的熵增定律是:在一个孤立系统里,如果没有外力做功,其总混乱度(熵)会不断增大,最后到达最混乱无序的状态。
所以从熵增原则来看代码的话:一个项目的代码(孤立的系统),如果没有保证代码质量的环节(外力做功),随着时间推移代码量越来越大的情况下,代码质量肯定是会变得越来越混乱甚至到最后无法正常去维护。一旦项目代码的熵超过了一定的界限,但是代码本身的产品还需要不停更新迭代时,就需要去重构代码,将其迁移到最开始整洁、简洁的结构中(降低熵值)。
为了不花大力气去重构代码,实际当中需要「外力做功」:比如上面提到的静态代码检查工具和个人编码的质量。
现目前采用比较多的流程举例:
第一步:Pre-commit阶段和lint工具结合(对应本文第一部分),例如:husky/pre-commit + lint-staged工具构建本地提交之前的检查。现目前React已经将lint-staged集成到了脚手架create-react-app中。
第二步:团队代码code review(对应本文第二部分)。GitHub 上许多流行项目采用 「PR(Pull Request) 」工作流的方式,一个 PR 至少经过三人次 review 通过才能合入。不过这一块就比较主观了,模式不固定需要根据实际情况去制定。
由于JavaScript语言没有编译的过程和一些历史因素导致在编写的代码不够稳定,所以在团队开发中需要用Prettier来统一多人的编码风格、用ESLint去做前期的静态检查保证代码的可读性和质量(降低熵值!)。如果在解决ESLint问题的时候搞明白规则背后的意义就更好了,这样不仅提高代码的编码质量而且也会使自己不断向前端的clean-code靠拢。
最后,如有错误烦请指正。