重构-改善既有代码设计-重构原则(2)

重构是在不改变软件功能的前提下,优化代码结构,提升代码可读性和可维护性。本文探讨重构的定义、目的、时机以及如何通过重构改善软件设计,帮助查找bug,提高编程速度。

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

神马是重构?从两方面来说:

一个是名词:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。

一个是动词:使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。

对重构的扩展

1.重构的目的是使软件更容易被理解和修改。(注意和性能优化的区别,会使代码难以理解)

2.重构不会改变软件可观察行为--重构之后软件功能一如既往。

两顶帽子:

1.添加新功能。添加新功能时,不应该修改现有代码,只管添加新功能。通过测试可衡量自己的工作进度。

2.重构。重构时不要添加新功能,只管改进程序结构。此时不应添加任何新的测试,只在绝对有必要时(接口变化)才修改测试。

软件开发过程中会经常变换帽子,但无论何时,都要清楚自己戴的是哪顶帽子

为何重构:

1.重构改进软件设计。经常性的重构可以帮助代码维持自己该有的形态。

2.重构使软件更容易理解。及时填补“想要计算机做什么”和“告诉计算机做什么”之间的缝隙。编程模式的核心即“准确说出我想要的”。除了计算机,还有其他程序猿要读你写的源码。这位第二位读者才是最重要的。利用重构协助理解不熟悉的代码。不满足于看代码,真正动手修改代码,让它更好的反应出你的理解。然后重新执行,通过是否仍然运行正常,来验证自己的理解是否正确。

一开始的重构都局限于细枝末节,随着代码的简洁,可以发现一些以前看不到的设计层面的东西。如果不对这些代码做修改,永远看不到它们。这种“早期重构”被称为“擦掉窗子上的污垢,使你看的更远。”重构把你带到更高的理解层次上。

3.重构帮助找到Bug。不擅长调试的人,盯着一大段代码看不到里面的Bug,但如果对这些代码进行重构,就可以深入理解代码的作为,并恰到好处的的把新的理解反馈回去。搞清楚长须结构的同时,也清楚了自己所做的一些假设,于是想不把Bug找出来都难。Ken Beck“我不是一个伟大的程序猿,我只是一个有着一些优秀习惯的好程序猿。”重构能帮助人写出更健壮的代码。

4.重构提高编程速度。良好的设计是快速开发的根本。拥有良好设计才可能做到快速开发。重构可以帮助你更快速的开发软件,因为它阻止系统腐败变质,甚至还可以提高设计质量。

何时重构:

无须专门拨出时间重构,重构本来就不是一件应该特别拨出时间做的事情,重构应该随时随地进行。不应该为了重构而重构,之所以要重构,是因为想做别的什么事,而重构可以帮你把哪些事做好。

三次法则:第一次做某件事时只管去做,第二次做类似的事会发生反感,但无论如何还是可以去做。第三次做类似的事,你就应该重构。

添加功能时重构:帮助自己理解需要修改的代码,为了下次再看这段代码时容易理解。最主要的原因是:如果在前进的过程中把代码结构理清,可以从中理解更多东西。

另一个原动力是:代码的设计无法帮助自己轻松添加自己所需要的特性。不用为过去的错误而懊恼,用重构来弥补它。一来可以让未来添加新特性时更轻松一些,最主要的原因还是这是最快捷的途径。重构是一份的快速流畅的过程,一旦完成重构,新特性的添加就会更流畅,更快速。

修补错误时重构:调试过程中重构,多半是为了让代码更具可读性。

复审代码时重构:复审代码时考虑是否可以通过重构立即轻松实现它们。如果可以,可以立即动手。这样做了几次之后,可以把代码看的更清楚。为了让过程正常运转,复审团队必须精炼。最好是一个复审者搭配一个原作者,共同处理这些代码。复审者提出修改建议,然后两人一起判断这些修改是否能够通过重构轻松实现。果真如此,就一起着手修改。

比较大的设计复审工作,在一个较大的团队内保留多种观点通常会更好一些,此时直接展示代码往往不是最好的办法。用UML展现设计,并以CRC卡展示软件情节。换句话说,和某个团队进行设计复审,二和单个复审者进行代码复审。

极限编程中的“结对编程”把代码复审的积极性发挥到了极致。一旦采用这种形式,所有正式开发任务都由两名开发者在同一台机器上进行,这样便在开发过程中随时进行代码的复审工作,而重构也就包含在代码的开发过程中了。

第一步,要先搭建可自我检验的测试系统,保证修改前后代码的处理结果相同。

程序的两面价值

“今天可以为你做什么”和“明天可以为你做什么”。大多数时候我们只关注自己今天想要程序做什么,让它在今天更有价值,但是系统当下的行为,只是整个故事的一部分。如果为了完成今天的任务不择手段,导致不可能完成明天的任务,那么最终还是会失败。重构时一条拜摆脱困境的路,如果发现昨天的决定已经不符合今天的情况,放心改变这个决定就是,然后就可以完成今天的工作了。明天如果回头看今天的理解也觉得幼稚,那是你还可以改变你的理解。

是什么让程序如此难以相与

1.难以阅读的程序,难以修改。

2.逻辑重复的程序,难以修改。

3.添加新行为时需要修改已有代码的程序,难以修改。

4.带复杂条件逻辑的程序,难以修改。

因此我们希望程序(1)易于阅读;(2)所有逻辑都只在唯一地点执行;(3)新的改动不会危及现有行为;(4)尽可能简单表达条件逻辑;

重构是这样一个过程:它在一个目前可以运行的程序上运行,在不改变程序行为的前提下使其具备上述美好性质,使我们能够继续保持告诉开发,从而增加程序价值。

 

间接层和重构

计算机科学是这样一门科学:它相信所有问题都可以通过增加一个中间层来解决。

间接层的价值

1.允许逻辑共享。如一个子函数在两个不同的地点被调用,或超类中的某个函数被所有子类共享。

2.分开解释意图和实现。选择每个类和函数的名字,给了一个解释自己意图的机会。类或函数内部则解释实现这个意图的做法。如果类和函数内部又以更小单元的意图来编写,你所写的代码就可以描述其结构中的大部分重要信息。

3.隔离变化。在两个不同地点使用同一对象,其中一个地点想改变对象行为,但如果改动了它,就要冒同时影响两处的风险。为此做出一个子类,并在需要修改处引用这个子类。现在可以修改子类而不必承担无意中影响另一处的风险。

4.封装条件逻辑。对象有一种奇妙的机制:多态消息,可以灵活而清晰的表达条件逻辑。将条件逻辑转化为消息形式,往往能降低代码的重复,增加清晰度并提高弹性。

这就是重构游戏:在保持系统现有行为的前提下,如何才能提高系统的质量或降低其成本,从而使它更有价值?

这个游戏中最常见的变量就是:你如何看待自己的程序。找出一个缺乏“间接层利益”之处,在不修改现有行为的前提下,为它加入一个间接层。现在你获得一个更有价值程序,因为它有较高的质量,让我们在明天(未来)受益。

这种与推测性的区别是,推测性设计试图在任何一行代码诞生之前就先让系统拥有所有优秀质量,然后程序猿将代码塞进这个强健的骨架中就行了。这个工程的问题在于:太容易猜错。如果运用重构,你就永远不会面临全盘错误的危险。程序自始至终都能保持一致的行为,而你又有机会我程序添加更多价值不菲的质量。

 

数据库:在非对象数据库中,解决这个问题的办法之一是,在对象模型和数据库模型之间插入一个分割层,可以隔离两个模型各自的变化。升级某一模型时,无须同时升级另一个模型。无需一开始就插入分割层,可以在发现对象模型变得不稳定时再产生它。这样你就可以为你的改变找到最好的平衡点。

修改接口:允许修改软件模块的实现和接口。可以安全的修改对象内部实现而不影响他人,但对于接口则需特别谨慎。如果接口被修改了,任何事情都有可能发生。

如何修改已发布接口。当要修改某个函数名称时,请留下旧函数,让它调用新函数。千万不要复制函数实现。保留旧接口,通常可行,但很烦人。在一段时间内必须构造并维护一些额外函数。

另外一个选择,不发布接口。不是说完全禁止,除非真的有必要,不要发布接口。 

难以通过重构手法完成的设计改动:是否存在某些核心设计决策,无法以重构修改。比如在一个项目中,我们很难(但还是有可能)将不考虑安全性需求时构造起来的系统重构为具备良好安全性系统。

此时的方法是:先想象重构的情况。考虑候选设计方案时,我会问自己:将某个设计重构为另一个设计的难度有多大?如果看上去简单,则无需太担心选择是否得当。测试就会选最简单的设计,哪怕不能覆盖所有潜在的需求。如果预先看不到简单的重构方法,就会在设计上投入更多力气,不过我发现,后一种情况很少出现。

 

何时不该重构:有时候根本不应该重构,例如你应该重新编写所有代码的时候。有时候既有代码实在太混乱,重构它还不如重新写一个来的简单。做这种决定很困难,我承认我也没有什么好准则可以判断何时应该放弃重构。

重写的一个清晰信号就是:现有代码根本无法正常工作。记住,重构之前,代码必须起码在大部分情况下正常工作。

一个折中的办法就是:将“大块头软件”重构为封装良好的小型组件。然后你就可以注意对组件做出“重构或重建”的决定。这是一个颇有希望的办法。但我还没有足够数据,所以无法写出好的指导原则。对于一个重要的遗留系统,这肯定会使一个很好的方向。

另外,如果项目已近最后期限,也应该避免重构。在此时机,从重构过程中赢得的生产力只有在最后期限过后才能体现出来,而那个时候已经为时晚矣。

未完成的重构工作形容为“债务”,很多公司都需要借债l来使自己更有效的运转。但是借债就需要得付利息,过于复杂的代码所造成的维护和扩招的额外成本就是利息。你可以承受一定程度的利息,但如果利息太高你就会被压垮。把债务管理好是很重要的,应该随时通过重构来偿还一部分债务。

如果项目已经非常接近最后期限,你不应该再分心于重构,因为已经没有时间了。不过多个项目经验显示:重构的确能提高生产力。如果最后产没有足够时间,通常就表示你其实早该进行重构。

重构的起源:与技术无关,暂不表述。

 

第1章 重构,第一个案例 1 1.1 起点 1 1.2 重构的第一步 7 1.3 分解并重组statement() 8 1.4 运用多态取代与价格相关的条件逻辑 34 1.5 结语 522章 重构原则 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
一直很喜欢重构这本书,但是由于自己记性不太好,书看过之后其中的方法总是记不住,于是想如果有电子重构书就好了,工作中遇到重构的问题可以随时打开查阅。在网上搜索了许久,发现重构这本书有英文chm本的,而中文的电子书只有扫描的PDF本,用起来非常不方便。于是萌生想做一本重构工具书的想法,本来打算自己重新将重构书的内容再整理归类一下,后来发现原书的目录编排就很适合做工具书,包括坏味道分类,重构手法归类等,都有了一个比较系统的整理。因此,我利用空余时间制作了这样的一本中文的chm重构,希望对大家有所帮助,也算对中国软件业做出一点小小的贡献。 本书基本上是取自”重构”中文一书的内容,但格式上参照的是chm英文的格式,还有一些格式小修改,比如第一章的重构前后代码对比。因为时间匆促,个人能力有限,本书难免存在一些缺漏,如果大家发现有问题,随时可以给我发邮件,我会尽快更新错误的内容。 最后再次感谢几位大师 Martin Fowler、Kent Beck等,还有翻译的侯捷和熊节先生,为我们带来这么精彩的一本书。谢谢。 免责声明:本书仅供个人学习研究之用,不得用于任何商业目的,不得以任何方式修改本作品,基于此产生的法律责任本人不承担任何连带责任。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值