每个程序员必备的重构技巧
Jan 03, 2024
推荐阅读:如何写出无法维护的代码
这里的重构技巧是:如何让代码更可读,而不是使代码性能更高
一、代码优化基本原则
易读性优先
尤其是在前端开发领域,你的代码好读比你的代码执行得快是更重要的
如果不是性能瓶颈,就不要为了性能而改写代码
复杂性守恒原则:无论你怎么写代码,复杂性都是不会消失的
推论:如果逻辑很复杂,那么代码看起来就应该是复杂的;如果逻辑很简单,代码看起来就应该是简单的。
二、如何命名
1. 程序员三大难题
- 变量命名
- 缓存失效
- 循环边界
2. 命名原则
- 注意词性
- 普通变量/属性用「名词」
1 | const person = { |
bool 变量/属性用
形容词
、be 动词
、情态动词
或者hasXxx
1
2
3
4
5
6const person = {
dead: false, // 如果是形容词,前面就没必要加 is,比如isDead 就很废话
canSpeak: true, //情态动词有 can、should、will、need 等,情态动词后面接动词
isVip: true, // be 动词有 is、was 等,后面一般接名词
hasChildren: true, // has 加名词
}普通函数/方法用
动词
开头1
2
3
4
5var person = {
run() {}, // 不及物动词
drinkWater() {}, // 及物动词
eat(foo) {}, // 及物动词加参数(参数是名词)
}回调、钩子函数用
介词
开头,或用动词的现在完成时态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21const person = {
beforeDie() {},
afterDie() {},
// 或者
willDie() {},
dead() {}, // 这里跟 bool 冲突,你只要不同时暴露 bool dead 和函数 dead 就行,怕冲突就用上面的 afterDie
}
button.addEventListener('click', onButtonClick)
const component = {
beforeCreate() {},
created() {},
beforeMount() {},
mounted() {},
beforeUpdate() {},
updated() {},
activated() {},
deactivated() {},
beforeDestroy() {},
destroyed() {},
errorCaptured() {},
}容易混淆的地方加前缀
1
2
3
4
5
6
7div1.classList.add('active') // DOM 对象
div2.addClass('active') // jQuery 对象
// 建议改成
domDiv1
// 或
elDiv1.classList.add('active')
$div2.addClass('active')属性访问器函数可以用名词
约定俗成
1 | $div.text() // 其实是 $div.getText() |
- 注意一致性
介词一致性
如果你使用了 before + after,那么就在代码的所有地方都坚持使用
如果你使用了 before + 完成时,那么就坚持使用
如果你改来改去,就「不一致」了,不一致将导致「不可预测」顺序一致性
比如
updateContainerWidth
和updateHeightOfContainer
的顺序就令人很别扭,同样会引发 「不可预测」表里一致性
函数名必须完美体现函数的功能,既不能多也不能少。
比如
1
2
3
4
5function getSongs(){
return $.get('/songs').then((response){
div.innerText = response.songs
})
}就违背了表里一致性,getSongs 表示获取歌曲,并没有暗示这个函数会更新页面,但是实际 上函数更新了 div,这就是表里不一
正确的写法是:
- 要么纠正函数名
1
2
3
4
5function getSongsAndUpdateDiv(){
return $.get('/songs').then((response){
div.innerText = response.songs
})
}- 要么写成两个函数
1
2
3
4
5
6
7
8
9function getSongs() {
return $.get('/songs')
}
function updateDiv(songs) {
div.innerText = response.songs
}
getSongs().then(response => {
updateDiv(response.songs)
})时间一致性
添加代码很容易,而改代码是很难的,改名字很容易引发其他 bug
有可能随着代码的变迁,一个变量的含义已经不同于它一开始的含义了,这个时候你需要及时
改掉这个变量的名字。这一条是最难做到的,因为写代码容易,改代码难。如果这个代码组织得不好,很可能会出现牵一发而动全身的情况(如全局变量就很难改)
三、如何改代码
如果你的代码有单元测试,那么改起来就很放心。如果没有单元测试,就需要用小步快跑
的策略来修改。
什么是小步快跑
:每次不要改很多,每次只改一点点,测试通过后,再改一点点,再测试,再改一点点… 如此反复。
那么如何修改一点点呢?《重构》这本书介绍了很多方法,但是讲得太宏观太啰嗦了,如果你有时间可以看看。
我比较喜欢下面两个经久不衰的方法
1. 使用函数来改代码
步骤:
- 将一坨代码放到一个函数里
- 将代码依赖的外部变量作为参数
- 将代码的输出作为函数的返回值
- 给函数取一个合适的名字
- 调用这个函数并传入参数
- 这个函数里的代码如果超过 5 行或者多处结构相似,则依然有优化的空间,请回到第 1 步
2. 使用对象来改代码
如果使用了函数改造法改造后,发现有太多的小函数,则可以使用对象讲这个函数串起来。
由于 JS 的 this,这种方法可能会不安全
3. 一些固定的套路
表驱动编程(《代码大全》里说的)
所有一一对应的关系都可以用表来做1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32// 计算分数对应的等级
// 优化前
function calculateGrade(score) {
if (score == 100) {
return 'A+'
} else if (score >= 90) {
return 'A'
} else if (score >= 80) {
return 'B'
} else if (score >= 70) {
return 'C'
} else if (score >= 60) {
return 'D'
} else {
return 'E'
}
}
// 优化后
function calculateGrade(score) {
const gradeMap = {
100: 'A+',
90: 'A',
80: 'B',
70: 'C',
60: 'D',
others: 'E',
}
const formatScore = parseInt(score / 10, 10)
return gradeMap[formatScore] || gradeMap['others']
}自说明代码(以 API 参数为例)
把一个变量名命名好,就已经是自说明代码了
这里另外一种方式是:把别人关心的东西放在显眼的位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 比如你封装一个 Dialog 组件,你可以让开发者知道有这些 API
class Dialog {
constructor(options) {
const defaultOptions = {
title: '提示',
content: '<h2>请填写content</h2>',
confirmButtonText: '确定',
cancelButtonText: '取消',
onConfirm: () => {},
onCancel: () => {},
}
}
this.options = Object.assign({}, defaultOptions, options)
// Object.assign 就是把对象后面的值依次赋值(覆盖)到前面
}defaultOptions 就类似文档说明
四、什么是 bad smell(坏味道)
有些代码可以用,但是散发着
恶臭
的味道。
例如:
- 表里不一的代码
- 过时的注释
- 逻辑很简单,但是看起来很复杂的代码
- 重复的代码
- 相似的代码
- 总是一起出现的代码
五、普遍现象 —— 破窗效应
一种犯罪心理学理论,此理论认为环境中的不良现象如果被放任存在,会诱使人们仿效,甚至变本加厉。
维基百科的例子:
以一幢有少许破窗的建筑为例,如果那些窗没修理好,可能将会有破坏者破坏更多的窗户。最终他们甚至会闯入建筑内,如果发现无人居住,也许就在那里占领、定居或者纵火。又或想像一条人行道有些许纸屑,如果无人清理,不久后就会有更多垃圾,最终人们会视为理所当然地将垃圾顺手丢弃在地上。因此破窗理论强调着力打击轻微罪行有助减少更严重罪案,甚至应该以零容忍的态度面对罪案。
在一个项目中,有一处写得不好的代码,就是破窗效应
的起源,最终变为屎山
所以我们程序员要有一点追求:只要是经过你手里的代码,都会比之前好一点。
感谢阅读,下次见 :)
cd ../