缩进是敌人
代码缩进越多,它就越复杂。简单的代码往往看起来更像一块砖或一个矩形。

编译器不关心缩进,缩进是为人类阅读准备的。
缩进表示这段代码被组合在一个块中,表示这段代码很特别,需要注意它与周围的其他代码的不同之处。当阅读缩进的代码时,需要记住一些上下文。缩进的级别越多,需要记住的上下文就越多,每个级别的缩进都会增加认知负担。每个级别的缩进都伴随着一些额外的东西。每个级别的缩进都表示增加的复杂性。
缩进让我们一眼就能看出我们的代码有多复杂,缩进不是真正的敌人。真正的敌人是复杂性。缩进是看门狗疯狂吠叫,让我们知道复杂性正在蔓延。
代码中总会有一些缩进。系统复杂性一直存在。如果没有,就不需要编写软件。但是有一些方法可以编写降低复杂性的代码,这样缩进也会随之减少。通过控制结构,我们的代码中引入的大部分复杂性就可以消失了。
一个例子
const myArray = [{id: 'a'}, {id: 'b'}, {id: 'c'}]; let ids = []; for (let i = 0; i < myArray.length; i++) { ids.push(myArray[i].id); } console.log(ids); //=> ['a', 'b', 'c']
但是使用 lodash 我们可以这样写:
import { map } from 'lodash'; const myArray = [{id: 'a'}, {id: 'b'}, {id: 'c'}]; const ids = map (myArray, 'id'); console.log(ids); //=> ['a', 'b', 'c']
似乎没什么大不了的。我们节省了一两行代码。但是
map()
函数比 for 循环更具表现力。在 for 循环版本中,我必须通读整个循环并自己识别并思考代码做了什么。
map()
函数用更少的空间传达更多的信息。这就是抽象之美。抽象有双重好处:
- 代码变得更具表现力。它向读者传达了有关我们正在努力实现的目标的更多信息。
- 通过隐藏实现细节来消除复杂性。
复杂性并没有消失,只是被隐藏了起来。这样做的好处是把复杂性转移给了 lodash,从而将 for 循环的复杂性变成了其他人的问题。
简单的代码出错更少,这为用户提供了更好的体验。减少错误可以让必须维护软件的开发团队更容易。即使是一个团队也是如此。当确实需要修复时,简单的代码也更容易修复。
没有循环
“……循环是一种命令式控制结构,很难重用,也很难插入到其他操作中。此外,它意味着代码不断变化或变异以响应新的迭代。” — 路易斯·阿滕西奥
while
循环 、for
循环或者for…of 循环
一般看起来是这样:let i = 0; const len = input.length; let output = []; while (i < len) { let item = input[i]; let newItem = doSomething(item); output.push(newItem); i = i + 1; } const len = input.length; let output = []; for (let i = 0; i < len; i = i + 1) { let item = input[i]; let newItem = doSomething(item); output.push(newItem); } let output = []; for (let item of input) { let newItem = doSomething(item); output.push(newItem); }
while
循环 、for
循环的问题是必须使用一个计数器 i ,并且需要初始化为0,并在每次循环时递增它。还必须不断将 i 与 len
进行比较,我们真正关心的是 doSomething(item)
,并不关心计数器。for…of
循环做出了改进,更简洁,但是还是有许多设置代码,看下问题:let output1 = []; for (let item of input1) { let newItem = doSomething1(item); output1.push(newItem); } let output2 = []; for (let item of input2) { let newItem = doSomething2(item); output2.push(newItem); }
出现了一些重复代码, 怎么解决下? 用map:
let output1 = input1.map(item => doSomething1(item)); let output2 = input2.map(item => doSomething2(item));
对比下来我们每次只需要关注循环的业务逻辑,不需要处理额外的代码了,并且代码没有重复。
条件语句的优化
摆脱else
大多数的时候不需要
else
的出现:function renderMenu(menuData) { let menuHTML = ''; if ((menuData === null) || (!Array.isArray(menuData)) { menuHTML = '<div class="menu-error">Most profuse apologies. Our server seems to have failed in it’s duties</div>'; } else if (menuData.length === 0) { menuHTML = '<div class="menu no-notifications">No new notifications</div>'; } else { menuHTML = '<ul class="menu notifications">' + menuData.map((item) => `<li><a href="${item.link}">${item.content}</a></li>`).join('') + '</ul>'; } return menuHTML; }
重构之后:
function renderMenu(menuData) { if ((menuData === null) || (!Array.isArray(menuData)) { return '<div class="menu-error">Most profuse apologies. Our server seems to have failed in it’s duties</div>'; } if (menuData.length === 0) { return '<div class="menu-no-notifications">No new notifications</div>'; } return '<ul class="menu-notifications">' + menuData.map((item) => `<li><a href="${item.link}">${item.content}</a></li>`).join('') + '</ul>'; }
对于阅读代码的人来说,只关心某种情况就可以,无需进一步阅读其他代码。
使用对象替换
switch
let notificationPtrn; switch (notification.type) { case 'citation': notificationPtrn = 'You received a citation from {{actingUser}}.'; break; case 'follow': notificationPtrn = '{{actingUser}} started following your work'; break; case 'mention': notificationPtrn = '{{actingUser}} mentioned you in a post.'; break; default: // Well, this should never happen }
使用对象替换后代码看来能更简洁些:
function getNotificationPtrn(n) { const textOptions = { citation: 'You received a citation from {{actingUser}}.', follow: '{{actingUser}} started following your work', mention: '{{actingUser}} mentioned you in a post.', } return textOptions[n.type]; }