2016 June 17 —— book; software

读重构

重构的思想,重构的手法,重构需要日常不断实践之。重构是什么?为什么要重构?如何判断哪些地方需要重构?

将重构当作一种日常

第一章节重构案例:重构是什么?

大部分情况下,编译器在不同代码结构下的运行效率并无太大差别,那么为什么我们要追求更加优雅的设计?因为代码不是一成不变的,需求总是在更新,代码在维护,如果每一次写代码都要推翻前人所有代码去更新,是不明智的,所以代码最终的面向对象依旧是人,靠人去维护更新,而人都是追求美的,优雅的结构,清晰的逻辑,艺术的代码;

重构的基础,第一步是构建测试环境,验证在重构过程中是否引入新的Bug;

重构函数,函数功能的细分,单一职责,越小的模块更加容易管理;

重构,逐步迭代处理,每一次更改的部分很小,故而容易校验错误的出现,进而避免了大规模的Debug,浪费过多时间;重构的本质——以微小的步伐修改程序

在逐步修改过程中,如果合适的变量名的修改是提升代码清晰的关键;

优秀的程序员写出人容易理解的代码,而不是仅仅机器理解的代码

根据函数功能的相关性,重构散乱无序的代码块;重构应该时刻进行,与功能的添加并行进行;

基于其他对象属性的Switch无法控制,不符合结构;如果Switch无可避免,理应针对本对象属性;

Case Type 模式转换为 State 模式构造,结合抽象与继承; ———— 这里看出合理的重构还要对于设计模式有充分的理解

PS:这里有一个矛盾,重构对于前期的不合理点进行了修正,那么有人说我前期尽可能的考虑得更加完善,但是前期过度设计拖慢节奏并不是一个很好的点,初期快速的迭代加上及时的重构才是正途,进一步说明了重构的重要性,重构是一种以不变应万变的方案;

第二章节:重构原则

重构基础:不改变软件可观察性;

重构本质是对软件内部结构的调整,提高其可理解性;开发过程中的两个分支—— 重构与新功能实现,在不断切换中螺旋前进,但是在分支切换时,要清楚当前所处在的分支 —— 弄清当前的任务究竟是新增功能还是重构结构;

重构的目的:

开发过程中,软件的设计会逐渐腐朽变质,如果没有重构来整理代码,减少代码结构的流失;没有重构会恶性循环,越难看明白整体代码结构,程序腐败的速度越快,最终趋近不可控;

随着时间的流失,自己是很难记住自己写过的代码,合理的利用重构可以使代码维护时,迅速切换情景,找到当初的上下文环境,进行后续的更新维护;—— 重构可以擦掉程序中的污渍,让你在整体结构上看得更远;

不要特意抽出时间重构,重构应该无时无刻不伴随在软件开发过程中,添加功能时的重构,BugFix时的重构,以及Review代码时的重构;

根本上要意识到有活力的软件是持续改变的,不要为了完成今天的功能而忘记明天还要维护更新迭代,以至于明天的工作无法开展

重构过程中的中间层问题 —— 合理使用,防止中间层过度膨胀,及时清理,中间层的合理利用可以使结构更加具有弹性;

接口的重构 —— 利用废弃字段,让新接口兼容旧接口,不要简单的复制粘贴旧实现,让重构无意义;同时对外暴露的接口应该慎重,不应该随意发布接口,最终导致接口泛滥,在接口的维护上浪费过多精力;可以利用抛出自定义异常的方式去不让他人调用这类接口,一旦调用了这类接口他们就要处理异常,进而他们能够意识到这类接口不应该调用;

重构不是银弹,何时重构,何时重写要能够辨别,在无法确定重构与重写时,可以将大项目拆分成小型子项后,针对子项进行进一步的处理,是选择重构还是重写;

重构对于初期设计的好处 —— 减轻初期设计的压力,因为通过重构可以逐步趋完善设计,而不需要在最开始考虑极限的各类复杂问题,设计完美的方案,事实上完美的方案并不存在,但是要注意的是减轻设计不是不设计;初期的过度设计很容易浪费大量的时间;凭空的度量过多的东西并没有什么益处;

重构可以使性能优化变得更加容易进行,而针对性能优化,如果你对所有代码进行优化,那么你所做的百分之九十是无用的,因为大部分情况下,软件的性能问题是极少部分代码引起的,所以不必绷紧神经,时刻惦记着最优性能,同时,时刻惦记着性能优化,大部分情况依旧是臆测的,在性能优化上臆测并没有什么好处,需要立足于度量工具监测,同时与重构类似,小步优化前进,逐步分析测量,无效则回退;最终结论就是—— 所以在软件实现过程中而更加应该立足全局,掌控大局上写出更加结构优雅的程序;

第三章节:针对哪些问题重构?

这一章节主要简述何时重构——找到需要重构的点;训练自己对于腐朽代码的敏锐度,增强自己的判断能力;

  • 重复代码的合并 —— 以及合并重复代码的一些技巧,如提取函数,分割后提取函数等等

  • 提炼函数,让函数更加精炼;提炼时注意一些比较显著的点 —— 条件,过长函数,函数中的注释语句,函数中的临时变量处理等等

  • 精简提炼类,通过简化类中函数,分割类功能,进一步提炼类实现接口行为;

  • 长函数参数列表的对象组合

  • 一处变化到处修改 与 多种变化都影响该实现的差异

  • 参数数据泥团的对象封装

  • 超类设计问题 以及 过多注解映射的程序隐含问题

核心章节:重构手法

第六章:方法重构

  • 函数提炼 —— Extract Method

  • 以查询函数取代临时变量 —— Replace temp with Query

  • 针对表达式复杂问题引入解释性变量 —— 很多时候可以与函数提炼法互换

  • 分解临时变量 —— 针对临时变量多次赋值问题,每一次赋值创造独立的对应临时变量,每个临时变量只代表一层语义,保证代码逻辑清晰性

  • 对参数赋值问题 —— Java中不要改变参数变量的对象指向问题,可以对参数使用final修饰符帮助检查参数变量的修改问题;

  • 函数对象提法函数 —— 过多的局部变量导致无法分解函数,可以进一步独立出函数对象,精简目标函数逻辑;

  • 算法替换 —— 用逻辑更加清晰,更加精简的算法替代原有的;

第七章:OO 对象间搬移

  • 对象职责的确定 —— 可以通过 字段属性转移 与 函数转移来改善初期类职能职能设计不合理的情况,类之间移动状态与行为是重构的核心支柱;

  • 函数搬移与否,通过查看该函数是否与函数所在对象的外部对象有过多紧密联系,造成紧密的耦合

  • 对像状态(字段)的搬移优先于对象行为函数的搬移—— 字段引用的更改;—— 结合自封装字段的手段改善

  • 类的提炼—— 在开发过程中,类的状态行为会膨胀,类的职责会逐渐混乱不清,直到整个腐朽不堪,在一些子类化的信号影响下,你需要去精炼类的职责,提炼新类去精简逻辑—— 决定新类的职责,进而结合搬移类属性状态与类行为共同组合作用,提炼新类,在新类的提炼之后,一个需要考虑的问题是新类的访问域,是否对外公开,需要结合情况考虑;

  • 中间对象的使用,使用中间对象可以隐藏委托关系 而当中间类成为累赘时,某个类过多的调用了中间对象,在扩展时造成过多的冗余代码,就应该去掉中间对象

  • 使用抽象父类(继承)或 包装类扩展类特性,当某个类的代码无法本地修改时而又需要为业务特性去提供额外函数 、

第八章:数据组织

  • 字段的封装 —— Android不推荐在类中使用封装函数获取或改变属性——JIT的影响下,直接获取属性性能将大大高于使用封装函数调用,但是外部调用依旧有必要使用封装函数,房子笨重的耦合;

  • 封装字段为对象使用

  • 改数组为对象,将数组中的各个index对应,改为更加具有表达语义性的对象各个字段,以顺序表达的数组会在最终难以记忆

  • View界面与Model业务逻辑分离构造,简化Model的维护

  • 反向指针设定,针对初期的单向指针,也就是对象需要引用其的对象的引用,完成一些功能设定,双向关联很强大,但是的其维护需要更加复杂,需要确保对象正确的创建删除,防止僵尸对象的内存泄漏,也就是需要回收的对象依旧存在引用而无法被回收

  • 消除幻数!—— 有特殊意义而又不能明确表达特殊含义——命名!

  • 封装提供数据隐藏 —— 改public

  • 封装集合对象,对集合对象提供一些有效封装,防止集合引用被直接获取,对象内容被更改而无法感知 – Collections.unmodifiableList()

  • 用类代替静态标量这类标志,以类的特性实现更加全能的类型检查

  • 以子类实现的多态实现类型码—— 可以构建宿主类中与特定类型码相关的特性,进一步将不同行为的了解

  • 以状态对象取代类型码,借助使用设计模式(策略,状态)

  • 不要滥用子类,子类特性是增加新特性或变化属性行为,而适当时候我们应该去子类而改字段,精简,借助工厂模式消除对象创建时的直接引用,可以完成更加灵活的重构

第九章:条件表达式简化

  • 分解表达式,提炼函数 —— 针对复杂的条件(if,else)与结果(than)提炼函数,可能函数简短,但依旧有提炼函数的必要,由于函数的存在可以通过函数名称瞬间明白含义,提升代码可读性 —— 再次表明了命名的重要性,不要无意义的命名!!

  • 条件的合并—— 逻辑与 逻辑或等逻辑表达式的应用,合并后结合分解表达式提炼函数法,进一步提升可读性;

  • 循环与条件嵌套中的 break与continue 以及return精简

  • 特殊嵌套条件表达式的改造 —— 守卫语句(guard clause)—— if/else语句条件的同等重要,属于同一重要性级别的分支,无需刻意遵守单一出口原则,保证代码逻辑清晰是最关键的

  • 利用多态封装函数,取代多条件分支,将条件逻辑转移到子类实现,结合继承与委托实现

  • NPE NULL判断的重构 —— 引入 NULL对象,可以利用抽象类或接口形式,针对null的返回处返回自定义的null对象

第十章:简化函数调用

第十一章:继承关系处理

  • 处理特性在继承体系中的上下移动—— 属性移动,行为函数移动,以及构造函数移动,去除重复代码,精简逻辑,重复代码往往引起代码腐化,结构混乱,且引入其他问题。在特性的移动过程中,尤其以函数的移动为重,需要结合委托,抽象等方式去灵活操作

  • 重复代码使程序扩展时,一处修改处处修改,在多个类有共性时应该提炼 super class,进而简化机制;注意提炼超类与提炼类的区别 —— 继承与委托;

  • 接口提炼,所依赖的部分责任分离,进一步划分系统中的责任划分——即一组客户只使用了类责任区中的某一个特定子集合亦或类间合作实现某一功能,构造更加清晰

  • 注意控制继承体系,重构继承体系,精简结构,合并父类与子类

  • 模板函数提炼,重构相视函数,提炼至超类,由子类实现各自差异部分

  • 委托替代继承——对于子类并不需要父类数据,或子类只需要用到父类部分内容时;继承将导致子类冗余属性过多;

  • 与此同时,当一个类的所有函数都被另一个类委托调用,这时候有必要转委托为继承是更好的方式;

总结

随时发现问题,随时重构,不断实践,适时停止,重构失控时回退再来;

牢记重构的目标: 使代码的功能不增不减;保持功能性;


待更新

上一篇
下一篇
Loading Disqus comments...
Table of Contents