概述

1.1 参考资料

  1. 《重构-改善既有代码的设计》读后总结
  2. 《重构改善既有代码的设计》
  3. 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 总结

文中只是把常用到的,比较好表述的重构方法或情况总结了一下,并没有覆盖到书中的所有情况,如果对重构非常有兴趣的话建议大家阅读原书。

重构—改善既有代码的设计相关推荐

  1. PHP 杂谈《重构-改善既有代码的设计》之二 对象之间搬移特性

    思维导图 索引: Ø Move Method(搬移函数) Ø Move Field (搬移值域) Ø Extract Class (提炼类) Ø Inline Class (将类内联化,就是把当前的类 ...

  2. 『重构--改善既有代码的设计』读书笔记----序

    作为C++的程序员,我从大学就开始不间断的看书,看到如今上班,也始终坚持每天多多少少阅读技术文章,书看的很多,但很难有一本书,能让我去反复的翻阅.但唯独『重构--改善既有代码的设计』这本书让我重复看了 ...

  3. PHP 杂谈《重构-改善既有代码的设计》之三 重新组织数据

    介绍 承接上文的PHP 杂谈<重构-改善既有代码的设计>之 重新组织你的函数继续重构方面的内容. 这章主要针对数据的重构. 1.争论的声音--直接访问Field还是通过函数(Accesso ...

  4. 重构 改善既有代码的设计:代码的坏

    以下内容来自<<重构 改善既有代码的设计>> 一.什么是重构 所谓重构(Refactoring)是这样一个过程:在不改变代码外在行为的前提下,对代码做出修改以改进程序的内部结构 ...

  5. 《重构-改善既有代码的设计》-第1例:租赁影片(2)

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. 上接  重构-改善既有代码的设计-第1例:租赁影片(1) 2  运用多态取代与价格相关的条件逻辑 2 ...

  6. 《重构-改善既有代码的设计》-第1例:租赁影片(1)

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. 买了<重构 - 改善既有代码的设计 >一书,一直没有好好看,大致过了下也觉得只是有点点印 ...

  7. 实践提高《重构改善既有代码的设计第2版》PDF中文+PDF英文+对比分析

    重构是编程的基础,是在不改变外部行为的前提下,有条不紊地改善代码.编程爱好者都知道,Martin Fowler 的<重构:改善既有代码的设计>已经成为全球有经验的程序员手中的利器,既可用来 ...

  8. 重构改善既有代码的设计(github源码)

    refactoring improving the design of existing code(重构改善既有代码的设计) https://github.com/CoderDream/refacto ...

  9. 重构:改善既有代码的设计(软件开发的不朽经典)

    重构:改善既有代码的设计(软件开发的不朽经典) 基本信息 作者: (美)Martin Fowler   译者: 熊节[同译者作品] 丛书名: 图灵程序设计丛书 出版社:人民邮电出版社 ISBN:978 ...

最新文章

  1. python评分卡建模-卡方分箱(2)之代码实现
  2. 征战蓝桥 —— 2016年第七届 —— C/C++A组第4题——快速排序
  3. JVM源码分析之synchronized实现
  4. 内核kernel以及根文件系统rootfs是如何映射到对应的nand flash的
  5. LeetCode 140. 单词拆分 II(DP+回溯)
  6. 数据结构基础(17) --二叉查找树的设计与实现
  7. AMI corpus download
  8. python封装sql脚本_flask-sqlalchemy如何使用原生的sql语句然后封装?
  9. 自动生成一列不重复数据库
  10. 计算机操作系统(第四版)课后习题答案西电版
  11. 【好书推荐】-你的灯亮着吗?
  12. Android安全 Hook技术,Android下通过hook技术实现透明加解密保障数据安全
  13. 练习京东顶部导航条、背景、渐变、按钮练习(雪碧图)、渐变
  14. 阿里云ACA试题——云安全
  15. Unix/Linux编程:Internet domain socket
  16. MapperReducer
  17. MFC对话框标题栏颜色自绘,标题栏由过渡色组成,自绘关闭按钮
  18. Linux内核4.14版本——SPI NOR子系统(2)——spi-nor.c分析
  19. 使用python中PIL库进行切图时候出现了图片全黑
  20. 用百度搜索SB,为什么是google排第一?

热门文章

  1. MySQL 命令行导出、导入Select 查询结果
  2. Mysql使用kill命令解决死锁问题(杀死某条正在执行的sql语句)
  3. PHP OPCode缓存:APC详细介绍
  4. php 获取时间段 今天昨天本周上周本月上月本季度本年去年
  5. python扫描端口脚本_Pyhton扫描端口脚本代码
  6. java反序列化 构造函数_FastJson反序列化和构造函数之间的一点小秘密
  7. recv返回值为0_基于GNES和Tensorflow 2.0的大规模视频语义搜索
  8. 小米路由器sn算ssh密码_【玩转路由】小米路由器开启SSH
  9. swiper用loop不出图片_swiper实现双向控制
  10. jquery 控制CSS属性display 实现元素的显示、隐藏