技术分享
首页
  • JavaScript

    • 构造函数和原型
    • Cookie和Session
    • Object.create(null)和{}
    • TypeScript配置
    • typescript入门到进阶
  • 框架

    • Vue-Router
    • React基础入门
  • 其它

    • Http协议
    • 跨域问题总结
  • 分析Promise实现
  • Axios源码分析
  • Webpack原理
  • vueRouter源码分析
  • Vue

    • Vite快速搭建Vue3+TypeScript项目
    • Cordova打包Vue项目的问题
    • Vue将汉字转为拼音,取出首字母
    • h5项目问题总结
  • JavaScript

    • new Function
  • 后端

    • Node.js中使用Crypto生成Token
    • Body-Parser处理多层对象的问题
  • 其它

    • 项目Demo汇总
    • Vuepress+Vercel搭建个人站点
    • 项目中能用到的
    • husky规范代码提交
  • Mongoose基础
  • Multer文件上传中间件的使用
  • JavaScript

    • 浅谈两数全等
    • JavaScript进制转换
    • 手写bind,apply,call和new
  • 算法

    • 数组去重和排序
    • 数组扁平化
    • 斐波那契数列
  • JavaScript 数据结构
  • 其它

    • webpack面试题
    • vite面试题
    • svg和canvas的优缺点
    • TypeScript面试题
    • Vue常见面试题
  • 计算机网络

    • 数据链路层
    • 网络层
  • Git的使用
  • Nginx的使用
  • CentOS7安装Nginx
  • 正则表达式
  • SEO搜索引擎优化
  • Serverless介绍
友链
GitHub (opens new window)

刘誉

总有人要赢,为什么不能是我
首页
  • JavaScript

    • 构造函数和原型
    • Cookie和Session
    • Object.create(null)和{}
    • TypeScript配置
    • typescript入门到进阶
  • 框架

    • Vue-Router
    • React基础入门
  • 其它

    • Http协议
    • 跨域问题总结
  • 分析Promise实现
  • Axios源码分析
  • Webpack原理
  • vueRouter源码分析
  • Vue

    • Vite快速搭建Vue3+TypeScript项目
    • Cordova打包Vue项目的问题
    • Vue将汉字转为拼音,取出首字母
    • h5项目问题总结
  • JavaScript

    • new Function
  • 后端

    • Node.js中使用Crypto生成Token
    • Body-Parser处理多层对象的问题
  • 其它

    • 项目Demo汇总
    • Vuepress+Vercel搭建个人站点
    • 项目中能用到的
    • husky规范代码提交
  • Mongoose基础
  • Multer文件上传中间件的使用
  • JavaScript

    • 浅谈两数全等
    • JavaScript进制转换
    • 手写bind,apply,call和new
  • 算法

    • 数组去重和排序
    • 数组扁平化
    • 斐波那契数列
  • JavaScript 数据结构
  • 其它

    • webpack面试题
    • vite面试题
    • svg和canvas的优缺点
    • TypeScript面试题
    • Vue常见面试题
  • 计算机网络

    • 数据链路层
    • 网络层
  • Git的使用
  • Nginx的使用
  • CentOS7安装Nginx
  • 正则表达式
  • SEO搜索引擎优化
  • Serverless介绍
友链
GitHub (opens new window)
  • 分析Promise实现
  • Axios源码分析
  • vueRouter源码分析
  • Webpack原理
  • 阅读《重构:改善既有代码的设计》笔记
    • 何时需要考虑重构
    • 重构手法
      • 拆分循环
      • 移动语句
      • 提炼函数
      • 内联函数
      • 提炼变量
      • 内联变量
      • 改变函数声明
      • 封装变量
      • 变量改名
      • 引入参数对象
      • 函数组合成类
      • 函数组合成变换
      • 拆分阶段
    • 封装
  • vue源码
  • webpack
  • 可视化
  • koa全家桶
  • 笔记
coderly
2021-09-05

阅读《重构:改善既有代码的设计》笔记

# 阅读《重构:改善既有代码的设计》笔记

  • 所谓重构(refactoring)是这样一个过程:在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。
  • 本质上:重构就是在代码写好之后改进它的设计。

# 何时需要考虑重构

如果你要给程序添加一个特性,但发现代码因缺乏良好的结构而不易于进行更改,那就先重构那个程序,使其比较容易添加该特性,然后再添加该特性。

重构步骤:

小步修改,每次修改后就运行测试。
重构技术就是以微小的步伐修改程序。如果你犯下错误,很容易便可发现它。

  1. 确保即将修改的代码拥有一组可靠的测试。
    • 重构前,先检查自己是否有一套可靠的测试集。这些测试必须有自我检验能力。
  2. 提炼函数
    • 需要检查一下,如果将这块代码提炼到自己的一个函数里,有哪些变量会离开原本的作用域。
    • 考虑哪些会被修改的变量以及不会被修改的变量
    • 考虑是否需要返回结果

# 重构手法

# 拆分循环

# 移动语句

# 提炼函数

将一块代码提炼到一个独立的函数中,并以这段代码的用途为这个函数命名

何时提炼:

  1. 被用过不止一次的代码
  2. 如果你需要花时间浏览一段代码才能弄清它到底在干什么,那么就应该将其提炼到一个函数中,并根据它所做的事为其命名。以后再读 到这段代码时,你一眼就能看到函数的用途,大多数时候根本不需要关心函数如 何达成其用途
    • 被提炼的函数无局部变量,直接放入函数中
    • 被提炼的函数有局部变量
      • 被提炼代码段只是读取这些变量的值(),作为形参传入,return 值
      • 被提炼代码段修改了这个变量的数据(Array,Object),作为形参传入,可选是否 return 值

eg:

// 修改前
let userInfo = {};
userInfo.name = info.name;
userInfo.age = info.age;
userInfo.sex = info.sex;
// TODO:其它对 userInfo 的操作

// 修改后
function updateInfo(info) {
  let userInfo = {};
  userInfo.name = info.name;
  userInfo.age = info.age;
  userInfo.sex = info.sex;
  return userInfo;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 内联函数

某些函数,其内部代码和函数名称同样清晰易读

  1. 检查函数,确定它不具多态性。
  2. 找出这个函数的所有调用点。
  3. 将这个函数的所有调用点都替换为函数本体。
  4. 每次替换之后,执行测试。

eg:

// 修改前
function getMoney(user) {
  return getDays(user) * 100;
}
function getDays(user) {
  return user.days > 7 ? user.days : user.days + 1;
}

// 修改后
function getMoney(user) {
  return (user.days > 7 ? user.days : user.days + 1) * 100;
}
1
2
3
4
5
6
7
8
9
10
11
12

# 提炼变量

表达式有可能非常复杂而难以阅读

  1. 确认要提炼的表达式没有副作用。
  2. 声明一个不可修改的变量,把你想要提炼的表达式复制一份,以该表达式的结果值给这个变量赋值。
  3. 用这个新变量取代原来的表达式。

eg: 计算圆柱体表面积:2 * 2π * r * h + 2π * r^2

// 修改前
function calcCylinderArea(r, h) {
  return 2 * 2π * r * h + 2π * r^2
}

// 修改后
function calcCylinderArea(r, h) {
  const rectArea = 2π * r^2 * h
  const circleArea = 2π * r
  return 2 * circleArea + rectArea
}
1
2
3
4
5
6
7
8
9
10
11

# 内联变量

在一个函数内部,变量能给表达式提供有意义的名字,因此通常变量是好东西

  1. 检查确认变量赋值语句的右侧表达式没有副作用
  2. 如果变量没有被声明为不可修改,先将其变为不可修改,并执行测试
  3. 找到第一处使用该变量的地方,将其替换为直接使用赋值语句的右侧表达式。
  4. 测试。
  5. 重复前面两步,逐一替换其他所有使用该变量的地方。
  6. 删除该变量的声明点和赋值语句。
  7. 测试。

eg:

// 修改前
let days = user.days;
return days > 7;

// 修改后
return user.days > 7;
1
2
3
4
5
6

# 改变函数声明

一个好名字能让人一眼看出函数的用途,而不必查看其实现代码

  1. 如果想要移除一个参数,需要先确定函数体内没有使用该参数。
  2. 修改函数声明,使其成为你期望的状态。
  3. 找出所有使用旧的函数声明的地方,将它们改为使用新的函数声明。
  4. 测试。

eg:

// 修改前
function getInfo(url) {
  return fetch({ url }).then((res) => res.json());
}

// 修改后
function getUserInfoById(url) {
  return fetch({ url }).then((res) => res.json());
}
1
2
3
4
5
6
7
8
9

# 封装变量

如果想要搬移一处被广泛使用的数据,最好的办法往往是先以函数形式封装所有对该数据的访问
这样,就能把“重新组织数据”的困难任务转化 为“重新组织函数”这个相对简单的任务。

  1. 创建封装函数,在其中访问和更新变量值。
  2. 执行静态检查。
  3. 逐一修改使用该变量的代码,将其改为调用合适的封装函数。每次替换之后,
  4. 执行测试。
  5. 限制变量的可见性。

eg:

// 修改前
let name = 'coderly'
let age = 18
let sex = '男'

function getSex() {
  return sex
}

// 修改后

let user = {
  name: 'coderly',
  age: 18,
  sex = '男'
}

function getSex() {
  return user.sex
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 变量改名

变量可以很好地解释一段程序在干什么 —— 如果变量名起得好的话

  1. 如果变量被广泛使用,考虑运用『封装变量』将其封装起来。
  2. 找出所有使用该变量的代码,逐一修改。
  3. 测试

eg:

// 修改前
let url = "https://coderly.cn";
function updateUrl(newUrl) {
  url = url;
}

// 修改后
let myBlogUrl = "https://coderly.cn";
function updateMyBlogUrl(newBlogUrl) {
  myBlogUrl = newBlogUrl;
}
1
2
3
4
5
6
7
8
9
10
11

# 引入参数对象

将数据组织成结构是一件有价值的事,因为这让数据项之间的关系变得明晰
使用该数据结构的函数都会通过同样的名字来访问其中的元素,从而提升代码的一致性。

  1. 如果暂时还没有一个合适的数据结构,就创建一个
  2. 测试。
  3. 使用『改变函数声明』给原来的函数新增一个参数,类型是新建的数据结构。
  4. 测试。
  5. 调整所有调用者,传入新数据结构的适当实例。每修改一处,执行测试。 用新数据结构中的每项元素,逐一取代参数列表中与之对应的参数项,然后删除原来的参数。测试。

eg:

// 修改前
const station = {
  name: "coderly",
  readings: [
    { temp: 47, time: "2021-12-05 09:10" },
    { temp: 53, time: "2021-12-05 09:20" },
    { temp: 58, time: "2021-12-05 09:30" },
    { temp: 53, time: "2021-12-05 09:40" },
    { temp: 51, time: "2021-12-05 09:50" },
  ],
};
// 找到超出指定范围的温度读数
function readingsOutsideRange(station, min, max) {
  return station.readings.filter((r) => r.temp < min || r.temp > max);
}
// 调用
readingsOutsideRange(station, min, max);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

// 修改后
const station = {
  name: "coderly",
  readings: [
    { temp: 47, time: "2021-12-05 09:10" },
    { temp: 53, time: "2021-12-05 09:20" },
    { temp: 58, time: "2021-12-05 09:30" },
    { temp: 53, time: "2021-12-05 09:40" },
    { temp: 51, time: "2021-12-05 09:50" },
  ],
};
function readingsOutsideRange(station, range) {
  return station.readings.filter(
    (r) => r.temp < range.min || r.temp > range.max
  );
}

class NumberRange {
  constructor(min, max) {
    this._data = { min: min, max: max };
  }
  get min() {
    return this._data.min;
  }
  get max() {
    return this._data.max;
  }
}

// 使用
let range = new NumberRange(min, max);
readingsOutsideRange(station, range);
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

# 函数组合成类

如果发现一组函数形影不离地操作同一块数据(通常是将这块数据作为参数传递给函数),就可以认为,是时候组建一个类了
类能明确地给这些函数提供一个共用的环境,在对象内部调用这些函数可以少传许多参数,从而简化函数调用,并且这样一个对象也可以更方便地传递给系统的其他部分。
使用类有一大好处:客户端可以修改对象的核心数据,通过计算得出的派生数据则会自动与核心数据保持一致。

  1. 运用『封装记录』对多个函数共用的数据记录加以封装。
  2. 对于使用该记录结构的每个函数,运用『搬移函数』将其移入新类。
  3. 用以处理该数据记录的逻辑可以用『提炼函数』提炼出来,并移入新类。

# 函数组合成变换

先用源 数据创建一个类,再把相关的计算逻辑搬移到类中
如果代码中会对源数据做更新,那么使用类要好得多;如果使用变换,派生数据会被存储在新生成的记录中,一旦源数据被修改,就会遭遇数据不一致。

  1. 创建一个变换函数,输入参数是需要变换的记录,并直接返回该记录的值。
  2. 挑选一块逻辑,将其主体移入变换函数中,把结果作为字段添加到输出记录中。修改客户端代码,令其使用这个新字段。
  3. 测试。
  4. 针对其他相关的计算逻辑,重复上述步骤。

# 拆分阶段

一段代码在同时处理两件不同的事,就把它拆分成各自独立的模块

  1. 将第二阶段的代码提炼成独立的函数。
  2. 测试。
  3. 引入一个中转数据结构,将其作为参数添加到提炼出的新函数的参数列表中。
  4. 测试。
  5. 逐一检查提炼出的“第二阶段函数”的每个参数。如果某个参数被第一阶段用 到,就将其移入中转数据结构。每次搬移之后都要执行测试。

# 封装

上次更新: 2021/12/05, 15:49:37
Webpack原理
vue源码

← Webpack原理 vue源码→

最近更新
01
代码片段
04-22
02
koa全家桶
03-29
03
mocks项目复盘
03-29
更多文章>
Theme by Vdoing | Copyright © 2021-2022 coderly | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式