重构—改善既有代码的设计
概述
1.1 参考资料
- 《重构-改善既有代码的设计》读后总结
- 《重构改善既有代码的设计》
- 22种代码的坏味道,一句话概括
1.2 何谓重构
首先要说明的是:视上下文不同,重构的定义可以分为名词和动词两种形式。
1. 重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
2. 重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
根据重构的定义可知,重构其实就是优化代码的结构,使其阅读性更好。需要强调的是:重构不能改变软件可观察的行为——重构之后软件功能一如既往。任何用户,不论最终用户或其它程序员,都不知道已经有东西发生了变化。
1.3 为何重构
重构可以帮助你始终良好的控制自己的代码。重构就是一个工具,它可以用于以下几个目的。
1.3.1 重构改进软件设计
当人们只为短期目的,或是在完全理解整体设计之前,就贸然修改代码,程序将逐渐失去自己的结构,就很难通过阅读源码而理解原来的设计。重构很像是在整理代码,你所做的就是让所有东西回到应处的位置上。代码结构的流失通常是累积性的,因此经常性的重构可以帮助代码维持自己该有的形态。
1.3.2 重构使软件更容易理解
其实写程序,就是和计算机在交谈:你编写代码告诉计算机做什么事,它的响应则是精确按照你的指示行动。当然除了计算机外,你的源码还有其他阅读者:未来可能会有另一位程序员尝试读懂你的代码并做一些修改。我们写代码的时候很容易忘记这位未来的程序员,但他才是最重要的。如果一个程序员不理解你的代码,可能需要很长的时间来修改代码——而他理解了你的代码,这个修改或许只需要花费一个小时。
其实很多时候那个未来修改你程序的开发者就是你自己。此时重构就显得尤其重要。利用重构可以协助你理解不够熟悉的代码,而且重构会把你带到更高的层次上。
1.3.3 重构帮忙找到bug
对代码的理解,可以更好的找到bug。如果对代码进行重构,就可以深入理解代码的功能,搞清楚程序结构的同时,就更容易找出程序的bug。
1.3.4 重构提高编程速度
其实前面提到的一切都归结到最后一点:重构帮你更快速的开发程序。
听起来有点儿违反直觉。可以看出重构能够提高质量。如改善设计、提升可读性、减少错误,这些都是提高质量。但这难道不会降低开发速度吗?
良好的设计是快速开发的根本——事实上,拥有良好的设计才可能做到快速开发。如果没有良好的设计,或许某一段时间内你的进展迅速,但是不好的设计很快会让你的速度慢下来。时间最终都浪费在调试上面。因为你必须花更多的时间去理解系统、寻找重复代码。如此的循环下去。
良好的设计是维持软件开发速度的根本。重构可以帮助你更快速地开发软件,因为重构可以提高设计质量,避免重复的工作。
1.4 何时重构
三次法则 第一次只管做,第二次会产生反感,第三次就应该重构
添加功能时重构 当给软件添加新特性不方便时候,就应该重构
修补错误时重构 对代码进行重构,可以更方便的发现程序中的bug
复审代码时重构 重构可以帮助复审别人的代码
2 代码的坏味道
如果一段代码是不稳定或者有一些潜在问题,那么代码往往会包含一些明显的痕迹。正如食物要腐坏之前,经常会发出一些异味一样。Martin Fowler把有问题的代码称为“代码的坏味道”。接下来本文对22种代码的坏味道进行整理。
2.1 重复代码
Duplicated Code ——————— (重复代码) 难维护。
解决方法:提取公共函数。
2.2 过长函数
Long Method ————————–(过长函数) 难理解
解决方法:拆分成若干函数
2.3 过大的类
Large Class—————————-(过大的类) 难用、难理解
解决办法:拆分成若干类,过程中若遇到Duplicated Code,就提取公共函数。
2.4 过长参数列表
Long parameter List——————(参数多)调用难
解决方法:将参数封装成结构或者类
2.5 发散式变化
Divergent Change———————(发散式变化)发散式修改,改好多需求,都会动它。
解决方法:拆,将总是一起变化的东西放在一块儿。
2.6 霰弹式修改
Shotgun Surgery———————(霰弹式修改)散弹式修改,改某个需求时,都会动他。
解决方法:将各个修改点,集中起来,抽象成一个类。
2.7 依恋情结
Feature Envy———————(红杏出墙的函数)使用了大量其它类的成员。
解决方法:将这个函数挪到那个类里面。
2.8 数据泥团
Data Clumps———————(数据团)。
解决方法:他们那么有基情,就在一起吧,给他们一个新的类。
2.9 基本类型偏执
Primitive Obsession———————(偏爱基本类型) 热衷于使用int,double等基本类型。
解决方法:反复出现的一组参数,有关联的多个数组换成类。
2.10 Switch惊悚现身
Switcth Statements———————(switch 语句)。
解决方法:state/strategy或者时简单的多态。
2.11 平行继承体系
Parallel Inheritance Hierarchies———————(平行继承)增加A类的子类ax,B类也需要相应的增加一个bx。
解决方法:应该有一个类时可以去掉继承关系的。
2.12 冗赘类
Lazy Class———————(冗赘类) 如果他不干活了,炒掉他吧。
解决方法:把这些不再重要的类里面的逻辑,合并到相关类,删掉旧的。
Speculative Generality———————(夸夸其谈未来性)。
解决方法:删掉。
2.13 令人迷惑的暂时字段
Temporary Field———————(临时字段)发散式修改,改好多需求,都会动他。
解决方法:将这些临时变量集中到一个新类中管理。
2.14 过度耦合的消息链
Message Chains———————(消息链) 过度耦合才是坏的。
解决方法:拆函数或移动函数。
2.15 中间人
Middle Man———————(中间人) 大部分都交给中间人来处理了。
解决方法:用继承替代委托。
2.16 狎昵关系
Inappropriate Intimacy———————(太亲密) 相似的类,有不同接口。
解决方法:划清界限拆散,或合并,或改成单项联系。
2.17 异曲同工的类
Alternative Classes with Different Interfaces———————(相似的类,有不同接口)。
解决方法:重命名、移动函数、或抽象子类。
2.18 不完美的类库
Incomplete Lirary Class———————(不完美类库)。
解决方法: 包一层函数或包成新的类。
2.19 纯稚的数据类
Data Class———————(纯数据类)类很简单,仅有公共成员变量,或简单操作函数。
解决方法:将相关操作封装进去,较少public成员变量。
2.20 被拒绝的遗赠
Refused Bequest———————(继承过多) 父类里面方法很多,子类只用有限几个。
解决方法:用代理替代继承关系。
2.21 过多的注释
Comments———————(太多注释)这里指代码太难动了,不得不用注释解释。
解决方法:避免用注释解释代码,而是说明代码的目的,背景等。好代码会说话。
3 重构方法举例
3.1 重构函数
3.1.1 重复代码
这种情况应该很多人都遇到过,编程过程中要尽量避免重复的代码,解决方法是将重复的内容提炼到一个单独的函数中。
void A() {.....System.out.println("name" + _name);
}void B() {.....System.out.println("name" + _name);
}
将代码更改为↓
void A() { .... }void B() { .... }void printName(String name) {System.out.println("name" + name);
}
3.1.2 内联临时变量
如果你对一个变量只使用了一次,那就不妨对它进行一次重构。
int basePrice = order.basePrice();
return (basePrice > 100);
更改为↓
return (order.basePrice() > 1000);
3.1.3 尽量去掉临时变量
临时变量多了会难以维护,所以尽量去掉所使用的临时变量。
int area = _length * _width;
if (area > 1000) return area * 5;
elsereturn area *4;
更改为↓
if (area() > 1000) return area() * 5;
elsereturn area() *4;int area() {return _length * _width;
}
3.1.4 引入解释性变量
跟上面那个相反,如果使用函数变得很复杂,可以考虑使用解释型变量了。
if ((platform.toUpperCase().indexOf("mac") > -1) &&(brower.toUpperCase().indexOf("ie") > -1) &&wasInitializes() && resize > 0) {......}
更改为↓
final boolean isMacOS = platform.toUpperCase().indexOf("mac") > -1;
final boolean isIEBrowser = brower.toUpperCase().indexOf("ie") > -1;
final boolean wasResized = resize > 0;if (isMacOS && isIEBrowser && wasInitializes() && wasResized) {......
}
3.1.5 移除对参数的赋值
参数传入函数中,应该尽量避免对其进行更改。
int discount (int inputVal, int quantity, int yearToDate) {if (inputVal > 50) inputVal -= 2;
}
更改为↓
int discount (int inputVal, int quantity, int yearToDate) {int result = inputVal;if (result > 50) result -= 2;
}
另外,函数中声明的临时变量最好只被赋值一次,如果超过一次就考虑再声明变量对其进行分解了。
一个函数也不应该太长,如果太长首先影响理解,其次包含的步骤太多会影响函数复用。做法是将里面的步骤提取为很多小函数,并且函数命名要体现出函数做了什么,清晰明了。
3.2 重构类
3.2.1 搬移方法
每一个方法应该放在最适合的位置,不能随便乱放,所以很多时候你需要考虑,一个方法在这里是不是最适合的。
class Class1 {aMethod();
}class Class2 {}
更改为↓
class Class1 {}class Class2 {aMethod();
}
3.3 搬移字段
每一个字段,变量都应该放到其自己属于的类中,不能随便放,不属于这个类中的字段也需要移走。
class Class1 {aField;
}class Class2 {}
更改为↓
class Class1 {}class Class2 {aField;
}
3.4 提炼一个新类
将不属于这个类中的字段和方法提取到一个新的类中。所以说在你写代码的时候一定要考虑放这里是不是合适,有没有其他更合适的地方?
提炼到新的类中↓
3.5 简化条件表达式
3.5.1 分解条件表达式
有时候看着一个if else语句很复杂,我们就试着把它分解一下。
class Person {private String name;private String officeAreaCode;private String officeNumber;public String getTelephoneNumber() { ..... }
}
更改为↓
class TelephoneNumber {private String areaCode;private String number;public String getTelephoneNumber() { ..... }
}class Person {private String name;private TelephoneNumber _officeNumber;
}
当然实际情况可能复杂的多,这样的重构才显得有意思,这里只是让大家脑子里有一个这样的思想,以后遇见这样的情况能想起来可以这样子重构。
3.5.2 分解条件表达式
有时我们写的多个if语句是可以合并到一起的。
if (isUp(case) || isLeft(case)) num = a * b;
else num = a * c;
更改为↓
if (isTrue(case)) numberB(a);
else numberC(a);boolean isTrue(case) {return isUp(case) || isLeft(case);
}int numberB(a) {return a + b;
}int numberC(a) {return a + c;
}
3.5.3 合并重复的条件片段
有时候你可能会在if else 语句中写重复的语句,这时候你需要将重复的语句抽出来。
if (isSpecialDeal()) {total = price * 0.95;send();
} else {total = price * 0.98;send();
}
更改为↓
if (isSpecialDeal())total = price * 0.95;
elsetotal = price * 0.98;send();
3.5.4 以卫句取代嵌套表达式
这个可能有点难以理解,但是我感觉用处还是比较大的,就是加入return语句去掉else语句。
if (a > 0) result = a + b;
else {if (b > 0) result = a + c;else {result = a + d;}
}
return result;
更改为↓
if (a > 0) return a + b;
if (b > 0) return a + c;
return a + d;
是不是变得很简单,加入卫语句就是合理使用return关键字。有时候反转条件表达式也能简化if else语句。
3.5.5 以多态取代switch语句
这个我感觉很重要,用处非常多,以后你们写代码的时候只要碰到switch语句就可以考虑能不能使用面向对象的多态来替代这个switch语句呢?
int getArea() {switch (_shap)case circle:return 3.14 * _r * _r; break;case rect;return _width + _heigth;
}
更改为↓
class Shap {int getArea(){};
}class Circle extends Shap {int getArea() {return 3.14 * _r * _r; break;}
}class Rect extends Shap {int getArea() {return _width + _heigth;}
}
然后在调用的时候只需要调用Shap的getArea()方法就行了,就可以去掉switch语句了。然后我们还可以在一个方法中引入断言,这样可以保证函数调用的安全性,让代码更加健壮。
4 总结
文中只是把常用到的,比较好表述的重构方法或情况总结了一下,并没有覆盖到书中的所有情况,如果对重构非常有兴趣的话建议大家阅读原书。
重构—改善既有代码的设计相关推荐
- PHP 杂谈《重构-改善既有代码的设计》之二 对象之间搬移特性
思维导图 索引: Ø Move Method(搬移函数) Ø Move Field (搬移值域) Ø Extract Class (提炼类) Ø Inline Class (将类内联化,就是把当前的类 ...
- 『重构--改善既有代码的设计』读书笔记----序
作为C++的程序员,我从大学就开始不间断的看书,看到如今上班,也始终坚持每天多多少少阅读技术文章,书看的很多,但很难有一本书,能让我去反复的翻阅.但唯独『重构--改善既有代码的设计』这本书让我重复看了 ...
- PHP 杂谈《重构-改善既有代码的设计》之三 重新组织数据
介绍 承接上文的PHP 杂谈<重构-改善既有代码的设计>之 重新组织你的函数继续重构方面的内容. 这章主要针对数据的重构. 1.争论的声音--直接访问Field还是通过函数(Accesso ...
- 重构 改善既有代码的设计:代码的坏
以下内容来自<<重构 改善既有代码的设计>> 一.什么是重构 所谓重构(Refactoring)是这样一个过程:在不改变代码外在行为的前提下,对代码做出修改以改进程序的内部结构 ...
- 《重构-改善既有代码的设计》-第1例:租赁影片(2)
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. 上接 重构-改善既有代码的设计-第1例:租赁影片(1) 2 运用多态取代与价格相关的条件逻辑 2 ...
- 《重构-改善既有代码的设计》-第1例:租赁影片(1)
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. 买了<重构 - 改善既有代码的设计 >一书,一直没有好好看,大致过了下也觉得只是有点点印 ...
- 实践提高《重构改善既有代码的设计第2版》PDF中文+PDF英文+对比分析
重构是编程的基础,是在不改变外部行为的前提下,有条不紊地改善代码.编程爱好者都知道,Martin Fowler 的<重构:改善既有代码的设计>已经成为全球有经验的程序员手中的利器,既可用来 ...
- 重构改善既有代码的设计(github源码)
refactoring improving the design of existing code(重构改善既有代码的设计) https://github.com/CoderDream/refacto ...
- 重构:改善既有代码的设计(软件开发的不朽经典)
重构:改善既有代码的设计(软件开发的不朽经典) 基本信息 作者: (美)Martin Fowler 译者: 熊节[同译者作品] 丛书名: 图灵程序设计丛书 出版社:人民邮电出版社 ISBN:978 ...
最新文章
- python评分卡建模-卡方分箱(2)之代码实现
- 征战蓝桥 —— 2016年第七届 —— C/C++A组第4题——快速排序
- JVM源码分析之synchronized实现
- 内核kernel以及根文件系统rootfs是如何映射到对应的nand flash的
- LeetCode 140. 单词拆分 II(DP+回溯)
- 数据结构基础(17) --二叉查找树的设计与实现
- AMI corpus download
- python封装sql脚本_flask-sqlalchemy如何使用原生的sql语句然后封装?
- 自动生成一列不重复数据库
- 计算机操作系统(第四版)课后习题答案西电版
- 【好书推荐】-你的灯亮着吗?
- Android安全 Hook技术,Android下通过hook技术实现透明加解密保障数据安全
- 练习京东顶部导航条、背景、渐变、按钮练习(雪碧图)、渐变
- 阿里云ACA试题——云安全
- Unix/Linux编程:Internet domain socket
- MapperReducer
- MFC对话框标题栏颜色自绘,标题栏由过渡色组成,自绘关闭按钮
- Linux内核4.14版本——SPI NOR子系统(2)——spi-nor.c分析
- 使用python中PIL库进行切图时候出现了图片全黑
- 用百度搜索SB,为什么是google排第一?
热门文章
- MySQL 命令行导出、导入Select 查询结果
- Mysql使用kill命令解决死锁问题(杀死某条正在执行的sql语句)
- PHP OPCode缓存:APC详细介绍
- php 获取时间段 今天昨天本周上周本月上月本季度本年去年
- python扫描端口脚本_Pyhton扫描端口脚本代码
- java反序列化 构造函数_FastJson反序列化和构造函数之间的一点小秘密
- recv返回值为0_基于GNES和Tensorflow 2.0的大规模视频语义搜索
- 小米路由器sn算ssh密码_【玩转路由】小米路由器开启SSH
- swiper用loop不出图片_swiper实现双向控制
- jquery 控制CSS属性display 实现元素的显示、隐藏