前言
为何阅读这本书
阅读这本书的初心在于,半年多的时间里一直在重构项目代码。
阅读了不少人移交过来的代码,项目代码整体看下来,就会发现代码的阅读起来非常费劲,并且复用性和拓展性都很差,另外代码逻辑上太绕让人费解。看这样的代码,简直内心在奔腾。
重构时,就会一边骂着一边改着,这种改造简直就是挑战人性,给人非常不好的体验。
在重构完一个项目时,就会发现糟糕代码的很多通病。
就是完全没有任何规则和规范,按照自己的规则来写,而好的代码会有一套规则约束,有很明确的规范。正因为脑中没有概念和理论支撑,才会写的如此【随心所欲】。
让我想起了托尔斯泰的一句话:幸福的人是相似的,不幸的人各有各的不幸。换做写代码就是:优雅的代码都是相似的,糟糕的代码各有各的毛病。
而理论支撑和方法论在哪呢?就要通过学习和阅读来获取。所以就找来这本书来阅读,以深刻深入学习这套理论。
花费5小时时间阅读完成本书,又花费3天下班时间将书中的观点进行汇总。整体而言,这本书的绝大部分观点在这次重构项目中都得到了应用和实践,所以在看到这些观点和方法时,会理解的更加深刻。
心得体会
-
开发
-
提炼短函数,保持单一职责原则,函数和数据应该被包装在形式良好的单元内。并将函数以用途命名
-
函数应保持变化只在一处发生,如果不能保持则应考虑分离出变化的部分
-
几个类中相同部分,应考虑提炼/分解函数,并继承。而代码太分散,应考虑合并代码
-
降低类、方法和值域的耦合性,可考虑移动、提炼相关内容
-
减少临时变量;临时表达式可考虑final。复杂表达式应考虑创建临时变量
-
调用函数中参数过多,应考虑提炼类或者用类来表示
-
多余的代码和注释就应被去掉
-
-
测试
-
小步前进、频繁测试
-
临界值测试
-
不断丰富测试用例
-
-
重构记录
- 应记录下来重构的过程、动机、案例、图
思维导图
书中汇总
-
代码的坏味道
-
重复代码
-
Q:冗余代码
-
R:提取相同的代码到一个方法/类中,分割开来相同和差异部分
-
-
过长函数
-
Q:过长难以理解
-
R:提取分解成不同的短函数,并用其用途进程命名
-
R:条件式和循环也可以提取函数
-
R:遵循单一职责原则,不同作用的代码抽取到不同的类中
-
-
过大类
-
Q:类中过多的代码,可能会造成代码重复、混乱
-
R:提炼类和子类,为每一种使用方法提炼不同的接口
-
-
过长参数列
-
Q:参数太多不易理解,可能会前后不一致,后续调用函数需要很多参数
-
R:只传需要的参数
-
R:用封装的实体类
-
-
发散式变化
-
Q:某个类受多种多种变化的影响
-
R:改变只应发生在单一类中,以应对不同外界的变化
-
-
散弹式修改
-
Q:每遇到变化,就需要多处不同的类做出小修改以响应之
-
R:移动类和方法,把所需的所有需要修改的代码放进同一个类中
-
-
依恋情结
-
Q:函数对某类的兴趣高于对自己所处的host class的兴趣
-
举例:某函数为了计算某值,从另一个函数对象调用几乎所有半打的取值函数
-
R:把函数移动到该去的地方。移动原则:哪个类拥有最多被此函数使用的数据,就把这个函数和那些数据放一块
-
-
数据泥团
-
Q:两个类内相同值域、许多函数签名式中的相同参数,就可以抽取自己的类
-
R;相同的属性抽取到一个类中
-
-
基本型别偏执
-
Q:使用大对象会增加性能开销
-
R:编写小对象,比如money类、表示范围的range
-
R:将原本单独存在的数据值替换为对象
-
-
switch惊悚现身
-
Q:switch表达式带来重复
-
R:运用多态
-
-
平行继承体系
-
Q:每为某一个类增加子类,另一个类也须增加一个子类。会发现,继承体系的类名前缀和另一个的完全相同,则需要分离两个体系
-
R:让一个继承体系的实体指涉(参考、引用、refer to)另一个继承体系
-
-
冗赘类
-
Q:类没有价值/多余
-
R:直接干掉多余的类
-
-
夸夸奇谈未来性
-
Q:以为在某天就会用到,并做一些非必要的处理
-
R:去除掉不必要的参数、测试类等
-
-
令人迷惑的暂时值域
-
Q:某个实例变量仅为某种特定情势而设,但并不是所有都会用到
-
R:利用提炼函数将这些变量和其相关的函数提炼到一个独立的类中
-
-
过度耦合的消息链
-
Q:一个对象索求另一个对象,形成一个消息链。耦合性太高
-
R:看最终得到的对象是什么用途,然后看是否可以提炼函数
-
-
中间转手人
-
Q:过度抽取函数/委托函数
-
R:直接和实责对象打交道,而去除掉不必要的中转
-
-
狎昵关系
-
Q:两个类太亲密,需要花费很多的时间去探究类关系
-
R:移动方法或者类,以划清界线
-
R:抽取共同点到一个类中
-
-
异曲同工的类
-
Q:两个函数做同一件事,却有不同的签名式
-
R:根据用途重命名类,或者直接抽取共用的部分
-
-
不完美的程序库类
-
Q:程序库类不能满足需要
-
R:引用外部方法,或引用本地拓展
-
-
幼稚的数据类
- 没有用处的类
-
被拒绝的遗赠
-
Q:子类中不需要的函数,就不该继承
-
R:抽取需要的父类,然后继承另一个类
-
-
过多的注释
-
Q:注释过多会引起误会
-
R:去除掉不必要的注释
-
-
-
构建测试体系
-
自我测试
-
频繁进行测试
-
每个类都应该有一个用于测试的main(),可能不好实现
-
-
JUnit测试框架
-
测试套件
- 测试代码
-
测试用例
-
独立的测试类
-
测试开始,先让它失败,比如错误期望值、导致失败/异常的值
-
单元测试与功能测试
-
-
测试装备
-
-
添加更多测试
-
观察类所做的一切,然后针对任何一项功能的任何一种可能的失败情况,进行测试
-
测试应该是风险驱动
-
寻找边界条件:寻找特殊的、可能导致失败的情况
-
不断丰富测试案例
-
一个测试类包含另一个测试类
-
测试应集中在容易出错的地方
-
-
-
重构名录
-
重构的记录格式
-
名称
- 建造一个重构词汇表
-
简短概要
-
重构手法的适用场景,它所做的事情
-
一个简短文句,介绍这个重构能够帮助的问题
-
一段简短陈述,介绍该做的事情
-
一幅速写图,简单展现重构前后示例;可展现代码,也可展现UML图
-
-
动机
-
为何需要这个重构
-
什么情况下不该重构
-
-
作法
-
简明扼要地介绍如何一步步的进行此重构
-
可以在未来回忆,快速记得怎么做的
-
每个步骤都写得简短
-
要安全的重构方式,应采用非常小的步骤,并在每个步骤后进行测试
-
-
范例
-
以一个十分简单的例子说明此重构如何运作
-
帮助解释重构的基本要素
-
-
-
寻找引用点
-
这些重构准则有多成熟
-
小步前进、频繁测试
-
引入设计模式
-
-
-
重新组织你的函数
-
提炼函数
-
动机:过长函数,或一段需要注释才能理解的代码
-
函数长度不是问题,关键在于函数名称和函数本体之间的语义距离
-
作法
-
创建新函数,名以它做什么来命名
-
颗粒度要小,单一原则,每个函数做一件事
-
-
-
将函数内联化
- 可以直接使用函数,不需要间接调用。
-
将临时变量内联化
-
将变量引用的动作,替换为它赋值的那个表达式本身
-
如果临时变量并未被声明为final,那就声明为final,然后编译
-
-
以查询代替临时变量
- 应减少临时变量,将临时变量的表达式放在独立函数中
-
引入解释性变量
- 复杂的表达式,可以用临时变量将表达式分级,用变量名称解释表达式用途
-
剖解临时变量
-
临时变量应被赋值一次,如果临时变量承担太多责任,则应被拆解变量。
-
final作用在临时变量,只承担一个责任
-
-
移除对参数的赋值动作
-
对对象进行赋值,则相当于改变了引用关系。会降低代码的清晰度,且混淆传值和传址的方式
-
建立一个临时变量,把待处理的参数值赋予它
-
可在参数上加关键字final,从而强制它遵循不对参数赋值。不过建议在长函数用final
-
如果返回的值有很多,可以封装成一个对象
-
-
以函数对象取代函数
-
局部变量太多不易提炼函数,可以用函数对象来替换
-
将常用的变量、值域抽取为函数对象
-
-
替换你的算法
-
将算法替换为更清晰的算法
-
测试:调用重构前后的方法,看返回值是否一致
-
-
-
在对象之间搬移特性
-
搬移函数
- 某个类使用的次数比较多,则可以抽取函数到一个类中
-
搬移值域
- 某个值域被其所驻类之外的另一个类更多的使用,则可以调整位置
-
提炼类
-
一个类应该是一个清楚的抽象,处理明确的责任
-
考虑哪些可以分离出去,并分离成一个单独的类
-
对于函数中的某些数据经常同时变化甚至相依,应将其分离出去
-
-
将类内联化
- 类没有承担足够的责任,则应该合并函数
-
隐藏委托关系
- 某个类建立客户所需的所有函数,用以隐藏委托关系
-
移除中间人
-
引入外加函数
- 建立一个函数做调用
-
引入本地拓展
- 添加额外的函数以拓展函数
-
-
重新组织数据
-
自封装值域
- 为待封装值域建立取值/设置函数
-
以对象取代数据值
- 为待封装值域建立一个类
-
将实值对象改为引用对象
- 可使用多个对象作为新对象的访问点
-
以对象取代数组
- 数组不同元素的位置难以记住且难以理解
-
复制被监视数据
- 实现良好分层
-
以符号常量/字面常量取代魔法数
- 魔法值无法正常说明数字含义
-
封装值域
-
public调整为private
-
为public值域提供取值、设值函数
-
-
以数据类取代记录
-
以策略/状态取代型别码
- 运用设计模式,每个类是不同的类别
-
-
简化条件表达式
-
分解表达式
-
如if中有非常复杂的表达式,应提炼成一个独立的函数
-
形成新分支和新函数,可以突出条件逻辑,更清晰表明每个分支的作用
-
-
合并表达式
-
合并重组的条件片段
-
鉴别出执行方式不随条件变化而变化的代码
-
共通的代码不止一条,就应考虑提炼函数
-
-
移动控制标记
- 不用通过标记来判断是否结束循环,可以直接用break或return
-
以卫语句取代嵌套条件式
-
某个条件极其罕见,就该单独检查该条件,并在条件为真时立刻从函数中返回
-
卫语句要么从函数中返回,要么抛错
-
-
以多态取代条件式
- 抽取抽象函数
-
引入null对象
-
引入断言
-
-
简化函数调用
-
重新命名函数
- 描述清楚函数的用途
-
添加参数
-
移除参数
-
将查询函数和修改函数分离
- 将查询和修改的函数分离
-
令函数携带参数
- 若干函数的工作是相似的,但函数却包含了不同的值,可统一函数
-
以明确函数取代参数
- 函数内完全取决于参数值而采用不同的反应,则应为每个可能值建立一个独立函数
-
保持对象完整
- 若将对象中的某几个数据作为参数传递给函数,则可改为使用传递整个对象
-
以函数取代参数
- 获取结果可以用函数
-
引入参数对象
- 某些参数同时出现,可能分布在不同类中,那么可以抽取到一个类中
-
移除设置对象
-
隐藏某个函数
- 某个函数没被其他类用到,就改为private
-
以工厂函数取代构造函数
-
封装向下转型动作
- 某个函数返回的对象,需要由函数调用者执行向下转型动作,将向下转型动作移到函数
-
用异常取代错误码
-
以测试取代异常
-
-
处理概括关系
-
值域上移
- 两个值域相同的应移到父类
-
函数上移
-
构造函数本体上移
- 子类中拥有一些函数,它们的本体代码几乎完全一致,那么应在父类中新建一个构造函数,并在子类构造函数中调用它
-
函数下移
-
值域下移
-
提炼子类
- 类中的某些特性只被某些实体用到,新建一个子类,将其特性移至子类
-
提炼父类
- 两个类有相似特性,那就为这两个类建立一个父类,将相同特性移至父类
-
提炼接口
- 提炼相同的接口子集
-
折叠继承关系
- 父类和子类没有太大区别,就合并为一体
-
塑造模板函数
- 继承是避免重复行为的一个强大工具
-
以委托取代继承
- 某个子类只使用父类接口的一部分,或根本 不需要继承数据,那么就为子类建立一个值域,或建立一个委托的类,然后去掉两者的继承关系
-
以继承取代委托
-
-
大型重构
-
梳理并分解继承体系
- 某个继承体系同时承担两项责任,建立两个继承体系,并通过委托关系让其中一个可以调用另一个
-
将过程化设计为对象设计
- 将数据记录变成对象,将行为分开,并将行为移入相关对象之中
-
将领域和表述、显示分离
-
提炼继承关系
-