前言
这是一本通用的书籍,当中的示例使用 JavaScript
编写,对于前端程序员挺友好的
对于重构的学习,其实有一个过程,如同没有见过健康代码的人,根本想不到代码原来可以这么写🎉
一
何为重构
在代码完成之后,不修改原有逻辑的情况之下,进行提纯,让代码总体阅读性更好,如
- 子类的共同方法,放置至父类上
- 提取公用函数
- 去除多余参数
- …
以抬杠思想来说,我难道不能在编写代码的时候,就将其逻辑思路分明,从而无需费这二遍手
那当然是最佳表现,但可惜的是,所有的开局设计,都会在编写时逐渐分离崩盘,几乎做不到与设计之初所想的那样,重构的思想则是,先写再改,放开手直接干,当然这也不是说让你瞎搞,然后靠重构起死回生
为什么要重构
那么是不是说,对其结构“不甚清晰”的评价只是美学意义上的判断,只是对所谓丑陋代码的反感呢?毕竟编译器也不会在乎代码好不好看。但是,当我们需要修改系统时,就涉及了人,而人在乎这些。
对于多数代码,我们只停留在了能跑就行,这也是最基本的,满足最低需求。而改动则表示了很多的可能,可能导致无法运行,变得更糟,或许会变得更好,但让人感受不着,甚至说从表面上看,代码逻辑被拆的更加复杂
我再强调一次,是需求的变化使重构变得必要。如果一段代码能正常工作,并且不会再被修改,那么完全可以不去重构它。能改进之当然很好,但若没人需要去理解它,它就不会真正妨碍什么。如果确实有人需要理解它的工作原理,并且觉得理解起来很费劲,那你就需要改进一下代码了。
重构是一种优化手段,并非是必须的。当然,其中描述的某人,也可能是你自己,现在回看一些自己亲手写的代码,你真的能分辨出它的效果是什么吗?在这里起到了什么作用?
我体验过,感觉好极了🤣甚至不敢相信这是自己写的
重构需要注意什么
进行一次重构之后,最好进行一波测试,与重构前对比,是否出现了意料之外的结果,慢慢的小步前进
可以配合 git
,每次成功的重构,进行一波存档
命名
对于一个函数,名称要能体现出函数的作用,参数名也是如此。最好的效果就是,一看函数名,就知道什么效果,通过传入不同的参数,又会造成什么影响
作者有一条风格,对于作为函数返回值的变量,统一使用 result
进行命名
因为重构过程一直在提取,修改变量名、给新函数取名的操作很多很多,命名这条还是很重要的,虽然我们更多时候是起名困难症,或者说一个词被使用掉了之后,就没词了
好看的代码有必要吗
傻瓜都能写出计算机可以理解的代码。唯有能写出人类容易理解的代码的,才是优秀的程序员。
虽然这方面一直被当成段子,一个将代码写的谁都能看懂的程序员,让人觉得谁都能接你的班,写出只有自己可以看懂的代码,便是无可替代👏
变量与参数
减少申明一堆傻乎乎的临时变量,绝大多数情况下,都是没有必要的,表达其意义的方式有很多
//bar
function template() {
const btn = `<button>按钮<button/>`
return btn
}
//good
function getBtn() {
return `<button>按钮<button/>`
}
需要通过其他参数计算得到的值,可以将其转换为一个函数,然后分布式计算,调用一次函数就好,纯函数的执行效率是不错的,比传参的效率要高也不要意外,通过函数名来表达其执行的结果
临时变量
什么样的变量会是一个典型的代表呢?
function fn(index) {
const arr = [1,3,5]
return arr[index]
}
恩对,就是这样,一个一成不变的变量,写成闭包都可以,当然也可以这样
const numFor = index => [1, 3, 5][index]
分离不相关的逻辑
一个示例
for (let perf of invoice.performances) {
volumeCredits += volumeCreditsFor(perf)
result += ` ${playFor(perf).name}: ${usd(amountFor(perf))} (${perf.audience} seats)\n`
totalAmount += amountFor(perf)
}
作者将其修改为了
for (let perf of invoice.performances) {
// print line for this order
result += ` ${playFor(perf).name}: ${usd(amountFor(perf))} (${perf.audience} seats)\n`;
totalAmount += amountFor(perf);
}
for (let perf of invoice.performances) {
volumeCredits += volumeCreditsFor(perf);
}
这倒是不咋理解,毕竟从表面上,这循环是重复的,这方面我还是不太乐意接受的,或许换成 forEach()
看起来会好一些,而且在上面的那两个也没有关联,还分三个不成,变量相应的移动还可以
好吧,看书只看一半也是害人,后续就将变量与循环逻辑给完全抽出函数块了,直接给最终结果就完了,这种移动的利处在于让人观察到可抽离之处,让其从这个函数中消失
重构造成的性能问题
大多数情况下可以忽略它。如果重构引入了性能损耗,先完成重构,再做性能优化
这一点得到了承认,当出现性能倒退时,给出的反应是,不理睬,继续完成重构,再根据状况,挽回性能,也就是说,付出一些性能的代价,是可以接受的
reduce()
的使用
当中给我的一点启发,对于 Array.reduce()
什么时候传入第二参数,也就是初始化统计数
如果数组长成这样,那么作为一个统计总和的方法来说,不传递也没有问题
[1, 2, 3]
若是数组状态的都是对象,则不能如此使用,需要先占个位置
[{ value: 1 }, { value: 2 }, { value: 3}]
[
{ value: 1 },
{ value: 2 },
{ value: 3 }
].reduce((count, current) => count + current.value, 0)
编程时,需要遵循营地法则:保证你离开时的代码库一定比来时更健康。