探索 JavaScript 代码简洁优化之道

发表时间: 2019-01-04 10:38

测试代码质量的唯一方式:别人看你代码时说 f * k 的次数。

代码质量与其整洁度成正比。干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好基础。

code

本文并不是代码风格指南,而是关于代码的可读性、复用性、扩展性探讨。

我们将从几个方面展开讨论:

  1. 变量
  2. 函数
  3. 对象和数据结构
  4. SOLID
  5. 测试
  6. 异步
  7. 错误处理
  8. 代码风格
  9. 注释

变量

用有意义且常用的单词命名变量

用有意义且常用的单词命名变量

保持统一

可能同一个项目对于获取用户信息,会有三个不一样的命名。应该保持统一,如果你不知道该如何取名,可以去 codelf (https://unbug.github.io/codelf/) 搜索,看别人是怎么取名的。

命名应该保持统一

每个常量都该命名

可以用 buddy.js 或者 ESLint 检测代码中未命名的常量。

每个常量都该命名

可描述

通过一个变量生成了一个新变量,也需要为这个新变量命名,也就是说每个变量当你看到他第一眼你就知道他是干什么的。

可描述

直接了当

直接了当

避免无意义的前缀

如果创建了一个对象 car,就没有必要把它的颜色命名为 carColor。

避免无意义的前缀

使用默认值

使用默认值

函数

参数越少越好

如果参数超过两个,使用 ES2015/ES6 的解构语法,不用考虑参数的顺序。

如果参数超过两个,使用 ES2015/ES6 的解构语法,不用考虑参数的顺序

只做一件事情

这是一条在软件工程领域流传久远的规则。严格遵守这条规则会让你的代码可读性更好,也更容易重构。如果违反这个规则,那么代码会很难被测试或者重用。

只做一件事情

顾名思义

看函数名就应该知道它是干啥的。

顾名思义

对象设置默认属性

对象设置默认属性

不要传 flag 参数

通过 flag 的 true 或 false,来判断执行逻辑,违反了一个函数干一件事的原则。

不要传 flag 参数

避免副作用(第一部分)

函数接收一个值返回一个新值,除此之外的行为我们都称之为副作用,比如修改全局变量、对文件进行 IO 操作等。

当函数确实需要副作用时,比如对文件进行 IO 操作时,请不要用多个函数/类进行文件操作,有且仅用一个函数/类来处理。也就是说副作用需要在唯一的地方处理。

副作用的三大天坑:随意修改可变数据类型、随意分享没有数据结构的状态、没有在统一地方处理副作用。

避免副作用(第一部分)

避免副作用(第二部分)

在 JavaScript 中,基本类型通过赋值传递,对象和数组通过引用传递。以引用传递为例:

假如我们写一个购物车,通过 addItemToCart() 方法添加商品到购物车,修改 购物车数组。此时调用 purchase() 方法购买,由于引用传递,获取的 购物车数组 正好是最新的数据。

看起来没问题对不对?

如果当用户点击购买时,网络出现故障, purchase() 方法一直在重复调用,与此同时用户又添加了新的商品,这时网络又恢复了。那么 purchase() 方法获取到 购物车数组 就是错误的。

为了避免这种问题,我们需要在每次新增商品时,克隆 购物车数组 并返回新的数组。

避免副作用(第二部分)

不要写全局方法

在 JavaScript 中,永远不要污染全局,会在生产环境中产生难以预料的 bug。举个例子,比如你在 Array.prototype 上新增一个 diff 方法来判断两个数组的不同。而你同事也打算做类似的事情,不过他的 diff 方法是用来判断两个数组首位元素的不同。很明显你们方法会产生冲突,遇到这类问题我们可以用 ES2015/ES6 的语法来对 Array 进行扩展。

不要写全局方法

比起命令式我更喜欢函数式编程

函数式变编程可以让代码的逻辑更清晰更优雅,方便测试。

函数式变编程可以让代码的逻辑更清晰更优雅,方便测试

封装条件语句

封装条件语句

尽量别用“非”条件句

尽量别用“非”条件句

避免使用条件语句

Q:不用条件语句写代码是不可能的。

A:绝大多数场景可以用多态替代。

Q:用多态可行,但为什么就不能用条件语句了呢?

A:为了让代码更简洁易读,如果你的函数中出现了条件判断,那么说明你的函数不止干了一件事情,违反了函数单一原则。

避免使用条件语句

避免类型检查(第一部分)

JavaScript 是无类型的,意味着你可以传任意类型参数,这种自由度很容易让人困扰,不自觉的就会去检查类型。仔细想想是你真的需要检查类型还是你的 API 设计有问题?

避免类型检查(第一部分)

避免类型检查(第二部分)

如果你需要做静态类型检查,比如字符串、整数等,推荐使用 TypeScript,不然你的代码会变得又臭又长。

避免类型检查(第二部分)

不要过度优化

现代浏览器已经在底层做了很多优化,过去的很多优化方案都是无效的,会浪费你的时间,想知道现代浏览器优化了哪些内容,请点这里。

不要过度优化

删除弃用代码

很多时候有些代码已经没有用了,但担心以后会用,舍不得删。

如果你忘了这件事,这些代码就永远存在那里了。

放心删吧,你可以在代码库历史版本中找他它。

删除弃用代码

对象和数据结构

用 get、set 方法操作数据

这样做可以带来很多好处,比如在操作数据时打日志,方便跟踪错误;在 set 的时候很容易对数据进行校验...

用 get、set 方法操作数据

使用私有变量

可以用闭包来创建私有变量

可以用闭包来创建私有变量

使用 class

在 ES2015/ES6 之前,没有类的语法,只能用构造函数的方式模拟类,可读性非常差。

bad

good

链式调用

这种模式相当有用,可以在很多库中发现它的身影,比如 jQuery、Lodash 等。它让你的代码简洁优雅。实现起来也非常简单,在类的方法最后返回 this 可以了。

链式调用

链式调用

不要滥用继承

很多时候继承被滥用,导致可读性很差,要搞清楚两个类之间的关系,继承表达的一个属于关系,而不是包含关系,比如 Human->Animal vs. User->UserDetails

不要滥用继承

SOLID

SOLID 是几个单词首字母组合而来,分别表示 单一功能原则、开闭原则、里氏替换原则、接口隔离原则以及依赖反转原则。

单一功能原则

如果一个类干的事情太多太杂,会导致后期很难维护。我们应该厘清职责,各司其职减少相互之间依赖。

单一功能原则

测试

随着项目变得越来越庞大,时间线拉长,有的老代码可能半年都没碰过,如果此时上线,你有信心这部分代码能正常工作吗?测试的覆盖率和你的信心是成正比的。

PS: 如果你发现你的代码很难被测试,那么你应该优化你的代码了。

单一化

单一化

异步

不再使用回调

不会有人愿意去看嵌套回调的代码,用 Promises 替代回调吧。

用 Promises 替代回调

Async/Await 比起 Promises 更简洁

Async/Await 比起 Promises 更简洁

错误处理

不要忽略抛异常

错误处理

不要忘了在 Promises 抛异常

不要忘了在 Promises 抛异常

代码风格

代码风格是主观的,争论哪种好哪种不好是在浪费生命。市面上有很多自动处理代码风格的工具,选一个喜欢就行了,我们来讨论几个非自动处理的部分。

常量大写

常量大写

注释

只有业务逻辑需要注释

代码注释不是越多越好。

注释

删掉注释的代码

git 存在的意义就是保存你的旧代码,所以注释的代码赶紧删掉吧。

删掉注释的代码

不要记日记

记住你有 git!,git log 可以帮你干这事。

不要记日记

注释不需要高亮

注释高亮,并不能起到提示的作用,反而会干扰你阅读代码。

注释不需要高亮

作者 : 缪宇