《重构》读书笔记 与 Eclipse 重构功能使用

第二章 重构原则
重构是什么?
重构(名词):对软件内部结构的一种调整,目的是在不改变[软件之可察行为]前提下,提高其可理解性,降低其修改成本。
重构(动词):使用一系列重构准则(手法),在不改变[软件之可察行为]前提下,调整其结构。

两顶帽子:添加新功能和重构,不能同时进行。

为何重构?
改进软件设计:可能设计之初根据已有需求,是世界上最优的设计。但是可能过程中增删许多功能,原有设计已经不满足现有需求。
使软件更易理解:
     通常多添加注释不一定是好的选择,因为可能代码会被别人修改,而忽略修改注释。后续的人在看注释反倒成了阻碍理解。
     程序是写给人看的,不是写给计算机看的。
找到BUG:重构的过程需要梳理流程,可以找出多个功能,或者多次迭代耦合放到一起的BUG
提高编程速度:便于理解,本身就是提高,新增功能的开发速度。


何时重构?
添加功能时、修改错误时、审查代码时、

重构的难题?
数据库、修改接口、

何时不改重构?
代码逻辑混乱,完全不能新增东西,可能某些情况下需要完全重写,不过还是建议先拆分成多个小模块,然后逐步重构与重建。
项目已经接近最后期限。

先设计后开发,但是没必要非得为求最灵活的设计方案而担忧。只需要选择一个最合理的方案,实施然后实践中不断尝试、验证、反馈、再修改。
因为永远不可能在前期预知到后续的变化,而且不可能设计时能考虑到所有细节,也没有必要。

重构与性能?
三种方式
     时间预算法(评估每次任务执行时间与执行链条)
     持续关注法(过程中注意,不过有些时候会导致开发速度变慢,或者程序难以理解)
     开发后期(关注比较大的性能消耗)


重构的起源?
1980年Smalltalk



第三章 代码的坏味道
3.1 Duplicated Code(重复代码)
     重复逻辑的函数(同一个类、多个子类中、不同类中)
3.2 Long Method(过长函数)
     一个函数仅有一个功能,在此情况下,很少超过15行
     方法内的功能越多,越难以复用。或者冗余的复用

3.3 Large Class(过大的类)
3.4 Long Parameter List(过长参数列)
     或者过多的全局变量,可以把变化划分为到新类中

3.5 Divergent Change(发散式变化)
     原本一个类中要承载太多功能。

3.6 Shotgun Surgery(霰弹式修改)
     一个功能的开发,需要牵扯到很多地方的修改。为什么不把一类功能放入一个类中呢?

3.7 Feature Envy(依恋情结)
     将总是一起变化的东西放到一起。数据和引用这些数据的行为总是一起变化的。

3.8 Data Clumps(数据泥团)
     一个类中的数据太多,或者太少而分布在多个类中

3.9 Primitive Obsession(基本类型偏执)
     不愿意把一类型的数据源放到一个新建数据类中

3.10 Switch Statements(switch惊悚现身)
     是否使用多态替换

3.11 Paralle lInheritance Hierarchies(平行继承体系)
     子类都有的函数,抽取到父类中。 或者干脆新建类以创建对象引用的方式使用。

3.12 Lazy Class(冗赘类)
     本身很复杂,重构后功能是在太少,或者很少地方调用,干脆把这个类删除。

3.13 Speculative Generality(夸夸其谈未来性)

     过度设计,说是为了以后便于扩展,但是可能很久是只是单一的使用。


3.16 Middle Man(中间人)
     某个类一半的函数都是委托给其他class。

3.14 Temporary Field(令人迷惑的暂时字段)
     零散的数据,抽到一个新建数据类中


3.15 Message Chains(过度耦合的消息链)
     A对象一个功能需要调用B -> C -> D -> F -> E。最后E中才有想要的功能,是否可以把过程都直接抽取到一起,A直接调用抽取后的对象。(公共对象)

3.17 Inappropriate Intimacy(狎昵关系)
     父类与子类之间过度调用,或者两个类见,过多的互相调用,干脆把过度调用的方法放到一起。

3.18 Alternative Classeswith Different Interfaces(异曲同工的类)
     类或者函数差不多


3.19 Incomplete Library Class(不完美的库类)
     为类库添加扩展

3.20 Data Class(纯稚的数据类)
3.21 Refused Bequest(被拒绝的遗赠)
     某些类的子类,并不需要其父类的功能


3.22 Comments(过多的注释)




第六章 重新组织函数
6.1 Extract Method(提炼函数)
动机:
     函数粒度更小,复用的机会更大。
     高层函数读起来像一系列注释
     函数的复写也会容易些
做法:
Eclipse 选中几行代码,选择Refactory -> Extract Method 或者使用快捷键(Alt + Shift + M)
任意选中一块代码,自动转换为函数,自动添加参数返回类型。

6.2 Inline Method(内联函数)
动机:有时可能发现直接使用函数中的代码比抽取为函数更便于理解。
做法:
Eclipse 选中函数名,选择Refactory -> Inline或者使用快捷键Alt + Shift + I
把调用此函数的地方直接替换成此函数的内容。选中任意函数才可使用此功能。(有All invocations 与 Only the selected invocation两个选项)

6.3 InlineTemp(内联临时变量)
动机:某些临时变量可能印象重构
直接修改为对变量的使用,而删除临时变量

6.4 ReplaceTempwithQuery(以查询取代临时变量)
动机:阻碍抽取函数,把计算放入固定函数中

6.5 IntroduceExplainingVariable(引入解释性变量)
Eclipse 选中表达式,选择Refactory -> Extract Local Variable或者使用快捷键Alt + Shift + L
通常用于表达式,把其中一个抽取为本地的变量,例如3 + 5 抽取为 int i = 3;

6.6 SplitTemporaryVariable(分解临时变量)
动机:一个临时变量仅被赋值一次,如果赋值多次说明承担了一个以上的职责。这样容易让阅读者糊涂。

6.7 RemoveAssignmentstoParameters(移除对参数的赋值)
值传递(pass-by-value)
     http://www.blogjava.net/heis/archive/2009/04/23/267256.html
引用传递(pass-by-refrence)


6.8 ReplaceMethodwithMethodObject(以函数对象取代函数)
     如果发现一个函数中有大量的临时变量导致无法拆分成细粒度的函数,可以把此函数放入新类中,这样函数中的临时变量就成了此类中的全局变量,可以随意抽取细粒度函数。


6.9 SubstituteAlgorithm(替换算法)






第七章 在对象之间搬移特性


在对象设计过程中,决定把职责放在哪儿是非常重要的一件事情。通常很苦恼,但是运用重构可以改变自己原本的设计。

7.1 Move Method(搬移函数)
动机:两个类之间有太多耦合行为,这样可以通过搬移函数,使得两个类变得干净利落。
做法:
     把要搬移的函数设置为static,这样可以看到所有此方法中对当前类全局变量、方法的引用,把这些替换成函数的参数传入。
     然后使用Eclipse 选中函数名,选择Refactory -> Move 或者使用快捷键(Alt + Shift + V)把字段移到其他类、把类移到其他包

7.2 Move Field(搬移字段)
动机:随着系统的发展,发现自己需要新的classes,并需要将原本的工作责任拖到新class中。
做法:使用Eclipse 选中字段,选择Refactory -> Move 或者使用快捷键(Alt + Shift + V)把字段移到其他类、把类移到其他包

7.3 Extract Class(提炼类)
动机:很多数据散落在各处,可以把逻辑上可以划分的数据放到一类中。
做法:使用Eclipse 选中字段,选择Refactory -> Extract Class
把所有选中字段提到新类中,可以选择新建文件也可以使内部类

7.4 Inline Class(将类内联化)
动机:类中没有太多行为,与Extract class相反。

7.5 Hide Delegate(隐藏“委托关系”)
动机:通过“委托关系”,降低类之间的耦合。

7.6 Remove MiddleMan(移除中间人)
与7.5相反

7.7 Introduce Foreign Method(引入外加函数)
动机:函数的所有参数都是使用同一个数据对象的值,干脆直接把数据对象传进入,由函数内部


7.8 Introduce Local Extension(引入本地扩展)
动机:有些无法直接修改源码的地方需要添加新的支持。
做法:继承,然后填加功能。




第八章 重新组织数据


8.1 Self Encapsulate Field(自封装值域)
动机:是直接使用字段还是添加setter/getter总是争论不休,建议直接使用字段,当必须使用setter/getter时通过此模式自动改变。
做法:
Eclipse 重构工具可以自动完成此类操作。
Eclipse 中选中属性,选择Refactor -> Encapsulate Filed。

1. 设置getter/setter名称
2. 使用此字段处如何修改,use setter and getter(使用set与get方法替换引用、keep field refrence 保持原有字段引用方式不变) -> OK
3. Insert new method after 插入位置

8.2 Replace Data Value with Object(以对象取代数据值)
动机:开发初期可能仅需要简单的数据项就可以搞定,随着开发的进行线管数据项越来越多,此时可以把数据值变成对象。
做法:
Eclipse 重构工具可以自动完成此类操作。
Eclipse 在需要抽取字段的类中,选择Refactor -> Extract Class 。
1. Class name指定新数据名称
2. Destination 新创建类文件,还是使用内部类形式
3. Select fields for extracted class 可以勾选抽取字段,右侧Edit可以编辑类型与字段名
4. Field name 指定当前类中使用此新数据对象的字段名。


8.3 Change Value to Reference (将值改为引用对象)
动机:
做法:

8.4 Change Reference to Value(将引用对象改为值对象)

8.5 Replace Array with Object(以对象取代数组)
动机:
做法:
          String[] person = new String[3];
          person[0] = "18";
          person[1] = "name";


修改后效果

8.6 DuplicateObservedData(复制“被监视数据”)
GUI多处显示的值有关联或者相同(把这些数据放置到一处)

8.7 ChangeUnidirectionalAssociationtoBidirectional(将单向关联改为双向关联)
A引用B,希望修改为A引用B,B也引用A

8.8 ChangeBidirectionalAssociationtoUnidirectional(将双向关联改为单向关联)
与8.7相反

8.9 ReplaceMagicNumberwithSymbolicConstant(以字面常量取代魔法数)
动机:魔法数字就是直接使用数字,存在的问题是,其他人不知道这个魔法数字是什么意思,为什么必须是这个数字,可能多处使用同样逻辑的魔法数字,仅修改一处不会可能会忘记其他地方的修改。
做法:
Eclipse 重构工具可以自动完成此类操作。
Eclipse 中 选中需要抽取为常量的魔法数字,这里是选中数字30,选择Refactor -> Extract Constant(提取常量、把任意位置的字符串或者数字抽取为一个静态全局常量。所有使用此字符或者数字的也会相应的被替换为使用常量。)

1. Constant name 设置名称
2. Access modifier 设置访问权限
3. Replace all occurrences of the selected expression with references to the constant 如果勾选,会自动替换所以选中数字的地方为此常量
4. Qualify constant references with type name 是否使用类型名限定常量引用


8.10 EncapsulateField(封装字段)
没看出与 “8.1 Self Encapsulate Field(自封装值域)” 有啥区别

8.11 EncapsulateCollection(封装集合)
集合(列表、数组)函数不建议提供setter/getter, 前者会直接覆盖掉整个集合,而且不方便反向查找。后者直接获得了所有集合的控制权。
建议修改为add/remove函数来间接操作集合。

8.12 ReplaceRecordwithDataClass(以数据类取代记录)
把逻辑上一种类型的所有数据,封装到一个新类中。

8.13 Replace Type Code with Class(以类取代类型码)

8.14 ReplaceTypeCodewithSubclasses(以子类取代类型码)

8.15 以State/Strategy取代识别码
动机:以State Object (专门用来描述状态的对象)取代type code
做法
1. 使用Self Encapsulate Filed 将type code自我封装起来。
2. 新建一个class,根据type code用途命名。这就是State Object
3. 为新创建的class添加subclasses,每个subclasses对应一个type code
4. 在supperclass中建议一个抽象的查询函数,用于返回type code。每个subclass中覆写此函数,返回确切type code。
5. 编译
6. 在source class中建立一个值域,用以保存新建state object。
7. 调整source class中负责查询type code的函数,将查询动作转发给state object
8. 编译,测试。


State 模式

8.16 Replace Subclass with Fields(以值域取代子类)
动机:如果每个子类中都返回常量,使用接口与多个子类已经多大意义。
做法:





第九章 简化条件表达式

9.1 Decompose Conditional(分解条件表达式)
动机:判断过多通常导致可读性下降
做法:把if判断中的条件抽取为方法

9.2 Consolidate Conditional Expression(合并条件表达式)
动机:有时发现一串条件检查,检查条件各不相同,但是最终行为却一致。
做法:合并多个判断,返回相同的值。

9.3 Consolidate Duplicate Conditional Fragments(合并重复的条件片段)
动机:如果if与else 中都执行的代码,为何不干脆放到if与else的外面?

9.4 Remove Control Flag(移除控制标记)
动机:Java依然支持条件语句中的标记,虽然不太常用的,但是如果使用会导致程序很难理解。

9.5 Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式)
动机:如果两条分支都是正常行为,就应该使用if else 。但是如果某些条件极其罕见,就应该单独检查改条件,然后直接返回。这种通常称为(卫语句guard clauses)
可以通过直接判断返回的形式,检查if嵌套的层级。

9.6 Replace Conditiona lwith Polymorphism(以多态取代条件表达式)??
把swtich修改为多态形式
动机:多态最根本的好处是:如果你需要根据对象的不同类别而采取不同的行为,多态使你不必编写明显的条件式。

9.7 Introduce Null Object(引入Null对象)
动机:很多地方判断某个对象是否为空,为空如何处理,不为空如何处理。干脆判断一次,如果为空返回一个null object,由其内部为所有字段赋予初始值,这样不会导致异常,也不需要太多if(value == null)的判断。

9.8 Introduce Assertion(引入断言)
动机:检查一定必须为真的条件。不要滥用,可以在编译的时候通过脚本删除所有断言。









第十章 简化函数调用

10.1 Rename Method(函数改名)
动机:为一个方法起一个好的名字便于程序的理解。可以通过Eclipse outline视图查看方法,像读文章一样。
做法:使用Eclipse 选中方法名,选择Refactory -> Rename或者使用快捷键Alt + Shift + R
可以对任意变量、类、方法、包名、文件夹进行重新命名,并且所有使用到的地方会统一进行修改。

10.2 Add Parameter(添加参数)
10.3 Remove Parameter(移除参数)
动机:随着系统的开发,添加与移除参数,或者修改参数的位置都是常见的重构。原有参数不能满足现有需求,或者一些参数并未使用到
做法:使用Eclipse 选中方法名,选择Refactory -> Change Method SignatureAlt + Shift + C或者使用快捷键
对方法进行操作,可以修改方法名、访问权限、增加删除方法参数、修改参数顺序、添加方法异常

10.4 Separate Query from Modifier(将查询函数和修改函数分离)
动机:将查询结果缓存下来,后续重复查询就可以大大加快速度。

10.5 Parameterize Method(令函数携带参数)
动机:多个函数执行的流畅一样,但是每个可能因为仅修改某个值,可以把这些值抽取到一个参数上,然后多处复用此方法传入不同参数。

10.6 Replace Parameter with Explicit Methods(以明确函数取代参数)
动机:如果函数中需要通过参数判断做哪些行为,干脆为这个参数再创建一个新的函数进行处理。

10.7 Preserve Whole Object(保持对象完整)
动机:函数参数用到同一个对象的多个参数,如果增加或者删除参数的话,需要对所有使用到的地方进行修改。
做法:把所有参数替换为此对象,函数内部从此对象上取值。

10.8 Replace Parameter with Methods(以函数取代参数)
动机:如果函数的参数可通过其他函数获取到,没必要把它作为函数的参数传入,直接在函数内部调用其想要当参数传入的函数即可。
过长的参数列表,不利于维护与理解。


10.9 Introduce ParameterObject(引入参数对象)
动机:把多个参数,如果是可以抽取为一种类型的,把这些字段都放入新建数据类中,然后进传递此数据类即可。


10.10 Remove Setting Method(移除设值函数)
动机:一些参数并不需要被改变,可以删除其setter函数达到效果。


10.11 Hide Method(隐藏函数)
动机:把某些数据隐藏起来,某些行为其他地方不能触发。可以把函数的权限调低。public -> private


10.12 Replace Constructor with Factory Method(以工厂函数取代构造函数)
动机:取消以type code创建对象


10.13 Encapsulate Downcast(封装向下转型)
动机:返回Object然后在向下转型,如果可以直接返回固定的类型。


10.14 Replace Error Code with Exception(以异常取代错误码)
动机:一些异常直接返回数值,为何不干脆throw new exception?


10.15 Replace Exception with Test(以测试取代异常)
动机:异常不能成为条件检查的替代品






第十一章 处理概括关系


11.1 PullUpField(字段上移)
11.2 PullUpMethod(函数上移)
11.3 PullUpConstructorBody(构造函数本体上移)
11.4 PushDownMethod(函数下移)
11.5 PushDownField(字段下移)
11.6 ExtractSubclass(提炼子类)


第十二章 大型重构


12.1 Tease Apart Inheritance(梳理并分解继承体系) 
建立两个继承体系,并通过委托关系让其中一个可以调用另外一个。

12.2 Convert Procedural Design to Objects(将过程化设计转化为对象设计) 
典型情况:class中有着长长的过程函数和极少的数据,以及所谓的亚数据对象,处了数据访问函数外没有其他任何函数。

12.3 Separate Domain from Presentation(将领域和表述/显示分离) 
将domain logic(领域逻辑)分离出来,为它们建立独立的domain classes
动机:MVC模式,将用户界面代码(view) 和 领域逻辑(model 模型)分离了。

12.4 Extract Hierarchy(提炼继承体系) 
动机:设计之初仅需要一个类完成功能 ,实现过程中发现一个类中有2,3个功能。可以把这些新增的功能作为子类实现。




第十三章 重构,复用与现实 


13.1 现实的检验 
只有已经开发完成一版,再次基础上新增功能,才会强烈的对重构的需求。


13.2 为什么开发者不愿意重构他们的程序 
1. 不知道如何重构
2. 短视,工期压力比较大
3. 代码重构是新工作量
4. 重构可能引入BUG


13.3 再论现实的检验 


13.4 重构的资源和参考资料 


13.5 从重构联想到软件复用和技术传播 


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。