带你入门前端工程(十):重构

简介: 带你入门前端工程(十):重构

《重构2》一书中对重构进行了定义:

所谓重构(refactoring)是这样一个过程:在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。重构是一种经千锤百炼形成的有条不紊的程序整理方法,可以最大限度地减小整理过程中引入错误的概率。本质上说,重构就是在代码写好之后改进它的设计。

重构和性能优化有相同点,也有不同点。

相同的地方是它们都在不改变程序功能的情况下修改代码;不同的地方是重构为了让代码变得更加容易理解、易于修改,性能优化则是为了让程序运行得更快。这里还得重点提一句,由于侧重点不同,重构可能使程序运行得更快,也可能使程序运行得更慢

重构可以一边写代码一边重构,也可以在程序写完后,拿出一段时间专门去做重构。没有说哪个方式更好,视个人情况而定。如果你专门拿一段时间来做重构,则建议在重构一段代码后,立即进行测试。这样可以避免修改代码太多,在出错时找不到错误点。

重构的原则

  1. 事不过三,三则重构。即不能重复写同样的代码,在这种情况下要去重构。
  2. 如果一段代码让人很难看懂,那就该考虑重构了。
  3. 如果已经理解了代码,但是非常繁琐或者不够好,也可以重构。
  4. 过长的函数,需要重构。
  5. 一个函数最好对应一个功能,如果一个函数被塞入多个功能,那就要对它进行重构了。(4 和 5 不冲突)
  6. 重构的关键在于运用大量微小且保持软件行为的步骤,一步步达成大规模的修改。每个单独的重构要么很小,要么由若干小步骤组合而成。

重构的手法

《重构2》这本书中,介绍了多达上百种重构手法。但我觉得以下八种是比较常用的:

  1. 提取重复代码,封装成函数
  2. 拆分功能太多的函数
  3. 变量/函数改名
  4. 替换算法
  5. 以函数调用取代内联代码
  6. 移动语句
  7. 折分嵌套条件表达式
  8. 将查询函数和修改函数分离

提取重复代码,封装成函数

假设有一个查询数据的接口 /getUserData?age=17&city=beijing。现在需要做的是把用户数据:{ age: 17, city: 'beijing' } 转成 URL 参数的形式:

let result = ''
const keys = Object.keys(data)  // { age: 17, city: 'beijing' }
keys.forEach(key => {
    result += '&' + key + '=' + data[key]
})
result.substr(1) // age=17&city=beijing

如果只有这一个接口需要转换,不封装成函数是没问题的。但如果有多个接口都有这种需求,那就得把它封装成函数了:

function JSON2Params(data) {
    let result = ''
    const keys = Object.keys(data)
    keys.forEach(key => {
        result += '&' + key + '=' + data[key]
    })
    return result.substr(1)
}

拆分功能太多的函数

下面是一个打印账单的程序:

function printBill(data = []) {
    // 汇总数据
    const total = {}
    data.forEach(item => {
        if (total[item.department] === undefined) {
            total[item.department] = 0
        }
        total[item.department] += item.value
    })
    // 打印汇总后的数据
    const keys = Object.keys(total)
    keys.forEach(key => {
        console.log(`${key} 部门:${total[key]}`)
    })
}
printBill([
    {
        department: '销售部',
        value: 89,
    },
    {
        department: '后勤部',
        value: 132,
    },
    {
        department: '财务部',
        value: 78,
    },
    {
        department: '总经办',
        value: 90,
    },
    {
        department: '后勤部',
        value: 56,
    },
    {
        department: '总经办',
        value: 120,
    },
])

可以看到这个 printBill() 函数实际上包含有两个功能:汇总和打印。我们可以把汇总数据的代码提取出来,封装成一个函数。这样 printBill() 函数就只需要关注打印功能了。

function printBill(data = []) {
    const total = calculateBillData(data)
    const keys = Object.keys(total)
    keys.forEach(key => {
        console.log(`${key} 部门:${total[key]}`)
    })
}
function calculateBillData(data) {
    const total = {}
    data.forEach(item => {
        if (total[item.department] === undefined) {
            total[item.department] = 0
        }
        total[item.department] += item.value
    })
    return total
}

变量/函数改名

无论是变量命名,还是函数命名,都要尽量让别人明白你这个变量/函数是干什么的。变量命名的规则着重于描述“是什么”,函数命名的规则着重于描述“做什么”。

变量

const a = width * height

上面这个变量就不太好,a 很难让人看出来它是什么。

const area = width * height

改成这样就很好理解了,原来这个变量是表示面积。

函数

function cache(data) {
    const result = []
    data.forEach(item => {
        if (item.isCache) {
            result.push(item)
        }
    })
    return result
}

这个函数名称会让人很疑惑,cache 代表什么?是设置缓存还是删除缓存?再一细看代码,噢,原来是获取缓存数据。所以这个函数名称改成 getCache() 更加合适。

替换算法

function foundPersonData(person) {
    if (person == 'Tom') {
        return {
            name: 'Tom',
            age: 18,
            id: 21,
        }
    }
    if (person == 'Jim') {
        return {
            name: 'Jim',
            age: 20,
            id: 111,
        }
    }
    if (person == 'Lin') {
        return {
            name: 'Lin',
            age: 19,
            id: 10,
        }
    }
    return null
}

上面这个函数的功能是根据用户姓名查找用户的详细信息,可以看到这个函数做了三次 if 判断,如果没找到数据就返回 null。这个函数不利于扩展,每多一个用户就得多写一个 if 语句,我们可以用更方便的“查找表”来替换它。

function foundPersonData(person) {
    const data = {
        'Tom': {
            name: 'Tom',
            age: 18,
            id: 21,
        },
        'Jim': {
            name: 'Jim',
            age: 20,
            id: 111,
        },
        'Lin': {
            name: 'Lin',
            age: 19,
            id: 10,
        },
    }
    return data[person] || null
}

修改后代码结构看起来更加清晰,也方便未来做扩展。

以函数调用取代内联代码

如果一些代码所做的事情和已有函数的功能重复,那就最好用函数调用来取代这些代码。

let hasApple = false
for (const fruit of fruits) {
    if (fruit == 'apple') {
        hasApple = true
        break
    }
}

例如上面的代码,可以用数组的 includes() 方法代替:

const hasApple = fruits.includes('apple')

修改后代码更加简洁。

移动语句

让存在关联的东西一起出现,可以使代码更容易理解。如果有一些代码都是作用在一个地方,那么最好是把它们放在一起,而不是夹杂在其他的代码中间。最简单的情况下,只需使用移动语句就可以让它们聚集起来。就像下面的示例一样:

const name = getName()
const age = getAge()
let revenue
const address = getAddress()
// ...
const name = getName()
const age = getAge()
const address = getAddress()
let revenue
// ...

由于两块数据区域的功能是不同的,所以除了移动语句外,我还在它们之间空了一行,这样让人更容易区分它们之间的不同。

折分嵌套条件表达式

当很多的条件表达式嵌套在一起时,会让代码变得很难阅读:

function getPayAmount() {
    if (isDead) {
        return deadAmount()
    } else {
        if (isSeparated) {
            return separatedAmount()
        } else if (isRetired) {
            return retireAmount()
        } else {
            return normalAmount()
        }
    }
}
function getPayAmount() {
    if (isDead) return deadAmount()
    if (isSeparated) return separatedAmount()
    if (isRetired) return retireAmount()
    return normalAmount()
}

将条件表达式拆分后,代码的可阅读性大大增强了。

将查询函数和修改函数分离

一般的查询函数都是用于取值的,例如 getUserData()getAget()getName() 等等。有时候,我们可能为了方便,在查询函数上附加其他功能。例如下面的函数:

function getValue() {
    let result = 0
    this.data.forEach(val => result += val)
    // 这里插入了一个奇怪的操作
    sendBill()
    return result
}

千万不要这样做,函数很重要的功能是职责分离。所以我们要将它们分开:

function getValue() {
    let result = 0
    this.data.forEach(val => result += val)
    return result
}
function sendBill() {
    // ...
}

这样函数的功能就很清晰了。

小结

古人云:尽信书,不如无书。《重构2》也不例外,在看这本书的时候一定要带着批判性的目光去阅读它。

里面介绍的重构手法有很多,多达上百种,但这些手法不一定适用所有人。所以一定要有取舍,将里面有用的手法摘抄下来,时不时的看几遍。这样在写代码时,重构才能像呼吸一样自然,即使用了你也不知道。

参考资料

带你入门前端工程 全文目录:

  1. 技术选型:如何进行技术选型?
  2. 统一规范:如何制订规范并利用工具保证规范被严格执行?
  3. 前端组件化:什么是模块化、组件化?
  4. 测试:如何写单元测试和 E2E(端到端) 测试?
  5. 构建工具:构建工具有哪些?都有哪些功能和优势?
  6. 自动化部署:如何利用 Jenkins、Github Actions 自动化部署项目?
  7. 前端监控:讲解前端监控原理及如何利用 sentry 对项目实行监控。
  8. 性能优化(一):如何检测网站性能?有哪些实用的性能优化规则?
  9. 性能优化(二):如何检测网站性能?有哪些实用的性能优化规则?
  10. 重构:为什么做重构?重构有哪些手法?
  11. 微服务:微服务是什么?如何搭建微服务项目?
  12. Severless:Severless 是什么?如何使用 Severless?
目录
相关文章
|
2月前
|
前端开发 机器人 API
前端大模型入门(一):用 js+langchain 构建基于 LLM 的应用
本文介绍了大语言模型(LLM)的HTTP API流式调用机制及其在前端的实现方法。通过流式调用,服务器可以逐步发送生成的文本内容,前端则实时处理并展示这些数据块,从而提升用户体验和实时性。文章详细讲解了如何使用`fetch`发起流式请求、处理响应流数据、逐步更新界面、处理中断和错误,以及优化用户交互。流式调用特别适用于聊天机器人、搜索建议等应用场景,能够显著减少用户的等待时间,增强交互性。
594 2
|
1月前
|
编解码 前端开发 JavaScript
从入门到精通:揭秘前端开发中那些不为人知的优化秘籍!
前端开发是充满无限可能的领域,从初学者到资深专家,每个人都追求更快、更稳定、更用户体验友好的网页。本文介绍了四大优化秘籍:1. HTML的精简与语义化;2. CSS的优雅与高效;3. JavaScript的精简与异步加载;4. 图片与资源的优化。通过这些方法,可以显著提升网页性能和用户体验。
23 3
|
1月前
|
机器学习/深度学习 自然语言处理 前端开发
前端神经网络入门:Brain.js - 详细介绍和对比不同的实现 - CNN、RNN、DNN、FFNN -无需准备环境打开浏览器即可测试运行-支持WebGPU加速
本文介绍了如何使用 JavaScript 神经网络库 **Brain.js** 实现不同类型的神经网络,包括前馈神经网络(FFNN)、深度神经网络(DNN)和循环神经网络(RNN)。通过简单的示例和代码,帮助前端开发者快速入门并理解神经网络的基本概念。文章还对比了各类神经网络的特点和适用场景,并简要介绍了卷积神经网络(CNN)的替代方案。
125 1
|
1月前
|
移动开发 前端开发 JavaScript
前端实训,刚入门,我用原生技术(H5、C3、JS、JQ)手写【网易游戏】页面特效
于辰在大学期间带领团队参考网易游戏官网的部分游戏页面,开发了一系列前端实训作品。项目包括首页、2021校园招聘页面和明日之后游戏页面,涉及多种特效实现,如动态图片切换和人物聚合效果。作品源码已上传至CSDN,视频效果可在CSDN预览。
43 0
|
2月前
|
缓存 前端开发 JavaScript
前端的全栈之路Meteor篇(二):容器化开发环境下的meteor工程架构解析
本文详细介绍了使用Docker创建Meteor项目的准备工作与步骤,解析了容器化Meteor项目的目录结构,包括工程准备、环境配置、容器启动及项目架构分析。提供了最佳实践建议,适合初学者参考学习。项目代码已托管至GitCode,方便读者实践与交流。
|
2月前
|
自然语言处理 资源调度 前端开发
前端大模型入门(四):不同文本分割器对比和效果展示-教你如何根据场景选择合适的长文本分割方式
本文详细介绍了五种Langchain文本分割器:`CharacterTextSplitter`、`RecursiveCharacterTextSplitter`、`TokenTextSplitter`、`MarkdownTextSplitter` 和 `LatexTextSplitter`,从原理、优缺点及适用场景等方面进行了对比分析,旨在帮助开发者选择最适合当前需求的文本分割工具,提高大模型应用的处理效率和效果。
203 1
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
前端大模型入门(三):编码(Tokenizer)和嵌入(Embedding)解析 - llm的输入
本文介绍了大规模语言模型(LLM)中的两个核心概念:Tokenizer和Embedding。Tokenizer将文本转换为模型可处理的数字ID,而Embedding则将这些ID转化为能捕捉语义关系的稠密向量。文章通过具体示例和代码展示了两者的实现方法,帮助读者理解其基本原理和应用场景。
519 1
|
2月前
|
人工智能 前端开发 JavaScript
前端大模型入门(二):掌握langchain的核心Runnable接口
Langchain.js 是 Langchain 框架的 JavaScript 版本,专为前端和后端 JavaScript 环境设计。最新 v0.3 版本引入了强大的 Runnable 接口,支持灵活的执行方式和异步操作,方便与不同模型和逻辑集成。本文将详细介绍 Runnable 接口,并通过实现自定义 Runnable 来帮助前端人员快速上手。
|
2月前
|
存储 人工智能 前端开发
前端大模型应用笔记(三):Vue3+Antdv+transformers+本地模型实现浏览器端侧增强搜索
本文介绍了一个纯前端实现的增强列表搜索应用,通过使用Transformer模型,实现了更智能的搜索功能,如使用“番茄”可以搜索到“西红柿”。项目基于Vue3和Ant Design Vue,使用了Xenova的bge-base-zh-v1.5模型。文章详细介绍了从环境搭建、数据准备到具体实现的全过程,并展示了实际效果和待改进点。
188 2
|
2月前
|
JavaScript 前端开发 程序员
前端学习笔记——node.js
前端学习笔记——node.js
54 0