接上文,在 JS 优雅指南 - 1 中我们聊到了基础的 JavaScript 编程指南,包括如何可靠的声明变量、常量、函数、以及利用函数正确的构建逻辑,现在我们思考改善代码质量的第一步,如何命名。

在阅读代码之初,编码的风格与命名会比抽象方式、设计技巧更令我们印象深刻,很多时候也会为项目的风格定下基调。比如当我们阅读一些算法工程师留下的代码块,常会见到 单字母变量反复的声明不知所以的赋值与拷贝累赘的条件判断 等,这些代码大多的的确确是可以正常工作的,甚至在性能或储存空间上有一些优势,但这在工程,特别是大型工程中是不值得称道的。

1. 富有准确性的命名

事实上,你完全可以使用 doSomeThing 代替所有的函数,毕竟它们真的只是提供某些微不足道的功能,但当你有了多个甚至是成百上千的函数时,这是一个灾难。这是一个浅显易懂的道理。

例 1, 命名只需要有必要的词,除非有必要,否则不要堆砌

// bad
const theBook = {}
const _b = {}
const bookObj = {}
const newBook = {}

// good
const book = {}

例 2,可读的条件判断

// bad
if (username && username.includes('prefix-')) {
}

// bad
const prefix = username && username.includes('prefix-')

// bad
const availableName = username && username.includes('prefix-')

// good
const hasPrefixName = username && username.includes('prefix-')

例 3,可读的函数

当我们要从网络上获取用户信息时,getUser 就不是一个准确的表达,get 过于宽泛,从数据库或网络、以用户名或 ID 都有区别,现在我们可以先从命名上思考它们的区别:

// bad
const getUser = name => {}
// good
const fetchUsersByName = name => {}

// good
const findOneUserByID = id => {}

// good (any environment, any params)
const getUsers = (...params) => {}

例 4,准确的表达

属性可以避免不必要的描述,言简意赅:

// bad
const book = {
  bookname: '',
  length: '',
}

// good
const book = {
  title: '',
  pages: '',
}
func(book.title)

注意单复数:

// bad
const book = findBooks()

// good
const books = findBooks()

例 5,不必要的约定

通常在示例或无意义的遍历中,我们会把每一个回调函数的参数写作 item / value / v 等等,这在一些场景的确可以让阅读者忽略掉不必要的描述,专注于逻辑本身,但并非总是合适的,特别是我们需要表达状态时:

// bad
const titles = books.map(item => item.title).filter(item => item.length > 0)

// good
const titles = books.map(book => book.title).filter(title => title.length > 0)

2. 观察与思考

观察是一个模糊的界定,可以尝试让同事朋友阅读你的代码,问问他们的感受,哪些命名生硬,哪些词不达意,哪些是感同身受的。一个可学习的方式是在 github 上阅读代码,你也能注意到开发者们情绪的波动,这里真的需要这些定义吗、这里有必要写的这么短小吗、这是不是太 OC 了…… 试图理解开发者创作时想法是很自然的,这些情绪的波动能让你明白他们大约处于编程、人生、情感的什么状态,有助于你深刻的理解接口与设计。

在一个项目中见到 created 时,便可以知道这调用在 create 之后 (而非之前或之中),依次类推便可以有 destroyed 或其他函数,如果项目来自是富有经验的开发者,这些细节会帮助你在代码中极快的理解作者的构思。

又如在 暴雪的官方 API 文档 中可以见到 GetRewardSpell 接口,既然有 reward spell,我们可以推断自然是有 GetRewardXPGetRewardMoney 一类接口,而它们的参数自然也相差不多。真的是这样吗?不,完全不是,当你阅读几十分钟的文档后才会明白,这些接口创造之初或许有一些设计,但在各个版本的补丁与不断的重构后已面目全非,尽管命名的结构相差无几参数却大相径庭。在不断的阅读后将你开始注意到重新命名的接口、参数、返回值似乎正在朝某个方向改进,他们似乎在修补一些问题。

在阅读 暴雪界面源码 后我们更能注意到这些细节,一些界面功能被改变了,程序员们只能被迫去修改这些接口,同时想要与原有的保持的一些同步,有时也会衍生出一些新的接口,它们被置于一些结构体中,随着时间的推移可以推理,未来的一些接口也会被移入这些结构体 —— 这就是我们对于命名的观察与思考。我曾经见过有人说 "狗是人类的朋友,taobao 的文档连狗都不如",这就是他观察的结果了,虽然令人不那么开心。

3.巧妙与投机取巧

我们可以断定 巧妙 是优雅命名中重要的一部分,如在 rvm 与其他一些命令行工具中的 uninstall 就是 implode,这有点意思,是吧。但事实上我见到 绝大多数的巧妙不过是 "投机取巧",他们苦心孤诣的作品是一大段没有说明的八进制、二进制代码,一堆三元堆砌的单行逻辑,一些谐音名字,不合时宜地正则表达式等等,这并不漂亮,这是歧路。

例 1,自作聪明的谐音

// bad
const markdown2html = template => {}

// good
const markdownToHTML = template => {}

例 2,可以语义化的正则

代码并非越简短越好,多数场景下我们需要尽可能的避免长篇累牍,但必要时,可以使用命名和语义化的代码块来试图说明逻辑:

// bad
const isUser = /^name/.test(user.name) && /^http/.test(user.blog)

// good
const isUserName = user.name.startsWith('name')
const isUserBlog = user.blog.startsWith('http')
const isUser = isUserName && isUserBlog

例 3,优美

// bad
const hasDirOrCreateDir = path => {}

// good
const ensureDir = path => {}
// bad
const moveCursorToLastLine = () => {}

// good
const cursorUp = () => {}
const cursorDown = () => {}