重构之改善既有代码的设计(一)
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下拉列表来显示一个简单的列表,尽管有时需要更多的控制。假设你的人员分散在四个地区:北部、南部、东部和西部。你希望按地区与人员合作,而不是与…...
自定义bean 加载到spring IOC容器中
自定义bean加载到spring容器中的两种方式: 1.在类上添加注解Controller、RestController(本质是Controller)、Service、Repository、Component2.使用Configuration和Bean 这篇文章主要介绍第二种方式原理(因为在实际使用中&#…...
[python入门㊻] - python装饰器和类的装饰器
目录 ❤ python装饰器介绍 ❤ 什么是装饰器 ❤ 装饰器的流程 ❤ 定义装饰器时通常会涉及以下3个函数 无参装饰器 有参装饰器 多重装饰器 ❤ 装饰器的用法(闭包) ❤ 装饰器语法糖 ❤ 时间计时器 ❤ 装饰器中wraps作用 不使用wraps装饰器 使用wraps装饰器解…...
企业级信息系统开发学习1.1 初识Spring——采用Spring配置文件管理Bean
文章目录一、Spring容器演示——采用Spring配置文件管理Bean(一)创建Maven项目(二)添加Spring依赖(三)创建杀龙任务类(四)创建勇敢骑士类(五)采用传统方式让勇…...
CSS盒子模型
盒子模型 CSS三大特性 继承性、层叠性、优先级 优先级比较 继承 < 通配符选择器 < 标签选择器 < 类选择器 < id选择器 < 行内样式 < !important 注意:!important不能提升继承的优先级,只要是继承优先级最低 复合选择器权重叠加计…...
Python基础学习笔记 —— 数据结构与算法
数据结构与算法1 数据结构基础1.1 数组1.2 链表1.3 队列1.4 栈1.5 二叉树2 排序算法2.1 冒泡排序2.2 快速排序2.3 (简单)选择排序2.4 堆排序2.5 (直接)插入排序3 查找3.1 二分查找1 数据结构基础 本章所需相关基础知识:…...
笔记本连接wifi,浏览器访问页面,显示访问被拒绝
打开chrome、edge浏览器访问第1个第2个页面正常,后面再打开页面显示异常。 但手机连接正常,笔记本连接异常,起初完全没有怀疑是wifi问题 以为用了vpn软件问题,认为中了病毒。杀毒,并没有中毒。 1、关闭vpn代理&#…...
36个物联网专业毕业论文选题推荐
物联网技术在智能家居系统中的应用研究物联网在智慧城市建设中的作用物联网在工业4.0中的实现与发展 物联网与智能物流系统的结合物联网与医疗健康领域的融合研究物联网与环境监测系统的集成物联网与农业生产的结合研究物联网技术对汽车行业的影响与发展物联网在智能安防领域的…...
Pytorch复习笔记--torch.nn.functional.interpolate()和cv2.resize()的使用与比较
1--前言 博主在处理图片尺度问题时,习惯使用 cv2.resize() 函数;但当图片数据需用显卡加速运算时,数据需要在 GPU 和 CPU 之间不断迁移,导致程序运行效率降低; Pytorch 提供了一个类似于 cv2.resize() 的采样函数&…...
ASP.NET Core MVC 项目 AOP之ActionFilterAttribute
目录 一:说明 二:实现ActionFilterAttribute父类 一:说明 ActionFilterAttribute比前两者简单方便,易于扩展,不易产生代码冗余。 ActionFilterAttribute过滤器执行顺序: 1:执行控制器中的构造函数,实例化控制器 2:执行ActionFilterAttribute.OnActionExecutionA…...
浅析EasyCVR安防视频能力在智慧小区建设场景中的应用及意义
一、行业需求 城市的发展创造了大量工作机会,人口的聚集也推动了居民住宅建设率的增长。人民生活旨在安居乐业,能否住得“踏实”是很多劳动工作者最关心的问题。但目前随着住宅小区规模的不断扩大、人口逐渐密集,在保证居住环境舒适整洁的同…...
海宁长安网站开发/百度灰色关键词排名代做
一、获取Code 假设我们需要网页授权的页面的地址为redirect_uri 需要获取Code的话我们第一步是跳转到授权地址,我们第一步便是获取拼接授权地址 --采用snsapi_base方式 public partial class WebAuth : System.Web.UI.Page{string Appid "XXXXXXXXXXXXXXXXXX…...
网罗天下做网站靠谱吗/seo交流qq群
控制语句 分支分流 循环语句判断语句 if...else...ifif 条件语句(比较 逻辑 成员运算符in)用法1:if 条件语句:子语句age 20if age > 18:#当条件是Ture时执行子语句print("恭喜你,你成年了")特殊:sif s:print("子语句执行了")这种情况等于判空操作,是空…...
做国际贸易有哪些平台/优化大师免安装版
2019独角兽企业重金招聘Python工程师标准>>> 两个项目,一个生产者一个消费者,这里只贴出关键代码(队列模式和订阅模式),文章最后会附上项目地址,有需要的可以自行下载。项目访问地址http://loca…...
电销如何介绍网站建设/做销售有什么技巧和方法
全自动无人值守安装,采用万能GHOST技术,安装系统过程只需5-8分钟,适合新旧各种机型。集成DX9.0c最新版、双核补丁。集成常用VB/VC2005、2008、2010、2012、2013/运行库、万能驱动助理稳定版安装过程会自动删除各分区下可能存在的AUTORUN病毒T…...
flash网站的优缺点/上海搜索优化推广
idea作为一个java开发的便利IDE工具,个人是比较喜欢的,今天来探索个小功能: 导出单个类文件为jar包!偶有这种需求,就是某个类文件独立存在,但是需要将其导出为jar,供别人临时使用,或…...
我的网站要换新域名如何做/杭州网站运营十年乐云seo
举止 如果你想让某个人为敌,只要告诉他“是你错了”。这个办法屡试不爽。作为架构师,或者这是你所努力的一个目标,那么你需确定的一点就是,你需要和单位中的各种人等打交道。调动所有人积极性的最好办法之一是你在任何环境中都举止…...