重构之改善既有代码的设计(一)
1.1 何为重构,为何重构
第一个定义是名词形式:
重构(名词):对软件内部结构的一种调整,目的是在不改变「软件可察行为」前提下,提高其可理解性,降低修改成本。
「重构」的另一个用法是动词形式:
重构(动词):使用一系列重构准则手法,在不改变「软件可察行为」前提 下,调整其结构。
-
改进软件设计,使软件更易被理解。
ps:重构是一种经济适用行为,而非道德使然,如果它不能让我们更快更好的开发,那么它是毫无意义。
-
重构对个体程序员的意义是提高ROI。
- 更快速的定位问题,节省调试时间。
- 最小化变更风险,提高代码质量,减少修复事故的时间。
- 得到程序员同行的认可,更好的发展机会。
-
重构对整个研发团队的意义是战斗力的提升。
1.2 什么时候需要重构?
三次法则;
添加功能更时重构;
修补错误时重构;
复审代码时重构;
- Code review : 在给别人code review时嗅出坏味道,在不失礼貌的前提下提出建议。
- 每次 commit 代码时: 每一次经你之手提交的代码都应该比之前更加干净。
- 当你接手一个异常难读的项目时: 说服项目组将重构作为一项需求任务来做。
- 当迭代效率低于预期时: 将重构当作一个项任务专门来做,必要的时候停下来迭代需求。
重构过程中关于两顶帽子的比喻:
使用重构技术开发软件时,你把自己的时间分配给两种截然不同的行为:「添加新功能」和「重构」。
添加新功能时,你不应该修改既有代码,只管添加新功能。重构时你就不能再添加功能,只管改进程序结构。
软件开发过程中,你可能会发现自己经常变换帽子。首先你会尝试添加新功能,然后觉得把程序结构改一下,功能的添加会容易得多。于是你换一顶帽 子,做一会儿重构工作。接着重复该过程。整个过程或许只花十分钟,但无论何时你都应该清楚自己戴的是哪一顶帽子。
二 代码/架构的坏味道
何时重构?书中告诉了你一些迹象,它会指出「这里有一个可使用重构解决的问题」。
详细可参考:导图,篇幅所限,下面举例了其中15条,重点在介绍这种“坏味道”,相应的应对方法可以参考:https://www.itzhai.com/articles/bad-code-small.html
2.1 Mysterious Name(神秘命名)
好的名字能节省未来用在猜谜上的大把时间。

源代码:
function getPrice(order) {const a = order.quantity * order.itemPriceconst b = Math.max(0, order.quantity - 500) * order.itemPrice * 0.05const c = Math.min(basePrice * 0.1, 100)return a - b + c
}
改进:
function getPrice(order) {// 获取基础价格const basePrice = order.quantity * order.itemPrice// 获取折扣const quantityDiscount = Math.max(0, order.quantity - 500) * order.itemPrice * 0.05// 获取运费const shipping = Math.min(basePrice * 0.1, 100)// 计算价格return basePrice - quantityDiscount + shipping
}
2.2 Duplicated Code(重复的代码)
“如果你在一个以上的地点看到相同的代码结构,那么可以肯定:设法将它们合而为一,程序会变得更好。一旦有重复代码存在,阅读这些重复的代码时你就必须加倍仔细,留意其间细微的差异。如果要修改重复代码,你必须找出所有的副本来修改。”
2.3 Long Method(过长函数)
“据我们的经验,活得最长、最好的程序,其中的函数都比较短。初次接触到这种代码库的程序员常常会觉得“计算都没有发生”——程序里满是无穷无尽的委托调用。但和这样的程序共处几年之后,你就会明白这些小函数的价值所在。间接性带来的好处——更好的阐释力、更易于分享、更多的选择——都是由小函数来支持的。”
2.4 Large Class(过大类)

2.5 Long Parameter List(过长参数列)
“刚开始学习编程的时候,老师教我们:把函数所需的所有东西都以参数的形式传递进去。这可以理解,因为除此之外就只能选择全局数据,而全局数据很快就会变成邪恶的东西。但过长的参数列表本身也经常令人迷惑。”
public class LongParameterListExample {public void processData(String name, String address, int age, String gender, String occupation, String phoneNumber, String email) {// process data here}
}
上面的代码定义了一个方法processData
,它有7个参数。在这个例子中,我们可以发现参数列非常长,不利于程序的可读性和可维护性。这是一个典型的过长参数列的例子。
2.6 Divergent Change(发散式变化)
指一个类受多种变化的影响。
你发现你想要修改一个函数,却必须要同时修改许多不相关的函数。例如,当你想要添加一个新的产品类型时,你需要同步修改对产品进行查找、显示、排序的函数。
2.7 Shotgun Surgery(霰弹式修改)
多种变化引发多个类相应的修改。
任何修改都需要在许多不同类上做小幅度修改。
可能原因:一个单一的职责被拆分成大量的类。
注意霰弹式修改 与 发散式变化 区别 : 发散式变化是在一个类受多种变化影响, 每种变化修改的方法不同, 霰弹式修改是 一种变化引发修改多个类中的代码。
2.8 Feature Envy(依恋情结)
函数大量地使用了另外类的数据。这种情况下最好将此函数移动到那个类中。
函数对某个class的兴趣高过对自己所处之 class的兴趣。无数次经验里,我们看到某个函数为了计算某值,从另一个对象那儿调用几乎一半以上的取值函数。 影响:数据和行为不在一处,修改不可控。 解决方案:让数据和行为在一起,通过 Extract Method(提炼函数)和Move Method(搬移函数)的方法来处理,这函数到该去的地方。
2.9 Data Clumps(数据泥团)
数据泥团指的是经常一起出现的数据,比如每个方法的参数几乎相同,处理方式与过长参数列的处理方式相同,用Introduce Parameter Object(引入参数对象)将参数封装成对象。
2.10 Primitive Obsession(基本型别偏执)
写代码时总喜欢用基本类型来当参数,而不喜欢用对象。当要修改需求和扩展功能时,复杂度就增加了。
2.11 Lazy Element(冗赘的元素)
去除多层不必要的包装。
如:方法a中包的是b,b包的是c,c包的是d。但是bc只是基于某种考虑的纯粹包装,而从未有其他变化,这时可以让a直接包d,bc就去掉吧。
class Customer {private String name;private String address;private String city;private String state;private String zip;private String phone;private String email;public Customer(String name, String address, String city, String state, String zip, String phone, String email) {this.name = name;this.address = address;this.city = city;this.state = state;this.zip = zip;this.phone = phone;this.email = email;}public String getEmail() {return email;}
}class Order {private Customer customer;private int total;public Order(Customer customer, int total) {this.customer = customer;this.total = total;}public String getCustomerEmail() {return customer.getEmail();}
}
在上面的代码中,我们定义了一个Order
类和一个Customer
类,其中Order
类知道Customer
类的详细信息,但仅使用Customer
的电子邮件。这是一个冗赘的元素,因为只需要知道用户的电子邮件,但是却存储了大量未使用的数据。在这种情况下,重构可能会改为:
class CustomerEmail {private String email;public CustomerEmail(String email) {this.email = email;}public String getEmail() {return email;}
}class Order {private CustomerEmail customerEmail;private int total;public Order(CustomerEmail customerEmail, int total) {this.customerEmail = customerEmail;this.total = total;}public String getCustomerEmail() {return customerEmail.getEmail();}
}
现在,我们只存储所需的信息,而不是冗赘的信息,这样可以使代码更简洁。
2.12 Message Chains(过长的消息链)
向一个对象请求另一个对象,然后再向后者请求另一个对象,然后再请求另一个对象……
未充分的考虑数据结构的读取场景,导致在需要使用某些数据的时候无法简单的获得其引用,或者为了使用某个字段,需要了解一堆中间封装的数据结构。
a.b.c.d.e()
2.13 Middle Man(中间人)
对象的基本特征之一就是封装——对外部世界隐藏其内部细节。封装往往伴随着委托。比如,你问主管是否有时间参加一个会议,他就把这个消息“委托”给他的记事簿,然后才能回答你。很好,你没必要知道这位主管到底使用传统记事簿还是使用电子记事簿抑或是秘书来记录自己的约会。
但是人们可能过度运用委托。你也许会看到某个类的接口有一半的函数都委托给其他类,这样就是过度运用。这时应该使用移除中间人,直接和真正负责的对象打交道。如果这样“不干实事”的函数只有少数几个,可以运用内联函数把它们放进调用端。如果这些中间人还有其他行为,可以运用以委托取代超类或者以委托取代子类把它变成真正的对象,这样你既可以扩展原对象的行为,又不必负担那么多的委托动作。
2.14 Refused Bequest(被拒绝的遗赠)
子类应该继承超类的函数和数据。但如果它们不想或不需要继承,又该怎么办呢?它们得到所有礼物,却只从中挑选几样来玩!
按传统说法,这就意味着继承体系设计错误。你需要为这个子类新建一个兄弟类,再运用函数下移和字段下移把所有用不到的函数下推给那个兄弟。这样一来,超类就只持有所有子类共享的东西。你常常会听到这样的建 议:所有超类都应该是抽象(abstract)的。
2.15 注释(Comments)

- 废话注释;
- 与代码逻辑不一致的注释;
- 尽量让提炼的函数和精炼易懂的命名减少注释的必要;
参考资料
https://refactoringguru.cn/
速看笔记版
https://www.itzhai.com/articles/refactoring-cheat-sheet.html
https://www.itzhai.com/articles/bad-code-small.html
《重构》笔记—坏代码的味道与处理
坏味道与重构手法速查表
相关文章:

重构之改善既有代码的设计(一)
1.1 何为重构,为何重构 第一个定义是名词形式: 重构(名词):对软件内部结构的一种调整,目的是在不改变「软件可察行为」前提下,提高其可理解性,降低修改成本。 「重构」的另一个用…...
Kotlin data class 数据类用法
实验数据 {"code":1,"message":"成功","data":{"name":"周杰轮","gender":1} }kotlin数据类使用方便提供如下内部Api: equals()/hashCode()对 toString() componentN()按声明顺序与属性相…...

随笔-老子不想牺牲了
18年来到这个项目组,当时只有8个人,包括经常不在的架构师和经理。当时的工位在西区1栋A座,办公桌很宽敞。随着项目的发展,入职的人越来越多,项目的工位也是几经搬迁。基本上每次搬迁时,我的工位都是挑剩下的…...

三种查找Windows10环境变量的方法
文章目录一.在设置中查看二. 在我的电脑中查看三. 在资源管理器里查看一.在设置中查看 在系统中搜索设置 打开设置,在设置功能里,点击第一项 系统 在系统功能里,左侧菜单找到关于 在关于的相关设置里可以看到高级系统设置 点击高级系…...

STM32单片机DS18B20测温程序源代码
OLED液晶屏电路接口DS18B20电路接口STM32单片机DS18B20测温程序源代码#include "sys.h"#define LED_RED PBout(12)#define LED_GREEN PBout(13)#define LED_YELLOW PBout(14)#define LED_BLUE PBout(15)#define DS18B20_IO_IN() {GPIOA->CRL&0XFFFFFFF0;GPIOA…...

java日志查看工具finder介绍
目录 一、finder介绍 二、单节点部署 1、服务器需要安装Tomcat,以2.82.16.35为例 2、进入Tomcat下目录webapps下,创建FIND目录,进入FIDN目录 3、下载findweb插件,解压缩 4、登录页面,配置 5、添加日志路径 三、…...
手写现代前端框架diff算法-前端面试进阶
前言 在前端工程上,日益复杂的今天,性能优化已经成为必不可少的环境。前端需要从每一个细节的问题去优化。那么如何更优,当然与他的如何怎么实现的有关。比如key为什么不能使用index呢?为什么不使用随机数呢?答案当然…...

【半监督医学图像分割 2022 MICCAI】CLLE 论文翻译
文章目录【半监督医学图像分割 2022 MICCAI】CLLE 论文翻译摘要1. 简介2. 方法2.1 半监督框架概述2.2 监督局部对比学习2.3 下采样和块划分3. 实验4. 结论【半监督医学图像分割 2022 MICCAI】CLLE 论文翻译 论文题目:Semi-supervised Contrastive Learning for Labe…...

vivo官网App模块化开发方案-ModularDevTool
作者:vivo 互联网客户端团队- Wang Zhenyu 本文主要讲述了Android客户端模块化开发的痛点及解决方案,详细讲解了方案的实现思路和具体实现方法。 说明:本工具基于vivo互联网客户端团队内部开源的编译管理工具开发。 一、背景 现在客户端的业…...
Python基础-数据类型之数字类型
变量中的变量值是用来存储事物状态的,事物的状态分成不同的种类(例如:人的姓名、年龄,身高、职位、工资等),因此变量值有多种不同的数据类型。 age 18 # 用整型记录年龄 salary 3.1 # 用浮点型记录…...

基于Web的6个完美3D图形WebGL库
现代前端、游戏和Web开发正是WebGL可以转化为数字杰作的东西。使用GPU绘制在浏览器屏幕上生成的矢量元素,WebGL创建交互式Web图形,从而获得用户体验。视觉元素的质量和复杂性使该工具在HTML或CSS等其他方法中脱颖而出。WebGL基础WebGL不是一个图形套件。…...

界面组件DevExpress Reporting v22.2 - 增强的Web报表组件UI
DevExpress Reporting是.NET Framework下功能完善的报表平台,它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集,包括数据透视表、图表,因此您可以构建无与伦比、信息清晰的报表。DevExpress Reporting v22.2版本已正式发布&…...

初学vector
目录 string的收尾 拷贝构造的现代写法: 浅拷贝: 拷贝构造的现代写法: swap函数: 内置类型有拷贝构造和赋值重载吗? 完善拷贝构造的现代写法: 赋值重载的现代写法: 更精简的现代写法&…...
Windows10 安装wsl2、Ubuntu相关操作
Windows10 安装wsl2、Ubuntu相关操作 安装wsl2 查看本机windows版本: 键盘上按下winr,输入winver,查看系统版本。必须运行 windows 10 版本 2004 及更高版本(内部版本 19041 及更高版本)或 windows 11。满足版本要求后…...
SpringBoot简单使用MongoDB
MongoDB介绍 SpringBoot简单使用MongoDB 一、配置步骤 1、application.yml 2、pom 3、entity 4、mapper 二、案例代码使用 1、库 前期准备上一篇安装MongoDB地址http://t.csdn.cn/G4oYJ 跟关系型数据库概念对比 Mysql MongoDB Database(数据库) Datab…...

Oracle Data Guard 角色转换(Role Transitions)
查询视图V$DATABASE的DATABASE_ROLE列可以看到数据库当前的角色。 1.角色转换介绍 Oracle Data Guard让你可以使用SQL语句或者通过Oracle Data Guard broker界面来动态更改数据库的角色,Oracle Data Guard支持以下的角色转换: 1࿰…...

opencv的TrackBar控件
大家好,我是csdn的博主:lqj_本人 这是我的个人博客主页: lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…...

关于基线长度对双天线GNSS测姿精度的影响
文章目录一、GNSS测姿原理1. 载波相位双差求解基线向量2. GNSS姿态角表示二、基线长度对GNSS测姿精度的影响三、GNSS定向产品精度描述实例四、参考文献在GNSS定向模块或者板卡的指标参数中,我们一般会看到航向的测量精度和基线的长度相关。在实际使用,用…...
口交换机睿易 RG-NBS1826GC 24 口
接口形态不将就,标配光纤接口传输性能不将就,标配千兆上联口和大缓存设计端口数量不将就,8/16/24 三种选择楼宇对讲交换机不将就,保证开锁指令品质服务不将就,监控专用交换机接口形态不将就,标配光纤接口非…...

如何在Excel中向下拉列表中添加条件
在Excel中向下拉列表中添加条件 创建矩阵型数据集创建下拉列表创建第一个下拉列表创建第二个下拉列表你可以使用Microsoft Excel下拉列表来显示一个简单的列表,尽管有时需要更多的控制。假设你的人员分散在四个地区:北部、南部、东部和西部。你希望按地区与人员合作,而不是与…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...

C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...

2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...