推荐阅读:如何写出无法维护的代码
这里的重构技巧是:如何让代码更可读,而不是使代码性能更高
一、代码优化基本原则
-
易读性优先
尤其是在前端开发领域,你的代码好读比你的代码执行得快是更重要的
-
如果不是性能瓶颈,就不要为了性能而改写代码
-
复杂性守恒原则:无论你怎么写代码,复杂性都是不会消失的
推论:如果逻辑很复杂,那么代码看起来就应该是复杂的;如果逻辑很简单,代码看起来就应该是简单的。
二、如何命名
1. 程序员三大难题
- 变量命名
- 缓存失效
- 循环边界
2. 命名原则
- 注意词性
- 普通变量/属性用「名词」
js
const person = {
name: 'Leo',
}
-
bool 变量/属性用
形容词
、be 动词
、情态动词
或者hasXxx
jsconst person = { dead: false, // 如果是形容词,前面就没必要加 is,比如isDead 就很废话 canSpeak: true, //情态动词有 can、should、will、need 等,情态动词后面接动词 isVip: true, // be 动词有 is、was 等,后面一般接名词 hasChildren: true, // has 加名词 }
-
普通函数/方法用
动词
开头jsvar person = { run() {}, // 不及物动词 drinkWater() {}, // 及物动词 eat(foo) {}, // 及物动词加参数(参数是名词) }
-
回调、钩子函数用
介词
开头,或用动词的现在完成时态
jsconst 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() {}, }
-
容易混淆的地方加前缀
jsdiv1.classList.add('active') // DOM 对象 div2.addClass('active') // jQuery 对象 // 建议改成 domDiv1 // 或 elDiv1.classList.add('active') $div2.addClass('active')
-
属性访问器函数可以用名词
约定俗成
js
$div.text() // 其实是 $div.getText()
$div.text('hi') // 其实是 $div.setText('hi')
- 注意一致性
-
介词一致性
如果你使用了 before + after,那么就在代码的所有地方都坚持使用 如果你使用了 before + 完成时,那么就坚持使用 如果你改来改去,就「不一致」了,不一致将导致「不可预测」
-
顺序一致性
比如
updateContainerWidth
和updateHeightOfContainer
的顺序就令人很别扭,同样会引发 「不可预测」 -
表里一致性
函数名必须完美体现函数的功能,既不能多也不能少。
比如
jsfunction getSongs(){ return $.get('/songs').then((response){ div.innerText = response.songs }) }
就违背了表里一致性,getSongs 表示获取歌曲,并没有暗示这个函数会更新页面,但是实际 上函数更新了 div,这就是表里不一
正确的写法是:
- 要么纠正函数名
jsfunction getSongsAndUpdateDiv(){ return $.get('/songs').then((response){ div.innerText = response.songs }) }
- 要么写成两个函数
jsfunction 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. 一些固定的套路
-
表驱动编程(《代码大全》里说的) 所有一一对应的关系都可以用表来做
js// 计算分数对应的等级 // 优化前 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 参数为例)
把一个变量名命名好,就已经是自说明代码了
这里另外一种方式是:把别人关心的东西放在显眼的位置
js// 比如你封装一个 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(坏味道)
有些代码可以用,但是散发着
恶臭
的味道。
例如:
- 表里不一的代码
- 过时的注释
- 逻辑很简单,但是看起来很复杂的代码
- 重复的代码
- 相似的代码
- 总是一起出现的代码
五、普遍现象 ------ 破窗效应
一种犯罪心理学理论,此理论认为环境中的不良现象如果被放任存在,会诱使人们仿效,甚至变本加厉。
维基百科的例子:
以一幢有少许破窗的建筑为例,如果那些窗没修理好,可能将会有破坏者破坏更多的窗户。最终他们甚至会闯入建筑内,如果发现无人居住,也许就在那里占领、定居或者纵火。又或想像一条人行道有些许纸屑,如果无人清理,不久后就会有更多垃圾,最终人们会视为理所当然地将垃圾顺手丢弃在地上。因此破窗理论强调着力打击轻微罪行有助减少更严重罪案,甚至应该以零容忍的态度面对罪案。
在一个项目中,有一处写得不好的代码,就是破窗效应
的起源,最终变为屎山
所以我们程序员要有一点追求:只要是经过你手里的代码,都会比之前好一点。
感谢阅读,下次见 :)