《重构-改善既有代码的设计》-方法部分笔记

《重构-改善既有代码的设计》-方法部分笔记

2018, Apr 19    

提炼函数:需要注释才能理解的代码,以更好的方式昭示代码意图

内联函数:内部代码和函数名称同样清晰易读

内联临时变量:某个临时变量被赋予某个函数的返回值,(改为final确定时候只被用过一次)

以查询取代临时变量:使得同一个类中其他部分也可以获取临时变量中计算的信息

引入注释性变量:表达式复杂难以解读。将复杂表达式的结果放进一个临时变量(多个条件&&   这样)

分解临时变量:某个临时变量被赋值超过一次,意味着承担了多个责任。

移除对参数的赋值:代码对参数复制,以一个临时变量取代该参数的位置

以函数对象取代函数:有一个大型函数,其中对局部变量的使用使你无法提炼函数。将这个函数放进一个单独对象,局部变量就成了对象中的字段,就可以分解为小函数,这些小函数可以共用这些字段。

替换算法:有更好的算法


搬移函数:某个函数与另一类进行更多交流

搬移字段: 某个字段被另一类更多用到


提炼类:某个类做个多个类应该做的事。(是改善并发程序的一种常用技术,因为它使你可以为提炼后的两个类分别加锁)

将类内联化:某个类没有做太多的事。


—【隐藏程度】

隐藏委托关系:客户通过一个委托类来调用另一个对象

移除中间人: 客户要使用受托类的新特性时


—【为服务类提供一些额外的函数】

引入外加函数:加一个函数,并且以第一参数的形式传入一个服务类实例

引入本地扩展:子类化(首选,但必须在对象创建时期实施,如果其他对象引用了旧对象,就同时有两个对象保存了原数据);包装,要注意隐藏包装类的存在,equals函数的复写



自封装字段:直接访问一个字段耦合关系变得笨拙。为这个字段建立取值/设置函数,并且在类内以这些函数来访问字段。好处是:子类可以通过覆写一个函数而改变获取数据的途径,还支持更灵活的数据管理方式,例如延迟初始化。

以对象取代数据值:一个数据项,需要与其他数据和行为一起使用才有意义。(注意,值对象应该是不可修改内容的)

—对象是否不可变,易管理

将值对象改为引用对象:想为值对象加入一些可修改的数据,并确保任何一个对象的修改都能影响到所有引用此一对象的地方。(用create返回特定值对象,控制者)

将引用对象改为值对象:有一个引用对象,很小且不可变,总是必须向起控制者请求适当的引用对象,造成内存区域错综复杂的关联。注意,这里要建立equals和hashcode函数,必须同时进行否则依赖hash的任何集合对象都可能会产生异常行为,一个简单的办法:服务equals()使用的所有字段的hash码,然后对他们进行按位异或操作。


以对象取代数组:数组中每个元素代码不同的对象。以对象替换数据,对数组中的每个字段以一个字段来表示。

复制被监听数据:领域数据置于GUI控件中。问题:不同界面表现相同业务逻辑;维护不容易。方法:复制到领域对象中,建立Observer模式,以同步对象和GUI对象内的重复数据。

—【是否都需要使用对方特性】

将单向关联改为双向关联

将双向关联改为单向关联


以字面常量取代魔法数(常量不会造成任何性能开销)

封装字段: public字段,浪费任何一个对象都是不好的。

封装集合:取值函数不应该返回集合自身,这会让用户得以修改集合内容和集合拥有者一无所知。unmodifiableXXX,或返回副本。Assert和判断的区别,assert只在debug中有效。

以数据类型取代记录:传统编程环境中的记录结构。

—【类中有一个数值类型码,是否影响类行为】

以类取代类型码:不会。为了增强可读性。比如int 0 -> BloodGroup.O

以子类取代类型码:会。

以State/strategy取代类型吗:会。类型码的值在对象生命期中发生变化,或宿主类已经有了子类,或其他原因使得宿主类不能被集成。


以字段取代子类:各个子类的唯一差别只在“返回常量数据”



分解条件表达式:把复杂if条件和then还有else三个段落提炼为独立函数

合并条件表达式:一系列条件测试,都得到相同结果


合并重复的条件片段:每个分支上有相同代码

移除控制标记:以break或return取代控制标记

以卫语句取代嵌套条件表达式:条件逻辑难以看清正常的执行路径,如果两条分支都是正常行为,就应该使用形如If …else,如果某个条件极其罕见,就应该单独,并且为真时退出。卫语句就告诉阅读者,这种情况很罕见,发生了,就必须做一些整理工作,然后退出。

以多态取代条件表达式:条件表达式根据对象类型的不同而选择不同的行为。讲每个分支放入一个子类的覆写函数中,然后将原始声明为抽象函数。多态的好处就是,如果你需要根据对象的不同类型而采取不同的行为,多态使你不必编写明显的条件表达式。如果你需要在对象创建好之后修改类型码,或要重构的类已经有了子类,就只能使用state/strategy。如果若干switch语句针对的是同一个类型吗,只需要针对这个类型吗建立一个继承结构就行了。

引入null对象:需要再三检查某对象是否为null。将Null替换为Null对象,替换可能返回null的地方返回空对象,可以加上断言保证不再出现null,覆写如果是null的一些操作,还可以添加isnull函数。Special Case模式。

引入断言:非受控异常。断言失败会受到运行期异常。如果发现代码假设某个条件始终为真,就加入一个断言说明这种情况,但不要用来检查。如果断言所指示的约束条件不能满足代码仍然能正常运行,就应该把断言拿掉。


函数改名

—【函数需要/不需要信息】

添加参数,建议用别的方法

移除参数


将查询函数和修改函数分离:某个函数即返回对象,又修改对象状态。任何有返回值的函数,都不应该有看得到的副作用。


令函数携带参数:若干函数做了类似的工作,但函数本地中却包含了不同的值,这时建立单一参数,以参数表达那些不同的值。

以明确函数取代参数:上一个情况下,函数内部又去判断这个参数值以采取不同的行为时。哪怕只是给一个内部的布尔变量赋值,相比较之下,switch.beOn也比Switch.setState(true)要清楚


保持对象完整:若从对象中取出若干值,又作为某一次函数调用时的参数,万一需要新的数据,就必须查找并修改函数的所有调用。

以函数取代参数:对象调用某个函数,并将所得结果作为参数传递给另一个函数。而接受该参数的函数本身也能够调用前一个函数。如果函数能够通过其他途径获得参数值,那么它就不应该通过参数取得该值。

加入参数对象:某些参数总是很自然的同时出现

移除设置函数:类中的某个字段应该在对象创建时被设值,就不再改变。如果某个子类通过设值函数给超类的某个Private字段设了值,就不应该修改构造函数,这种情况应该提供Protect函数来给这些字段设值。

隐藏函数:类中有从未被其他类用到的函数。一般发生在越多行为引入这个类时,很多取设值函数不再需要公开。

以工厂函数取代构造函数:通常在派生子类的过程中以工厂函数取代类型码。create(type)。Class.forName()来创建可以防止添加新的子类必须更新switch的情况,但是需要谨慎,因为它暴露了子类名称。还可以以明确函数创建子类。还可以考虑ProductTrader模式。

封装向下转型:某个函数反回一个值,需要由调用者执行向下转型,但是你知道返回的对象类型比函数签名所昭告的更特化。return (Reading) ….

—【是否是罕见的行为】

以异常取代错误码:函数中发现错误的地方,并不一定知道如何处理错误,它需要让调用者知道这个错误码。用异常能够更清楚的将“普通程序”和“错误处理分开”。return -1这种 换成 异常。我们需要决定使用受控异常还是非受控异常。

以测试取代异常




字段上移:子类拥有相同字段 字段下移:某些字段只与部分子类相关



函数上移:函数在各个子类有相同的结果

构造函数本体上移:各个子类的构造函数本地几乎完全一样。无法在子类中继承超类构造函数。

函数下移:某些函数只与部分子类相关


提炼子类:类中的某些特性只被一部分实例用到。此外,子类只能用以表现一组变化。如果希望一个类以几种不同的方式变化,就必须使用委托。

提炼超类:两个类有相似特性。另一种选择是提炼类,也就是继承和委托的选择,如果有共享行为,也可以共享接口。Composite模式可以考虑下。

提炼接口:若干客户使用类接口中的同一子集,或者两个类的接口有部分相同。这个类需要与所有协助处理某些特定请求的类合作。这样可以更看清责任划分。如果某个类再不同环境下扮演不同的角色,接口就是个好主意。

折叠继承体系:超类和子类之间并无太大区别。

塑造模板函数:某些子类,其中相应的某些函数以相同顺序执行类似操作,但在细节上不同。将这些操作分别放进独立函数中,并保持他们都有相同的签名,然后原函数就相同了,可以上移到超类。可以将执行操作的序列移至超类,用多态保持差异性。


以委托取代继承:某个子类只使用超类接口中的一部分,或者根本不需要继承而来的数据。

以继承取代委托:如果两个类之间使用委托并经常为整个接口边写许多极简单的委托函数。1. 如果并没有使用受托类的所有函数 2. 受托对象被不只一个其他对象共享,而且受托对象是可变的。这两种情况都不应该变。