重构-改善既有代码的设计 读书心得(二)

本文是关于《重构:改善既有代码的设计》的读书心得,主要讨论了如何通过重构改善代码质量,包括搬移函数、提炼类、隐藏委托关系、引入外加函数等方法。此外,还介绍了如何重新组织数据,如对象取代数据值、值对象的转换,以及简化函数调用的策略,如命名方法、分离查询和修改等。重构的意义在于保持代码的灵活性和可维护性,使系统在变化中保持稳定。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第七章

  1. 在对象之间搬移特性
    在这里插入图片描述
    如果一个类有太多行为,或者与另一个类有太多合作形成高度耦合,就可以搬移函数。如果被搬移函数只引用了原类的一个字段,那么只需将这个字段作为参数传递过来,如果调用了原类的函数,那么必须将源对象传递过来。如果需要很多原类特性,那就要进一步重构,比如分解目标函数,把其中一部分移回原类。
  2. move field
    对于一个字段,如果另一个类有更多函数使用了它,就可以考虑move。如果是public的字段,考虑先封装起来。本书写的有点早,现代java ide早就有成熟的move field refactor工具了。可以先利用ide迁移过来,再考虑是否要做封装。
  3. 提炼类
    在这里插入图片描述
    实际工作中,类会不断成长扩展,一开始可能只有一种功能,慢慢地随着责任不断增加,类会变得过分复杂,此时你需要考虑哪些部分可以分离出去,形成一个单独的类。某些数据和函数总是一起出现,那么它们就应该分离出去。还有一个extract class的信号:如果你发现子类化只影响类的部分特性,或者某些特性需要以另一种方式来子类化,这就意味着你需要分解原来的类。
    有一个需要考虑的事情是是否公开新类,这个需要具体情况具体分析。
  4. inline class
    在这里插入图片描述
    如果一个类不再承担足够责任,挑选这一萎缩类的最频繁用户,以inline class手法将萎缩类塞进另一个类中。
  5. 隐藏委托关系 hide delegate
    如果client先通过服务对象的字段得到另一个对象,然后调用后者的函数,那么客户就必须知晓这一层委托关系。万一委托关系变化,client也得相应变化。可以在服务对象上放置一个委托函数,将委托关系隐藏起来。这样即使委托关系发生变化,变化也被限制在服务对象中,不会波及客户。
    在这里插入图片描述
  6. 移除中间人 remove middle man
    跟上一条是相反的,如果频繁使用上一条,会导致委托函数越来越多,服务类完全变成了一个中间人,此时你就应该让client直接调用受托类。
    这两条规则怎么使用取决于系统需要的隐藏的程度。不过重构的好处是你不必现在就做决定,你可以在系统运行过程中不断进行调整。随着系统的变化,合适的隐藏程度这个尺度也相应改变。重构的意义:你永远不必说对不起,只要把出问题的地方修补好就行了。
  7. 引入外加函数
    你正在使用一个第三方类,它很好用,但你又需要一个新服务,但这个类无法提供,而且你不能修改类的源码,所以你不得不在客户端编码。如果你在多处需要这段代码,就应该抽成一个函数来调用,避免重复代码。
  8. 引入本地扩展 Introduce Local Extension
    上一条的加强版,如果引入了很多外加函数,就新建一个类来包括这些函数,可以用子类和包装类的方法来做。

第八章 重新组织数据

  1. 对象取代数据值
    比如一开始你用一个String来表示customer,但系统后来又增加了客户地址,信用等级等信息,String明显不够用了,你需要构造一个Customer类来表示一个customer。
  2. 值对象改为引用对象
    引用对象:每个对象对应真实世界中的一个事物,比如客户对象,账户对象。值对象:对象是否相同完全由值来定义,比如日期,钱。值对象有很多个副本存在是没有意义的浪费。有时候我们会在多个地方引用值对象,我们希望对这个对象的修改能影响到所有引用它的地方,这个时候我们需要把值对象变成引用对象。解决方案:提前创建好引用对象或动态对象,用工厂方法返回这些对象。
  3. 引用对象改为值对象
    引用对象有时又需要转换成值对象。值对象有个非常重要的特性:不可变。这在并发系统中特别好用。比如Money对象有币种和金额两个Field,那么Money对象通常是不可变的。但并不是说你的薪资不能改变,而是在你改变薪资的时候,需要用另一个Money对象来取代现有的Money对象。解决方案:先确保是不可变对象,然后去掉工厂方法,开放构造函数,重写equals和hashcode方法
  4. 以对象取代数组
    人们很难记住“数组的第一个元素是人名”这样的约定。如果你用对象就不一样,你可以运用字段名和函数名来传达这样的信息。
  5. 以字面常量取代魔法数
    魔法数:拥有特殊含义,却不能明确表现出这种意义的数字。比如重力常量,π等。这些数字发生改变,就必须在程序中找到所有魔法数修改,非常麻烦。
    解决方案:声明一个常量来表示魔法数。
  6. 封装集合
    不返回集合本身,可以返回一个集合的复制给client。可以提供集合的增删方法,但不要提供set集合的方法。
  7. 以数据类取代记录
    你可能面对一个遗留的用非面向对象语言写的程序,或者从数据库读出的记录,它们都是记录型结构(struct),这时候你有必要创建一个数据类,对于记录中的每个数据,在类中都有一个对应的field。
  8. 以类取代类型码
    现在java中有更好的解决方案,用Enum。
  9. 以State/Strategy取代类型码
    通过例子来看更直观些:
    在这里插入图片描述在这里插入图片描述
    新建一个抽象类,把ENGINEER,MANAGER,SALESMAN变成抽象类的子类:
    在这里插入图片描述在这里插入图片描述在这里插入图片描述
    再把switch语句移到它应该呆的类中:
    在这里插入图片描述在这里插入图片描述
  10. 以字段取代子类
    如果子类只是用来返回常量数据,那就可以消除它,避免继承带来的额外复杂性。
    在这里插入图片描述在这里插入图片描述在这里插入图片描述

第九章

  1. 分解条件表达式
    复杂的条件逻辑是最常导致复杂度上升的原因之一。你必须编写代码检查不同的条件分支、根据不同的分支做不同的事情,然后你就会得到一个大函数。而且条件逻辑代码会使代码更难阅读。你可以将它分解为多个独立函数,为新函数命名,更清楚地表达自己的意图。
    在这里插入图片描述
  2. 合并条件表达式
    在这里插入图片描述
  3. 合并重复的条件片段
    在这里插入图片描述
  4. 以卫语句取代嵌套条件表达式
    条件表达式有两种形式,一种是所有分支都是正常行为,第二种是只有一种是正常行为,其他都是不常见的情况。对于第一种,应该用if else的条件表达式,对于第二种,如果某个条件极其罕见,就应该单独检查该条件,并在条件为真时立刻从函数中返回。这样的单独检查被称为卫语句(guard clauses)
    在这里插入图片描述在这里插入图片描述
  5. 以多态取代条件表达式
    在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
  6. 引入Null对象
    反复地判断一个对象是否为null是非常繁琐的。我们可以引入一个空对象,它不会破坏系统的逻辑,对空对象的所有请求都和真实对象一样。但这并非总是好事,有时会造成查找问题比较困难。空对象一定是常量,我们可以用单例模式来构造它。空对象需要有一个能被识别出是空对象的方法,比如isNull(),如果client需要空对象作出不同的响应,就可以调用isNull().
  7. 引入断言
    断言不是用来检查“你认为应该为真”的条件,它是用来检查“一定必须为真”的条件的。

第十章 简化函数调用

  1. rename method
    本书作者特别提倡将复杂的处理过程分解成小函数,但要明确小函数的用途关键就是给函数起一个好名字。函数命名有个好方法,首先考虑应该给这个函数写上一句怎样的注释,然后想办法将注释变成函数名称。
  2. 添加参数
    使用这项重构的动机很简单:你必须修改一个函数,修改后的函数需要 一些过去没有的信息,所以你需要添加参数。但谨慎选择这项优化,因为这会增加参数列的长度,长参数往往伴随着坏味道。
  3. 删除参数
    当一个参数不再需要的时候,删除它。
  4. 查询和修改分离
    一个用来查询的方法,要确保它不会有修改的操作。
  5. 令函数携带参数
    你可能会发现有两个函数:它们功能类似,只是因为少数几个值导致行为略有不同,那你可以把那几个值用参数来表示,把两个函数合并成一个。
    在这里插入图片描述
  6. 以明确函数取代参数
    跟上面的优化相反。如果某个参数有多种可能的值,函数内又用条件表达式检查这些值,根据不同的值做出不同的行为,那么就应该用本项重构。
    在这里插入图片描述
  7. Preserve whole object(保持对象完整)
    有时你会从某个对象中取出若干值,将它们作为某次函数调用时的参数。此时可以考虑把对象传给函数,这样万一将来增减函数参数时只需要在函数内部改就行了。但这会造成对象的依赖,所以需要权衡利弊。更进一步,如果一个函数用了某个对象的很多值,应该考虑把这个函数放到该对象所属的类。
    在这里插入图片描述
  8. Replace Parameter with Methods(以函数取代参数)
    如果函数可以通过其他途径获得参数值,那么就不应该通过参数取得该值。过长的参数列会增加理解难度。缩减参数的办法之一就是:看看接收端是否可以通过与调用端相同的计算来取得参数值,如果可以,那么就应该将这个计算过程转移到接收端内,从而去除该项参数。如果接收端有参数发送端对象的引用,那么可以直接通过引用获得参数的值,从而去掉参数列表里的参数。如果接收端没有发送端对象的引用,那就需要你自己权衡,加入一个对象的引用是否值得。
  9. 引入参数对象
    你常会看到特定的一组参数总是一起被传递。我们可以用一个对象包装所有这些数据,再用该对象取代它们。本项重构的价值跟上面的优化类似,都是为了缩短参数列。还有一个潜在的好处是:当你把这些参数组织到一起后,往往会发现一些可被移至新建类的行为。调用函数一般会对这一组参数有一些共通的处理,如果把这些共通行为移到新对象中,可以减少很多重复代码。
    在这里插入图片描述在这里插入图片描述
    将一些行为挪到新的类:
    在这里插入图片描述
  10. Replace Error Code with Exception(异常取代错误码)
    在这里插入图片描述
  11. replace exception with test
    异常不应该被滥用。它只应该被用于异常的、罕见的行为,不应该成为条件检查的替代品。
    在这里插入图片描述
一直很喜欢重构这本书,但是由于自己记性不太好,书看过之后其中的方法总是记不住,于是想如果有电子版的重构书就好了,工作中遇到重构的问题可以随时打开查阅。在网上搜索了许久,发现重构这本书有英文chm版本的,而中文版的电子书只有扫描的PDF版本,用起来非常不方便。于是萌生想做一本重构工具书的想法,本来打算自己重新将重构书的内容再整理归类一下,后来发现原书的目录编排就很适合做工具书,包括坏味道分类,重构手法归类等,都有了一个比较系统的整理。因此,我利用空余时间制作了这样的一本中文的chm版重构,希望对大家有所帮助,也算对中国软件业做出一点小小的贡献。 本书基本上是取自”重构”中文版一书的内容,但格式上参照的是chm英文版的格式,还有一些格式小修改,比如第一章的重构前后代码对比。因为时间匆促,个人能力有限,本书难免存在一些缺漏,如果大家发现有问题,随时可以给我发邮件,我会尽快更新错误的内容。 最后再次感谢几位大师 Martin Fowler、Kent Beck等,还有翻译的侯捷和熊节先生,为我们带来这么精彩的一本书。谢谢。 免责声明:本书仅供个人学习研究之用,不得用于任何商业目的,不得以任何方式修改本作品,基于此产生的法律责任本人不承担任何连带责任。
第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
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值