JavaScript 代码整洁之道

Ba la la la ~ 读者朋友们,你们好啊,又到了冷锋时间,话不多说,发车!


一、变量

使用有准确意义的变量名

不好:

var yyyymmdstr = moment().format('YYYY/MM/DD');

好:

var yearMonthDay = moment().format('YYYY/MM/DD');
在变量的值不会改变时使用 ES6 的常量

在不好的示例中,变量可以被改变。如果你申明一个常量,它会在整个程序中始终保持不变。
不好:

var FIRST_US_PRESIDENT = "George Washington";

好:

const FIRST_US_PRESIDENT = "George Washington";
对同一类型的变量使用相同的词汇

不好:

getUserInfo();getClientData();getCustomerRecord();

好:

getUser();
使用可检索的名称

我们阅读的代码永远比写的折。写可读性强、易于检索的的代码非常重要。在程序中使用明确意义的变量名会难以理解,对读者造成伤害。所以,把名称定义成可检索的。
不好:

// 见鬼,525600 是个啥?for (var i = 0; i < 525600; i++) { runCronJob();}

好:

// 用 var 申明为大写的全局变量var MINUTES_IN_A_YEAR = 525600;for (var i = 0; i < MINUTES_IN_A_YEAR; i++) { runCronJob();}

使用解释性的变量

不好:

const cityStateRegex = /^(.+)[,\s]+(.+?)\s*(\d{5})?$/;saveCityState(cityStateRegex.match(cityStateRegex)[1], cityStateRegex.match(cityStateRegex)[2]);

好:

const cityStateRegex = /^(.+)[,\s]+(.+?)\s*(\d{5})?$/;const match = cityStateRegex.match(cityStateRegex)const city = match[1];const state = match[2];saveCityState(city, state);

避免暗示 显式优于隐式。

不好:

var locations = ['Austin', 'New York', 'San Francisco'];locations.forEach((l) => { doStuff(); doSomeOtherStuff(); ... ... ... // 等等,l 又是什么? dispatch(l);});

好:

var locations = ['Austin', 'New York', 'San Francisco'];locations.forEach((location) => { doStuff(); doSomeOtherStuff(); ... ... ... dispatch(location);});

不要添加没必要的上下文

如果你的类名称/对象名称已经说明了它们是什么,不要在(属性)变量名里重复。
不好:

var Car = { carMake: 'Honda', carModel: 'Accord', carColor: 'Blue'};function paintCar(car) { car.carColor = 'Red';}

好:

var Car = { make: 'Honda', model: 'Accord', color: 'Blue'};function paintCar(car) { car.color = 'Red';}

短路语法比条件语句更清晰

不好:

function createMicrobrewery(name) { var breweryName; if (name) { breweryName = name; } else { breweryName = 'Hipster Brew Co.'; }}

好:

function createMicrobrewery(name) { var breweryName = name || 'Hipster Brew Co.'}

二、函数

函数参数 (理论上少于等于2个)

限制函数参数的数量极为重要,它会让你更容易测试函数。超过3个参数会导致组合膨胀,以致于你必须根据不同的参数对大量不同的情况进行测试。
理想情况下是没有参数。有一个或者两个参数也还好,三个就应该避免了。多于那个数量就应该考虑合并。通常情况下,如果你有多于2个参数,你的函数会尝试做太多事情。如果不是这样,大多数时候可以使用一个高阶对象作为参数使用。
既然 JavaScript 允许我们在运行时随意创建对象,而不需要预先定义样板,那么你在需要很多参数的时候就可以使用一个对象来处理。
不好:

function createMenu(title, body, buttonText, cancellable) { ...}

好:

var menuConfig = { title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true}function createMenu(menuConfig) { ...}

一个函数只做一件事

目前这是软件工程中最重要的原则。如果函数做了较多的事情,它就难以组合、测试和推测。当你让函数只做一件事情的时候,它们就很容易重构,而且代码读起来也会清晰得多。你只需要遵循本指南的这一条,就能领先于其他很多开发者。
不好:

function emailClients(clients) { clients.forEach(client => { let clientRecord = database.lookup(client); if (clientRecord.isActive()) { email(client); } });}

好:

function emailClients(clients) { clients.forEach(client => { emailClientIfNeeded(client); });}function emailClientIfNeeded(client) { if (isClientActive(client)) { email(client); }}function isClientActive(client) { let clientRecord = database.lookup(client); return clientRecord.isActive();}

函数名称要说明它做的事

不好:

function dateAdd(date, month) { // ...}let date = new Date();// 很难从函数名了解到加了什么dateAdd(date, 1);

好:

function dateAddMonth(date, month) { // ...}let date = new Date();dateAddMonth(date, 1);

函数应该只抽象一个层次

如果你有多个层次的抽象,那么你的函数通常做了太多事情,此时应该拆分函数使其易于复用和易于测试。
不好:

function parseBetterJSAlternative(code) { let REGEXES = [ // ... ]; let statements = code.split(' '); let tokens; REGEXES.forEach((REGEX) => { statements.forEach((statement) => { // ... }) }); let ast; tokens.forEach((token) => { // lex... }); ast.forEach((node) => { // parse... })}

好:

function tokenize(code) { let REGEXES = [ // ... ]; let statements = code.split(' '); let tokens; REGEXES.forEach((REGEX) => { statements.forEach((statement) => { // ... }) }); return tokens;}function lexer(tokens) { let ast; tokens.forEach((token) => { // lex... }); return ast;}function parseBetterJSAlternative(code) { let tokens = tokenize(code); let ast = lexer(tokens); ast.forEach((node) => { // parse... })}

删除重复代码

任何情况下,都不要有重复的代码。没有任何原因,它很可能是阻碍你成为专业开发者的最糟糕的一件事。重复代码意味着你要修改某些逻辑的时候要修改不止一个地方的代码。JavaScript 是弱类型语句,所以它很容易写通用性强的函数。记得利用这一点!
不好:

function showDeveloperList(developers) { developers.forEach(developers => { var expectedSalary = developer.calculateExpectedSalary(); var experience = developer.getExperience(); var githubLink = developer.getGithubLink(); var data = { expectedSalary: expectedSalary, experience: experience, githubLink: githubLink }; render(data); });}function showManagerList(managers) { managers.forEach(manager => { var expectedSalary = manager.calculateExpectedSalary(); var experience = manager.getExperience(); var portfolio = manager.getMBAProjects(); var data = { expectedSalary: expectedSalary, experience: experience, portfolio: portfolio }; render(data); });}

好:

function showList(employees) { employees.forEach(employee => { var expectedSalary = employee.calculateExpectedSalary(); var experience = employee.getExperience(); var portfolio; if (employee.type === 'manager') { portfolio = employee.getMBAProjects(); } else { portfolio = employee.getGithubLink(); } var data = { expectedSalary: expectedSalary, experience: experience, portfolio: portfolio }; render(data); });}

使用默认参数代替短路表达式

不好:

function writeForumComment(subject, body) { subject = subject || 'No Subject'; body = body || 'No text';}

好:

function writeForumComment(subject = 'No subject', body = 'No text') { ...}

用 Object.assign 设置默认对象

不好:

var menuConfig = { title: null, body: 'Bar', buttonText: null, cancellable: true}function createMenu(config) { config.title = config.title || 'Foo' config.body = config.body || 'Bar' config.buttonText = config.buttonText || 'Baz' config.cancellable = config.cancellable === undefined ? config.cancellable : true;}createMenu(menuConfig);

好:

var menuConfig = { title: 'Order', // User did not include 'body' key buttonText: 'Send', cancellable: true}function createMenu(config) { config = Object.assign({ title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true }, config); // 现在 config 等于: {title: "Foo", body: "Bar", buttonText: "Baz", cancellable: true} // ...}createMenu(menuConfig);

不要把标记用作函数参数

标记告诉你的用户这个函数做的事情不止一件。但是函数应该只做一件事。如果你的函数中会根据某个布尔参数产生不同的分支,那就拆分这个函数。
不好:

function createFile(name, temp) { if (temp) { fs.create('./temp/' + name); } else { fs.create(name); }}

好:

function createTempFile(name) { fs.create('./temp/' + name);}function createFile(name) { fs.create(name);}

避免副作用

如果一个函数不是获取一个输入的值并返回其它值,它就有可能产生副作用。这些副作用可能是写入文件、修改一些全局变量,或者意外地把你所有钱转给一个陌生人。
现在你确实需要在程序中有副作用。像前面提到的那样,你可能需要写入文件。现在你需要做的事情是搞清楚在哪里集中完成这件事情。不要使用几个函数或类来完成写入某个特定文件的工作。采用一个,就一个服务来完成。
关键点是避免觉的陷阱,比如在没有结构的对象间共享状态,使用可以被任意修改的易变的数据类型,没有集中处理发生的副作用等。如果你能做到,你就能比其他大多数程序员更愉快。
不好:

// 下面的函数使用了全局变量。// 如果有另一个函数在使用 name,现在可能会因为 name 变成了数组而不能正常运行。var name = 'Ryan McDermott';function splitIntoFirstAndLastName() { name = name.split(' ');}splitIntoFirstAndLastName();console.log(name); // ['Ryan', 'McDermott'];

好:

function splitIntoFirstAndLastName(name) { return name.split(' ');}var name = 'Ryan McDermott'var newName = splitIntoFirstAndLastName(name);console.log(name); // 'Ryan McDermott';console.log(newName); // ['Ryan', 'McDermott'];

不要写入全局函数

JavaScript 中全局污染是一件糟糕的事情,因为它可能和另外库发生冲突,然而使用你 API 的用户却不会知道——直到他们在生产中遇到一个异常。来思考一个例子:你想扩展 JavaScript 的原生 Array,使之拥有一个 diff
方法,用来展示两数据之前的区别,这时你会怎么做?你可以给 Array.prototype
添加一个新的函数,但它可能会与其它想做同样事情的库发生冲突。如果那个库实现的 diff
只是比如数组中第一个元素和最后一个元素的异同会发生什么事情呢?这就是为什么最好是使用 ES6 的类语法从全局的 Array
派生一个类来做这件事。
不好:

Array.prototype.diff = function(comparisonArray) { var values = []; var hash = {}; for (var i of comparisonArray) { hash[i] = true; } for (var i of this) { if (!hash[i]) { values.push(i); } } return values;}

好:

class SuperArray extends Array { constructor(...args) { super(...args); } diff(comparisonArray) { var values = []; var hash = {}; for (var i of comparisonArray) { hash[i] = true; } for (var i of this) { if (!hash[i]) { values.push(i); } } return values; }}

喜欢上命令式编程之上的函数式编程

如果 Haskell 是 IPA 那么 JavaScript 就是 O'Douls。就是说,与 Haskell 不同,JavaScript 不是函数式编程语言,不过它仍然有一点函数式的意味。函数式语言更整洁也更容易测试,所以你最好能喜欢上这种编程风格。
不好:

const programmerOutput = [ { name: 'Uncle Bobby', linesOfCode: 500 }, { name: 'Suzie Q', linesOfCode: 1500 }, { name: 'Jimmy Gosling', linesOfCode: 150 }, { name: 'Gracie Hopper', linesOfCode: 1000 }];var totalOutput = 0;for (var i = 0; i < programmerOutput.length; i++) { totalOutput += programmerOutput[i].linesOfCode;}

好:

const programmerOutput = [ { name: 'Uncle Bobby', linesOfCode: 500 }, { name: 'Suzie Q', linesOfCode: 1500 }, { name: 'Jimmy Gosling', linesOfCode: 150 }, { name: 'Gracie Hopper', linesOfCode: 1000 }];var totalOutput = programmerOutput .map((programmer) => programmer.linesOfCode) .reduce((acc, linesOfCode) => acc + linesOfCode, 0);

封装条件

不好:

if (fsm.state === 'fetching' && isEmpty(listNode)) {  /// ...}

好:

function shouldShowSpinner(fsm, listNode) { return fsm.state === 'fetching' && isEmpty(listNode);}if (shouldShowSpinner(fsmInstance, listNodeInstance)) { // ...}

避免否定条件

不好:

function isDOMNodeNotPresent(node) { // ...}if (!isDOMNodeNotPresent(node)) { // ...}

好:

function isDOMNodePresent(node) { // ...}if (isDOMNodePresent(node)) { // ...}

避免条件

这似乎是个不可能完成的任务。大多数人第一次听到这个的时候会说,“没有 if
语句我该怎么办?”回答是在多数情况下都可以使用多态来实现相同的任务。第二个问题通常是,“那太好了,不过我为什么要这么做呢?”答案在于我们之前了解过整洁的概念:一个函数应该只做一件事情。如果你的类和函数有 if
语句,就意味着你的函数做了更多的事。记住,只做一件事。
不好:

class Airplane { //... getCruisingAltitude() { switch (this.type) { case '777': return getMaxAltitude() - getPassengerCount(); case 'Air Force One': return getMaxAltitude(); case 'Cessna': return getMaxAltitude() - getFuelExpenditure(); } }}

好:

class Airplane { //...}class Boeing777 extends Airplane { //... getCruisingAltitude() { return getMaxAltitude() - getPassengerCount(); }}class AirForceOne extends Airplane { //... getCruisingAltitude() { return getMaxAltitude(); }}class Cessna extends Airplane { //... getCruisingAltitude() { return getMaxAltitude() - getFuelExpenditure(); }}

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

JavaScript 是无类型的,也就是说函数可以获取任意类型的参数。有时候你会觉得这种自由是种折磨,因而会不由自主地在函数中使用类型检查。有很多种方法可以避免类型检查。首先要考虑的就是 API 的一致性。
不好:

function travelToTexas(vehicle) { if (vehicle instanceof Bicycle) { vehicle.peddle(this.currentLocation, new Location('texas')); } else if (vehicle instanceof Car) { vehicle.drive(this.currentLocation, new Location('texas')); }}

好:

function travelToTexas(vehicle) { vehicle.move(this.currentLocation, new Location('texas'));}

**避免类型检查(第2部分)

如果你在处理基本类型的数据,比如字符串,整数和数组,又不能使用多态,这时你会觉得需要使用类型检查,那么可以考虑 TypeScript。这是普通 JavaScript 的完美替代品,它在标准的 JavaScript 语法之上提供了静态类型。普通 JavaScript 手工检查类型的问题在于这样会写很多废话,而人为的“类型安全”并不能弥补损失的可读性。让你的 JavaScript 保持整洁,写很好的测试,并保持良好的代码审查。否则让 TypeScript (我说过,这是很好的替代品)来做所有事情。
不好:

function combine(val1, val2) { if (typeof val1 == "number" && typeof val2 == "number" || typeof val1 == "string" && typeof val2 == "string") { return val1 + val2; } else { throw new Error('Must be of type String or Number'); }}

好:

function combine(val1, val2) { return val1 + val2;}

不要过度优化

现在浏览器在运行时悄悄地做了很多优化工作。很多时候你的优化都是在浪费时间。这里有很好的资源 可以看看哪些优化比较缺乏。把它们作为目标,直到他们能固定下来的时候。
不好:

// 在旧浏览器中,每次循环的成本都比较高,因为每次都会重算 len。// 现在浏览器中,这已经被优化了。for (var i = 0, len = list.length; i < len; i++) { // ...}

好:

for (var i = 0; i < list.length; i++) {  // ...}
删除不用的代码

不用的代码和重复的代码一样糟糕。在代码库中保留无用的代码是毫无道理的事情。如果某段代码用不到,那就删掉它!如果你以后需要它,仍然可以从代码库的历史版本中找出来。
不好:

function oldRequestModule(url) { // ...}function newRequestModule(url) { // ...}var req = newRequestModule;inventoryTracker('apples', req, 'www.inventory-awesome.io');

好:
function newRequestModule(url) { // ...}var req = newRequestModule;inventoryTracker('apples', req, 'www.inventory-awesome.io');


以上为个人意见,如有雷同,纯属巧合,欢迎大家多提意见!Bey 了 个 Bey ~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,726评论 2 17
  • 概述 Robert C. Martin 在 《代码整洁之道》{:target="_blank"} 中提到的软件工程...
    Xindot阅读 696评论 0 1
  • 单例模式 适用场景:可能会在场景中使用到对象,但只有一个实例,加载时并不主动创建,需要时才创建 最常见的单例模式,...
    Obeing阅读 2,058评论 1 10
  • 不支持上传文件,所以就复制过来了。作者信息什么的都没删。对前端基本属于一窍不通,所以没有任何修改,反正用着没问题就...
    全栈在路上阅读 1,952评论 0 2