《重构》笔记
对 Martin Fowler 的《重构:改善既有代码的设计》一书所做的笔记和总结。
书籍信息:
出版信息
- 作者: Martin Fowler
- 出版社: 人民邮电出版社
- 副标题: 改善既有代码的设计
- 原作名: Refactoring: Improving the Design of Existing Code
- 译者: 熊节
- 出版年: 2010
- 页数: 428
- 定价: 69.00元
- 装帧: 平装
- 丛书: 图灵程序设计丛书
- ISBN: 9787115221704
内容简介
重构,一言以蔽之,就是在不改变外部行为的前提下,有条不紊地改善代码。多年前,正是本书原版的出版,使重构终于从编程高手们的小圈子走出,成为众多普通程序员日常开发工作中不可或缺的一部分。本书也因此成为与《设计模式》齐名的经典著作,被译为中、德、俄、日等众多语言,在世界范围内畅销不衰。
本书凝聚了软件开发社区专家多年摸索而获得的宝贵经验,拥有不因时光流逝而磨灭的价值。今天,无论是重构本身,业界对重构的理解,还是开发工具对重构的支持力度,都与本书最初出版时不可同日而语,但书中所蕴涵的意味和精华,依然值得反复咀嚼,而且往往能够常读常新。
作者简介
Martin Fowler 世界软件开发大师,在面向对象分析设计、UML、模式、XP和重构等领域都有卓越贡献,现为著名软件开发咨询公司ThoughtWorks的首席科学家。他的多部著作《分析模式》、《UML精粹》和《企业应用架构模式》等都已经成为脍炙人口的经典。
其他参编者—— Kent Beck 软件开发方法学的泰斗,极限编程的创始人。他是 Three Rivers Institute 公司总裁,也是 Agitar Software的成员。 John Brant 和 Don Roberts The Refactory 公司的创始人,Refactoring Browser (http://st-www.cs.illinois.edu/users/brant/Refactory/)的开发者,多年来一直从事研究重构的实践与理论。 William Opdyke 目前在朗讯贝尔实验室工作,他写的关于面向对象框架的博士论文是重构方面的第一篇著名文章。
重构
重构的含义
重构,就是在不改变代码外部行为的前提下,对代码进行修改,以改进改善程序的内部结构。
- 程序整理方法
- 最大程序减少整理过程中引入错误的几率
- 在代码写好之后改进它的设计
重构的原因
- 规模大、历史久、代码质量差、添加单元测试或理解其内部逻辑成为不可能的任务
- 难以理解软件的行为
- 软件不一定一开始就是正确的
- 随着时间推移,软件的实现脱离了最初的设计
- 任何傻瓜都能写出计算机能理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员。
- 天才程序员只是少数,大多数人不可避免会犯错
重构目的
- 消除重复
- 使代码易理解、易修改
- 改进软件的设计
- 查找 bug,提高质量
- 提高编码效率
重构的技巧
- 准从规则。编码、分层、归类
- 小步执行,保证每步都正确。小修改、测试、小修改、测试……
- 系统化进行
- 频繁测试。确定一套测试机制
- 用绝对安全的行为从焦油坑中整理出可测试的接口,给他添加测试,以此作为继续重构的立足点。
- 记住所有软件的“坏味道”,以及对应的重构手法,记住常见的重构步骤。
- 添加新功能前先重构
保证重构的效果
- 熟悉常用的重构的手法
- 统一编码规范
- 测试
- Code Review
- 选用强大的重构工具,如 eclipse
何时重构
- 随时。重构一种开发的习惯
- “事不过三,三则重构”
- 添加新功能时
- 修改错误时
- Code Review
重构的难题
- 数据层(数据模型)的变更压力
- 修改接口
- 那些难以通过重构改变的设计改动
- 项目期限压力(何时不该重构)。项目后期,或者打算重写
- 代码不能运行
重构 VS 设计
一般的编码有如下方式:
- 预先设计->编码
- 编码-> 重构
- 预先设计->编码-> 重构
编程不是机械的开发,而是艺术行为!设计和重构的要取得平衡(预先设计的难度和重构灵活性的平衡)
重构 VS 性能
- 重构并不对性能做优化,有时可能会让性能下降。但对后期优化的调整,会更加容易
- 提前优化是万恶之源
代码的坏味道
- DuplicatedCode(重复代码):万恶之源
- LongMethod(过长函数):会导致责任不明确/难以切割/难以理解等一系列问题
- LargeClass(过大的类):职责不明确,垃圾滋生地
- LongParameterList(过长参数列):没有面向对象
- DivergentChange(发散式变化):一个类会响应多种需求而被修改
- ShotgunSurgery(霰弹式修改):其实就是没有封装变化处,由于一个需求,多处需要被修改
- FeatureEnvy(依恋情结):一个类对其他类过多的依赖
- DataClumps(数据泥团):如果数据有意义,就将结构数据变成对象
- PrimitiveObsession(基本类型偏执):使用 Class 替代
- SwitchStatements(switch惊悚现身):少用 switch,考虑多态
- ParallelInheritanceHierarchies(平行继承体系):过多平行的类,使用类继承并联起来
- LazyClass(冗赘类):去除冗余
- SpeculativeGenerality(夸夸其谈未来性):扯了太多没用的淡
- TemporaryField(令人迷惑的暂时字段):封装它
- MessageChains(过度耦合的消息链):使用真正需要的函数和对象,而不要依赖于消息链
- MiddleMan(中间人):过度代理
- InappropriateIntimacy(狎昵关系):过度使用其他类private值域
- AlternativeClasseswithDifferentInterfaces(异曲同工的类):重复作用的类,合并成一个类
- IncompleteLibraryClass(不完美的库类):引入外部方法
- DataClass(纯稚的数据类)
- RefusedBequest(被拒绝的遗赠)
- Comments(过多的注释):写注释前先重构,去掉多余的注释。注释多了,就说明代码不清楚了
测试体系
- 每个类都要有测试
- 自动化测试,自动检查测试结果
- 编写功能前先写测试代码
- 使用 JUnit 进行单元测试
- 编写测试时,先让测试失败,以检验测试机制可行
- 测试是风险驱动的,测试现在或者未来可能出现的错误。“编写未臻完善的测试并实际运行,好过完美测试的无尽等待”
- 考虑可能出错的边界条件
- “花合理的时间抓出大多数bug”好过“穷尽一生抓出所有的bug”
重构常用手法
重新组织函数
- ExtractMethod(提炼函数)110
- InlineMethod(内联函数)117
- InlineTemp(内联临时变量)119
- ReplaceTempwithQuery(以查询取代临时变量)120
- IntroduceExplainingVariable(引入解释性变量)124
- SplitTemporaryVariable(分解临时变量)128
- RemoveAssignmentstoParameters(移除对参数的赋值)131
- ReplaceMethodwithMethodObject(以函数对象取代函数)135
- SubstituteAlgorithm(替换算法)139
在对象之间搬移特性
- MoveMethod(搬移函数)142
- MoveField(搬移字段)146
- ExtractClass(提炼类)149
- InlineClass(将类内联化)154
- HideDelegate(隐藏“委托关系”)157
- RemoveMiddleMan(移除中间人)160
- IntroduceForeignMethod(引入外加函数)162
- IntroduceLocalExtension(引入本地扩展)164
重新组织数据
- SelfEncapsulateField(自封装字段)171
- ReplaceDataValuewithObject(以对象取代数据值)175
- ChangeValuetoReference(将值对象改为引用对象)179
- ChangeReferencetoValue(将引用对象改为值对象)183
- ReplaceArraywithObject(以对象取代数组)186
- DuplicateObservedData(复制“被监视数据”)189
- ChangeUnidirectionalAssociationtoBidirectional(将单向关联改为双向关联)197
- ChangeBidirectionalAssociationtoUnidirectional(将双向关联改为单向关联)200
- ReplaceMagicNumberwithSymbolicConstant(以字面常量取代魔法数)204
- EncapsulateField(封装字段)206
- EncapsulateCollection(封装集合)208
- ReplaceRecordwithDataClass(以数据类取代记录)217
- ReplaceTypeCodewithClass(以类取代类型码)218
- ReplaceTypeCodewithSubclasses(以子类取代类型码)223
- ReplaceTypeCodewithState/Strategy(以State/Strategy取代类型码)227
- ReplaceSubclasswithFields(以字段取代子类)232
简化条件表达式
- DecomposeConditional(分解条件表达式)238
- ConsolidateConditionalExpression(合并条件表达式)240
- ConsolidateDuplicateConditionalFragments(合并重复的条件片段)243
- RemoveControlFlag(移除控制标记)245
- ReplaceNestedConditionalwithGuardClauses(以卫语句取代嵌套条件表达式)250
- ReplaceConditionalwithPolymorphism(以多态取代条件表达式)255
- IntroduceNullObject(引入Null对象)260
- IntroduceAssertion(引入断言)267
简化函数调用
- RenameMethod(函数改名)273
- AddParameter(添加参数)275
- RemoveParameter(移除参数)277
- SeparateQueryfromModifier(将查询函数和修改函数分离)279
- ParameterizeMethod(令函数携带参数)283
- ReplaceParameterwithExplicitMethods(以明确函数取代参数)285
- PreserveWholeObject(保持对象完整)288
- ReplaceParameterwithMethods(以函数取代参数)292
- IntroduceParameterObject(引入参数对象)295
- RemoveSettingMethod(移除设值函数)300
- HideMethod(隐藏函数)303
- ReplaceConstructorwithFactoryMethod(以工厂函数取代构造函数)304
- EncapsulateDowncast(封装向下转型)308
- ReplaceErrorCodewithException(以异常取代错误码)310
- ReplaceExceptionwithTest(以测试取代异常)315
处理概括关系
- PullUpField(字段上移)320
- PullUpMethod(函数上移)322
- PullUpConstructorBody(构造函数本体上移)325
- PushDownMethod(函数下移)328
- PushDownField(字段下移)329
- ExtractSubclass(提炼子类)330
- ExtractSuperclass(提炼超类)336
- ExtractInterface(提炼接口)341
- CollapseHierarchy (折叠继承体系)344
- FormTemPlateMethod (塑造模版函数)345
- ReplaceInheritanceWithDelegation (以委托取代继承)352
- ReplaceDelegationWithInheritance (以继承取代委托)355
大型重构
- TeaseApartInheritance (梳理并分解继承体系) 362
- ConvertPrceduralDesignToObjects (将过程化设计转化为对象设计)368
- SeparateDomainFromPresentation (将领域和表述/显示分离)370
- ExtractHierarchy (提炼继承体系)375
重构的本质
- 消除重复代码
- 减少结构的体积
- 更加 OO
- 让代码易读、易懂、易维护
重构的阻力
- 不知道如何重构
- 缺乏重构的动力。只看到眼前的利益,并不关心项目的未来
- 进度的压力
- 老板、客户更加关注新功能,看不到重构的价值
- 重构有破坏现有程序的风险
如何学习重构
- 随时进行重构。当发现某处代码发出“坏味道”,就要马上解决
- 没有把握就停下来。如果代码改善了就发布,如果没有就撤回
- 学习原路返回。回到没有出错的状态,再前进重构,重构完都要测试