重构,改善既有代码的设计

本系列是用来记录《重构,改善既有代码的设计》这本书的读书笔记。方便自己查看,也方便大家查阅。

欲速则不达,欲达则欲速!

重构,绝对是写程序过程中最重要的事之一。在写程序之前我们不可能事先了解所有的需求,设计肯定会有考虑不周的地方,而且随着项目需求的修改,也有可能原来的设计已经被改的面目全非了。更何况,我们很少有机会从到到尾完成一个项目,基本上都是接手别人的代码,即使这个项目从头到尾参与,也有可能接手其它组员的代码。我们都有这样的经验,看到别人的代码时感觉就像屎一样,有一种强烈的想重写的冲动,但一定要压制住这种冲动,完全重写,可能比原来好一点,但浪费时间不说,还有可能引入原来不存在的bug,而且,你不一定比原来设计的好,也许原来的设计考虑到了一些你没考虑到的情况。我们写的代码,终有一天也会被别人接手,很有可能到时别人会有和我们现在一样的冲动。所以,我们要做的重构,从小范围的重构开始。

重构不只是可以改善既有的设计,还可以帮助我们理解原来很难理解的流程。比如一个复杂的条件表达式,我们可能需要很久才能明白这个表达式的作用,这时候,抽象出来,起一个易于理解的名字,函数名字很重要,下次再见到的时候,自然知道当初的想法了,好的代码胜过注释,毕竟注释有可能更新的不是很及时。

《重构,改善既有代码的设计》,这是一本经典之作,看过这本书要收获的是,让重构融入整个写代码的过程中,让重构不再作为一项独立的任务,而是在写代码的过程中随时随地的进行,一个函数不容易理解,重构;添加新功能时很不方便,重构。

定义

对软件内部结构的一种调整,使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构,提高其可理解性,降低其修改成本。

为何重构
  • 重构改进软件设计:原来的设计不可能考虑到所有的情况,随意添加功能修改东西,可能已经看不出原本的设计了
  • 重构使软件更容易理解
  • 重构帮助找到bug:重构可以增加对代码的理解,从而容易发现bug
  • 重构提高编程速度:重构虽然花费时间,但是重构可以改善程序的设计,使程序更不容易出现bug,使添加新特性更容易
何时重构无须专门拨出时间进行重构,重构应该随时随地进行,事不过三,三则重构,当添加新功能时如果不是特别容易,可以通过重构使添加新特性更容易,修补错误时重构可以更容易发现bug,复审代码也是重构的好时机
代码的坏味道
  • 重复代码
  • 过长函数
  • 过大的类
  • 过长参数列
  • 发散式变化:一个类受多种变化的影响
  • 散弹式修改:一种变化引发多个类响应修改
  • 依恋情结:函数对某个类的兴趣高过对自己所处类的兴趣,是时候考虑这个函数到底应该放在什么位置了
  • 数据泥团:两个类中相同的字段,许多函数中相同的参数,这时候就可以让他们拥有自己的类了,简而言之,类似的东西写一个类里
  • 基本类型偏执:编写小对象,如表示范围的range
  • switch惊悚现身:switch带来重复,同样的switch语句经常散布于不同的地址,如果要加一个新的case子句,就必须找到所有switch语句并修改他们
  • 平行继承体系:每当你为某个类增加一个子类,必须也为另一个类增加一个子类,大多数时候你会发现,某个继承体系的类名前缀和另一个继承体系的类名前缀完全相同,是时候分离为两个继承体系了
  • 冗赘类:没啥用的类就应该干掉
  • 夸夸其谈的未来性:无用的抽象类,无用的预留参数
  • 令人迷惑的局部变量
  • 过度耦合的消息链:函数过大时,就应该提取函数
  • 中间人:无用的委托,过多中间层
  • 异曲同工的类:不同的类或函数,干相同的事,写一个不好吗
  • 过多的注释:如果一个函数需要过多的注释,是时候重构了,把代码当注释,好的名字就是注释
重构组织函数
  • 提炼函数:代码粒度越小越容易重用,而且这部分代码被组织到一起后,可以起一个易于理解的名字解释其意图。要注意临时变量的处理
  • 内联函数:对于一些函数,可能其实现和名字一样容易理解,没必要再封装一层;或者一些没必要的中间层都可以去掉,java中用final修饰,C++中用inline修饰,c#中没有内联函数,也许微软认为C++才需要关心性能,而C#关注快速开发,不必理会这些开销吧。
  • 内联临时变量:你有一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其它重构手法
  • 以查询取代临时变量:应减少定义临时变量,如程序以一个临时变量保存某一表达式的运算结果并作为方法的返回值,直接用表达式作为返回结果就算了
  • 引入解释性变量:有时候也会遇到非常复杂的表达式,定义一个临时变量,解释其用途也是可以的
  • 分解临时变量
  • 移除对参数的赋值:不要对函数传进来的参数赋值
  • 建立新的类取代函数:有一个大型函数,这个函数有太多的临时变量,以至于不能重构,这时就可以另起炉灶,新建一个类,原来函数的参数就变成了新的类成员,可以方便的对新的勒种响应的函数进行各种重构。
  • 替换算法:将两个方法中相同的部分,提取出来
在对象之间搬移特性
  • 搬移函数:类中的一个函数使用另一个类的对象的次数比使用自己所在类的对象的次数还要多,很有可能这个函数定义错地方了
  • 搬移字段
  • 提炼类
  • 内联类
  • 隐藏委托关系:有时调用一个对象的方法时获得另一个对象,然后再调用获得对象的一个方法获得另一对象,如此反复,此时可以直接调用第一个对象再加一个方法直接返回最终对象
  • 移除中间人
重新组织数据
  • 自封装数据:为字段建立赋值取值函数
  • 以对象取代数据值:有一个数据项,可能需要对这个数据项添加一个行为,把这个数据项封装为一个类
  • 将值对象改为引用对象
  • 将引用对象改为值对象:对象不好控制时改为不可变的值对象,这样就不用了考虑同步的问题了。
  • 以对象取代数组:一个数组中的不同元素表示不同的东西,不容易理解
  • 复制“被监视数据”:主要针对GUI中的数据,实现UI与逻辑分享,MVC,将V中的数据复制到M中,并在M中数据变化时通过listener进制或observer弄死同步更新UI
  • 封装字段:public改为private,提供相应的访问函数
简化条件表达式
  • 分解条件表达式:有一个复杂的条件表达式,将if条件,then、else中的三个段落全部提取出独立函数以方便理解
  • 合并条件表达式
  • 合并重复的条件判断:在条件表达式的每个分支上有着相同的一段代码,把这段代码搬移到条件表达式之外
  • 移除控制标记:不用通过控制标记来决定是否退出循环或跳过函数剩下的操作,直接break或return
  • 以卫语句取代嵌套表达式:如果某个条件不常见,应该单独检查该条件,这种操作成为卫语句
  • 以多态取代条件表达式
  • 引入null对象
  • 引入断言
简化函数调用
  • 函数改名:好的函数名字很重要,名字起得好可以看出函数具体是做什么的,对于理解复杂逻辑非常有帮助
  • 添加参数
  • 移除参数
  • 将查询函数和修改函数分离
  • 令函数携带参数
  • 以明确函数取代参数:有一个函数,其中完全取决于参数值而采取不同的行为,针对该参数的每一个可能性,建立一个单独的函数
  • 引入参数对象:某些参数总是同时出现,先建一个变量取代这些参数,减少参数的数量
  • 移除赋值函数:如果类中的某个应该在对象创建时被赋值,此后不再改变,不要添加赋值函数
  • 隐藏函数:有一个函数从来没有被其它类用到,或者本来被用到,但随着类添加接口,之后就用不到了,那么隐藏这个函数,也就是减小作用域
  • 封装向下转型:如果返回的值一定需要调用者转型,那么最好在函数中完成转型动作
  • 以异常取代错误码
  • 以测试取代异常:异常只应该被用于异常的、罕见的、意料之外的行为,不应该作为条件检查用
处理概括关系
  • 字段上移:连个子类拥有相同的字段,将该字段移至父类消除重复
  • 函数上移:有些函数在各个子类中产生相同的结果,上移至父类消除重复并方便修改
  • 构造函数本体上移
  • 函数下移
  • 字段下移
  • 提炼子类
  • 提炼父类:两个类有相似的特性,为这两个类建立一个父类,将相同特性移至父类
  • 体连接口
  • 折叠继承体系:父类与子类无太大区别,之前没考虑明白,消除继承关系,合并在一起
  • 塑造模板函数
  • 以委托取代继承:某个子类只使用父类接口中的一部分,将父类作为子类的一个字段,消除继承关系
  • 以继承取代委托:一个类的行为基本上都是委托另一个类,当另一个类接口改变时也要同时修改委托类,直接继承省事方便
大型重构
  • 梳理并分解继承体系:某个继承体系同时承担两项责任,建立两个继承体系,并通过委托关系让其中一个调用另一个
  • 将过程化设计转化为对象设计
  • 将领域和表述/显示分离
  • 提炼继承关系:某个类做了太多的事情,其中一部分工作是以大量条件表达式完成的,建立继承体系,以一个子类表示一种特殊情况

 

鸣谢:特别感谢作者Martin Fowler提供的技术支持!

  • 9
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
第1章 重构,第一个案例 1 1.1 起点 1 1.2 重构的第一步 7 1.3 分解并重组statement() 8 1.4 运用多态取代与价格相关的条件逻辑 34 1.5 结语 52 第2章 重构原则 53 2.1 何谓重构 53 2.2 为何重构 55 2.3 何时重构 57 2.4 怎么对经理说 60 2.5 重构的难题 62 2.6 重构设计 66 2.7 重构与性能 69 2.8 重构起源何处 71 第3章 代码的坏味道 75 3.1 Duplicated Code(重复代码) 76 3.2 Long Method(过长函数) 76 3.3 Large Class(过大的类) 78 3.4 Long Parameter List(过长参数列) 78 3.5 Divergent Change(发散式变化) 79 3.6 Shotgun Surgery(霰弹式修改) 80 3.7 Feature Envy(依恋情结) 80 3.8 Data Clumps(数据泥团) 81 3.9 Primitive Obsession(基本类型偏执) 81 3.10 Switch Statements(switch惊悚现身) 82 3.11 Parallel InheritanceHierarchies(平行继承体系) 83 3.12 Lazy Class(冗赘类) 83 3.13 Speculative Generality(夸夸其谈未来性) 83 3.14 Temporary Field(令人迷惑的暂时字段) 84 3.15 Message Chains(过度耦合的消息链) 84 3.16 Middle Man(中间人) 85 3.17 Inappropriate Intimacy(狎昵关系) 85 3.18 Alternative Classes with Different Interfaces(异曲同工的类) 85 3.19 Incomplete Library Class(不完美的库类) 86 3.20 Data Class(纯稚的数据类) 86 3.21 Refused Bequest(被拒绝的遗赠) 87 3.22 Comments(过多的注释) 87 第4章 构筑测试体系 89 4.1 自测试代码的价值 89 4.2 JUnit测试框架 91 4.3 添加更多测试 97 第5章 重构列表 103 5.1 重构的记录格式 103 5.2 寻找引用点 105 5.3 这些重构手法有多成熟 106 第6章 重新组织函数 109 6.1 Extract Method(提炼函数) 110 6.2 Inline Method(内联函数) 117 6.3 Inline Temp(内联临时变量) 119 6.4 Replace Temp with Query(以查询取代临时变量) 120 6.5 Introduce Explaining Variable(引入解释性变量) 124 6.6 Split Temporary Variable(分解临时变量) 128 6.7 Remove Assignments to Parameters(移除对参数的赋值) 131 6.8 Replace Method with Method Object(以函数对象取代函数) 135 6.9 Substitute Algorithm(替换算法) 139 第7章 在对象之间搬移特性 141 7.1 Move Method(搬移函数) 142 7.2 Move Field(搬移字段) 146 7.3 Extract Class(提炼类) 149 7.4 Inline Class(将类内联化) 154 7.5 Hide Delegate(隐藏“委托关系”) 157 7.6 Remove Middle Man(移除中间人) 160 7.7 Introduce Foreign Method(引入外加函数) 162 7.8 Introduce Local Extension(引入本地扩展) 164 第8章 重新组织数据 169 8.1 Self Encapsulate Field(自封装字段) 171 8.2 Replace Data Value with Object(以对象取代数据值) 175 8.3 Change Value to Reference(将值对象改为引用对象) 179 8.4 Change Reference to Value(将引用对象改为值对象) 183 8.5 Replace Array with Object(以对象取代数组) 186 8.6 Duplicate Observed Data(复制“被监视数据”) 189 8.7 Change Unidirectional Association to Bidirectional(将单向关联改为双向关联) 197 8.8 Change Bidirectional Association to Unidirectional(将双向关联改为单向关联) 200 8.9 Replace Magic Number with Symbolic Constant(以字面常量取代魔法数) 204 8.10 Encapsulate Field(封装字段) 206 8.11 Encapsulate Collection(封装集合) 208 8.12 Replace Record with Data Class(以数据类取代记录) 217 8.13 Replace Type Code with Class(以类取代类型码) 218 8.14 Replace Type Code with Subclasses(以子类取代类型码) 223 8.15 Replace Type Code with State/Strategy(以State/Strategy取代类型码) 227 8.16 Replace Subclass with Fields(以字段取代子类) 232 第9章 简化条件表达式 237 9.1 Decompose Conditional(分解条件表达式) 238 9.2 Consolidate Conditional Expression(合并条件表达式) 240 9.3 Consolidate Duplicate Conditional Fragments(合并重复的条件片段) 243 9.4 Remove Control Flag(移除控制标记) 245 9.5 Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式) 250 9.6 Replace Conditional with Polymorphism(以多态取代条件表达式) 255 9.7 Introduce Null Object(引入Null对象) 260 9.8 Introduce Assertion(引入断言) 267 第10章 简化函数调用 271 10.1 Rename Method(函数改名) 273 10.2 Add Parameter(添加参数) 275 10.3 Remove Parameter(移除参数) 277 10.4 Separate Query from Modifier(将查询函数和修改函数分离) 279 10.5 Parameterize Method(令函数携带参数) 283 10.6 Replace Parameter with Explicit Methods(以明确函数取代参数) 285 10.7 Preserve Whole Object(保持对象完整) 288 10.8 Replace Parameter with Methods(以函数取代参数) 292 10.9 Introduce Parameter Object(引入参数对象) 295 10.10 Remove Setting Method(移除设值函数) 300 10.11 Hide Method(隐藏函数) 303 10.12 Replace Constructor with Factory Method(以工厂函数取代构造函数) 304 10.13 Encapsulate Downcast(封装向下转型) 308 10.14 Replace Error Code with Exception(以异常取代错误码) 310 10.15 Replace Exception with Test(以测试取代异常) 315 第11章 处理概括关系 319 11.1 Pull Up Field(字段上移) 320 11.2 Pull Up Method(函数上移) 322 11.3 Pull Up Constructor Body(构造函数本体上移) 325 11.4 Push Down Method(函数下移) 328 11.5 Push Down Field(字段下移) 329 11.6 Extract Subclass(提炼子类) 330 11.7 Extract Superclass(提炼超类) 336 11.8 Extract Interface(提炼接口) 341 11.9 Collapse Hierarchy(折叠继承体系) 344 11.10 Form Tem Plate Method(塑造模板函数) 345 11.11 Replace Inheritance with Delegation(以委托取代继承) 352 11.12 Replace Delegation with Inheritance(以继承取代委托) 355 第12章 大型重构 359 12.1 Tease Apart Inheritance(梳理并分解继承体系) 362 12.2 Convert Procedural Design to Objects(将过程化设计转化为对象设计) 368 12.3 Separate Domain from Presentation(将领域和表述/显示分离) 370 12.4 Extract Hierarchy(提炼继承体系) 375 第13章 重构,复用与现实 379 13.1 现实的检验 380 13.2 为什么开发者不愿意重构他们的程序 381 13.3 再论现实的检验 394 13.4 重构的资源和参考资料 394 13.5 从重构联想到软件复用和技术传播 395 13.6 小结 397 13.7 参考文献 397 第14章 重构工具 401 14.1 使用工具进行重构 401 14.2 重构工具的技术标准 403 14.3 重构工具的实用标准 405 14.4 小结 407 第15章 总结 409

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哪 吒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值