`

重构学习笔记

阅读更多

一.怎样的代码该考虑重构

1、重复代码

2、过长函数

3、过大的类

4、过长的参数列

5、发散式变化(让系统容易修改,当需要修改时只要改一点就好而不用改很多紧密相连得为地方)

6、散弹试修改,跟上一点想反,如果发生一个变化发现要在许多类上做小修改,代码四处分散。这样不集中不利于修改容易遗漏需修改代码。

7、依恋情节 (某个函数对某个类的兴趣高过自己所处的类,最常见的是某个函数为了计算某个值从另一个类里面拿了半打的数据过来。)

8、数据泥团 (在很多地方看到相同的三四项数据,两个类中有相同的字段,许多函数签名中有相同的参数)

9、基本类型偏执(相对于基本类型我们可以考虑结构类型,把几个集体出现的基本数据类组合成一个新对象)

10、switch惊悚现身(尽量少用switch语句,可以用多态来代替)

11、平行集成关系(当你为一个类增加子类时另一个类也需要增加)

12、多余类 

13、夸夸其谈未来性(为未来做了很多打算,供未来使用却没有用到)

14、令人迷惑的暂时字段

15、过度耦合的消息链(比如用户请求一个对象。一个对象又去请求另一个对象,这样一层层)

16、中间人(过度运用委托,比如某类接口一般的函数都委托给了其他类)

17、狎昵关系:两个类关系太过紧密

18、异曲同工的类:两个函数做同一件事情,却有不同的签名

19、不完美的库类(库类很难定义)

20、纯稚的数据库(有一些字段,和访问这些字段的函数,除此一无长物)

21、被拒绝的遗赠(子类应该继承超类的函数和数据,但是如果他们不想继承呢?)

22、过多的注释(如果过长的注释要考虑是不是代码写的不好才需要这么多注释)

 

 

 

二.重新组织函数

把一个函数翻来覆去的,该拆的拆该组合的组合,注意临时变量的定义,基本就可以了。

 

提炼函数(把一个函数分为多个,原子化):就是把一个长长的函数,提炼出子函数,当然争对提炼过程的变量作用域要多加判断根据不同的作用域,再提炼中做处理。如果变了是仅用于被提炼出去的代码的临时变量,则在提炼的方法里面申明临时变量,如果是值会改变的局部变量,则需要再改变的子函数中返回改变后的值。。动机: 函数过程,或者一段需要注释才能让人理解意图的代码。

 

内联函数(把多个子函数合成一个函数):跟上面相反,将分开的函数,子体重新放到本体里面来,不单独提取出一个函数,原则就是代码怎么清晰怎么好读怎么来。动机: 不必要提炼出去的,或者需要重新提炼的。

 

内联临时变量(就是去掉这个临时变量):一个只被赋值一次的临时变量,当影响其他地方重构时,将临时变量引用点替换为赋值的表达式,去除临时变量。动机:有妨碍作用等的时候 

 

以查询取代临时变量(一个临时变量赋值逻辑提取成一个函数):将临时变量等号右边的处理逻辑提提取出来,将处理过程放到一个函数里面返回。动机: 表达式非常复杂,尤其是在条件逻辑中

 

引入解释性变量(变量不要重复赋值,责任单一):将复杂表达式赋值给一个临时变量。以变量名来代替复杂表达式。分解临时变量:争对每次赋值,创造一个独立、对应的临时变量。动机:表达式非常那阅读的时候,临时变量可以帮助分解表达式,尤其是在条件逻辑中

 

分解临时变量:某个变量既不是循环变量也不是用于收集计算结果,争对每次赋值。创造一个独立的对应的临时变量。 动机:除了循环变量(如for循环中)结果收集变量(通过整个函数的运算获取得到某个值)临时变量多用于保存一串代码的运算结果,以便稍后使用,这种一般只赋值一次用final修饰。如果有多次赋值则设为多个临时变量。责任单一制。

 

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

动机:如果你把一个名为foo的对象作为参数传给一个函数,那对参数赋值,则会使引用这个参数的对象可能发生改变。

 

以函数对象取代函数:你有一个大型函数,其中对局部变量的使用使你无法提取函数,将这个函数放到一个单独的类里面,如此一来局部变量就成了对象内的字段,然后你就可以在同一个对象中将这个大型函数分解为多个小型函数动机:为了方便好看嘛

 

替换算法:想把某个算法替换为另一个更清晰的算法,将函数本体替换为另一个算法。

动机:用简单的替换复杂的多好

 

 

三在对象之间搬移特性

--在对象设计的过程中。将责任放在哪。

如果上一节是将函数移来移去改来改去,这一节就是将类移来移去改来改去

 

搬移函数:你的程序中有个函数与其驻类之外的另一个类进行更多的交流:调用后者,或者被后者调用,在该函数最常被引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或者将旧函数完全移除。动机:如果一个类行为太多或者与另一个类合作而形成高度耦合就该考虑搬移函数

 

搬移字段:你的程序,某个字段在所驻类之外的另一个类更多的用到了,在目标类新建一个字段,修改原字段的所有用户,另他们改用新字段。动机:如果对于一个字段被另一个类更多的引用了我会考虑搬移这个字段,所谓引用是通过设值取值函数的间接进行的,也可以考虑移到引用该字段的函数,这取决于这个函数是否更适合搬移还是留在原地。

提炼类:某个类做了应该由两个类做的事,建立一个新类,将相关的字段和函数从旧类搬移到新类。动机:一个类应该是一个清楚的抽象,处理一些明确的责任。当类不断变大,责任越来越多就应该考虑提炼类

 

将类内联化:某个类没做太多事情,将这个类所有的特性搬移到另一个类中,然后移除原类。动机:其实和提炼类正好相反,某个类承担责任很少时没有单独存在的理由就考虑内联。

 

隐藏“委托关系”:客户通过一个委托类来调用另一个对象,在服务器上建立客户所需的所有函数,泳衣隐藏委托关系。动机:“封装”意味着每个对象都要尽量少的了解系统的其他部分。如果客户线通过服务对象的字段得到另一个对象,然后调用后者那么客户必须知晓这一层关系,比如,客户先拿到人这个对象,然后根据人知道他所在的部门,然后得到部门名称,就必须知道每个人都属于一个部门,但是如果我们人直接提供一个函数给客户,反馈他所在部门的部门名称。这么一来就算委托关系发生变化,变化也发生在人上不会影响客户。

 

移除中间人:某个类做了过多的简单委托,让客户直接调用受托类,其实就是和上一条相反。动机:当客户需要一个新的受托类(也就是部门)的更多信息时,你需要给人不断的加函数,越加越多,这样你就可以考虑解除这一层委托关系。去掉中间人,直接调用受托类。

 

引入外加函数:你需要为提供服务的类添加一个函数,但你无法修改这个类。在客户类中建立一个函数,并以第一参数形式传入一个服务类实例。动机:你正在使用一个类,很好用,但是你需要一个新服务,这个类无法提供,你又无法修改这个列,那就再客户端编码,补足你要的那个函数。

 

引入本地扩展:你需要服务类提供一些额外的函数,但你无法修改这个类。建立一个新类,使它包含这些额外的函数,让这个扩展品成为原类的子类或者包装类。动机:同理你无法修改原类,但是你又需要不止一两个扩展函数,那么就可以考虑这个重构方法。

 

 

 

 

 

四重新组织数据

处理数据重构,是直接访问数据还是通过访问函数来访问,用基本数据类型还是引用数据类型,用数组还是定义数据结构,魔法数的处理,对象之间的关联是单向还是双向,view层是否处理了不该处理的业务逻辑,封装、类型码等

 

自封装字段:你直接访问一个字段,但与字段之间的耦合关系逐渐变的笨拙,为这个字段建立get/set方法,并且以这些函数访问字段。动机:直接访问还是函数间接访问,其实可以根据情况自己定,间接访问的好处是子类可以通过覆写一个函数而改变获取数据的途径,支持更灵活的数据管理方式,比如延迟加载。直接访问的好处是,代码更清晰。

 

以对象取代数值:你有一个数据项,需要和其他数据和行为一起才有意义,将数据变为对象。动机:随着开发的进行,你发现简单数据项不在简单,比如你可能用一个字符串来表示电话号码,但是后来你发现你需要对电话号码进行格式化、抽取区号等操作。但是当操作越来越多,你就不得不考虑把这个数据项换成一个对象。

 

将值对象改为引用对象:你从一个类衍生出许多彼此相等的实例,希望将他们替换成为同一个对象,将这个值对象改为引用对象。动机:值对象:就像日期和钱完全由其所包含的数据值来定义,你并不在意副本的存在,系统中可能存在成千上万的内容为1的钱对象。如果要判断值对象是否相等需要重写equals和hashcode方法。引用对象,就像客户和账户的关系。每个对象都是真实世界的一个实物,可以直接用==号判断是否为同一个对象。当你希望你修改一个对象的值后,其他拥有该对象的值也得到修改,那么就该考虑引用对象。

 

将引用对象改为值对象:你有一个引用对象很小且不可变,不易管理,将它变成一个值对象。动机:引用对象难以使用时,哎根据经验自己判断吧。用引用对象还是值对象。值对象应该是不可变的,并不是说对象的值不能变,如果对象的值变了就是变了一个值对象。

 

以对象取代数据:你有一个数组,其中的元素各自代表不同的东西,以对象替代数组,对于数组的每个元素,以一个字段来表示。动机:数组应该只用于,以某种顺序存放一组相似的对象,如果每个对象都表示不同的含义,那就很麻烦需要改成对象来存储信息。

 

复制“被监视数据”:将GUI数据复制到领域对象,建立一个Observer模式,用以同步领域对象和GUI对象内的重复数据。动机:其实这一点我也不是很懂,毕竟这本书写的历史比较久,GUI我也用的少,所以这一部分先暂定放在这里。不知是否可以理解为类似于MVC模式这样的多层模式,不同层级间数据各有一份但应该保持同步。

 

将单向关联改为双向关联:两个类使用对方特征,单期间只有一条单向连接,添加一个方向,使相互关联。动机:一个类引用另一个类,随着时间的推移你发现另一个类也需要引用一个类,所以添加双向关联关系。

 

将双向关联关系改为单向关联关系:同上相反。

 

 

以常量取代魔法数:你有一个字面数值,带有特别的意义,创造一个常量,根据意义为它命名,并将上述字面数值替换为这个常量。动机:常量不会造成性能开销却能提高代码的可读性。且一个地方多次引用此常量的话可以只修改一处。其实一般代码最好不要出现魔法数。

 

封装字段:将public字段修改为private并提供get set方法。动机:不多说基本现在大家都知道要这样。

 

封装集合:有个函数返回一个集合,让这个函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数。(即有原来的get/set方法改为get/add/remove方法)动机:根据集合我们实际经常的操作性来讲,提供后三种方法更方便,当然要看实际情况。

 

以数据类取代记录:你需要面对传统编程环境中的记录结构,为记录创建一个“哑数据”对象,其实我的理解就是pojo。动机:用一个对象来记录外来数据,比如从数据库取出的数据。

 

以类取代类型码:类之中一个有数值的类型码(比如我们用1表示血型A,2表示B..),但它并不影响类的行为,以一个新的类替换该数值类型码。动机:任何接受类型码的函数,所期望的其实都是一个数值,无法强制使用符号名,如果那样的可能给个不合法的数据,但是用类,可以保证合法性,只要为这个类提供工厂函数。

 

以子类取代类型码:你有一个不可变的类型码,他会影响类的行为,以子类取代类型码。

动机:如果类型码不影响宿主行为则可以考虑上一种方式,但是如果影响就最好以这种方式,多态处理。比如,血型A还是B一般不会影响人的其他行为,但是一个人是属于销售还是管理员要影响人的工资等。前者就适合以类取代类型码,后者适合多态。

 

以state/strategy取代类型码:你有一个类型码它会影响类,你又不能通过继承的手法消除它,以状态对象取代类型码。动机:state/strategy模式我自己先学一下吧~哎。

 

以字段取代子类:你的各个子类的唯一的差别只是“返回常量数据”的函数身上,修改这些函数,使他们返回超类中的某个字段,然后销毁子类。动机:建立子类的目的是为了添加新特性或变化其行为,有一种变化行为是常量函数,会返回硬编码的值,但如果只有这样的函数,就没必要写子类了,毕竟继承会带来额外的复杂性。

 

 

 

 

 

 

 

 

 

 

 

 

 

五简化条件表达式

分解条件表达式:你有一个复杂的条件语句,从if then else三个段落中提炼出独立函数。

动机:其实就是将大块头函数分解,增加可读性。

 

合并条件查询表达式:你有一系列条件测试,都得到相同的结果。将这些测试合并为一个条件表达式,并将这个表达式提炼为一个独立函数。动机:太简单不说了。

 

合并重复的条件片段:在条件表达式中,每个分支有着相同的一段代码,将这段重复代码搬移到条件表达式之外。动机:去重复代码嘛

 

移除控制标记:在一系列布尔表达式中,某个变量带有控制标记,以break或return取代控制标记。动机:控制标记,现在基本不用了。

 

以卫语句取代嵌套条件语句:函数中逻辑诗人难以看清正常的执行路径,使用卫语句表现所有特殊情况。动机:条件较为罕见,就应该独立检察该条件,当条件为真时立刻从函数中返回,这样的独立检查称之为“卫语句”。If-else 或者 if-then-else这样的结构传递的信息是分支有同样的重要性,但是卫语句比较罕见。

 

以多态取代条件表达式:你手上有个条件表达式,它根据对象类型不通而选择不同的行为,将这个表达式的每个分支都放到一个子类的覆盖函数中,然后将原始函数声明为抽象函数。

动机:使用条件语句时,如果想建立一个新类型,就必须找到所有的条件分支进行修改,用多态只需要添加一个新的子类,降低系统之间的依赖性。

 

引入null对象:你需要再三检察是否为空,将null值替换为null对象。动机:每次获取字段时我们要去判断此类是否为空,如果为空怎么处理,这样如果重复太多,就考虑建立一个空对象,当对象为空时,就设为一个空对象,然后继续处理。这是一种特殊的多态行为。

 

引入断言:某一段代码需要对程序作出某种假设,以断言来确定这种假设。动机:常常有段代码只有当某个条件成立时才会运行,但是代码中没有明确的表示出来,使用断言来明确表示这种假设,如果断言失败则是程序员出了错,因为断言应该是总是为真的。

 

 

 

 

 

 

 

 

 

 

 

 

六简化函数调用

函数改名:函数的名字未能揭示函数的用途,修改函数名称。动机:就是要让程序易读。

 

添加参数:某个函数需要从调用端获取更多信息,添加参数把信息带进来。

移除参数:函数本体不要这个参数了,将参数移除

将查询函数和修改函数分离:某个函数即返回对象状态值,又修改对象状态,建立两个不同的函数一个负责查询,一个负责修改。动机:责任分开。

 

令函数携带参数:若干函数,做了类似的工作,但是函数本体确包含不同的值,建立一个单一函数,以参数表达不同的值。动机:去重。

以明确的函数取代参数:你有一个函数,其中完全取决于参数值采取不同的行为,争对该参数值建立一个独立的函数。

 

保持对象的完整性:你从某个对象中取得若干值,将他们作为某一次函数调用时的参数。改为传递整个对象。动机:一个函数用到的很多参数都是属于一个对象,那么传递整个对象更方便。

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

 

引入参数对象:某些参数总是很自然的同时出现,以一个对象取代这些参数。动机:你总是看到特定的一组参数总是同时传递(比如开始时间和结束时间),在同一个类或者好几个类中有好几个方法同时用到这一组参数,这一组参数就是数据泥团,可以利用一个对象包装这些数据。

移除设值函数:类中的某个字段应该在对象创建时被设值,然后就不在改变,去掉该字段的所有设值函数。动机:有设置函数就意味着这个值可以变。

 

隐藏函数:有一个函数从来没有被其他类用到,应该用private修饰。

以工厂函数取代构造函数:你希望在创建对象是不仅仅是做简单的建构动作,将构造函数替换为工厂函数。动机:最显而易见的就是在派生子类时以工厂函数取代类型码,根据不同的类型码创建不同的对象。构建函数只能返回单一类型的对象。

 

封装向下转型:某个函数返回对象,需要由函数调用者执行向下转型,将向下转型动作移到函数中。动机:转型这个事应该是函数本身做完的,不能强加给用户去承担。

以异常取代错误码:某个函数返回一个特定的代码(比如-1)表示错误情况,改用异常。

动机:区分好普通程序还是错误处理,如果是错误处理同异常。

 

以测试取代异常:面对一个调用者可以预先检查的条件,你抛出一个异常,修改调用者使他在调用函数钱做检查。动机:异常应该用于异常的罕见的行为,也就是意料之外的错误,不应该是条件检查的替代品

 

 

 

 

七处理继承关系

字段上移:两个子类拥有相同字段,将该字段移到超类。动机:去重。

 

函数上移:有些函数在各个子类中产生了完全相同的结果,将函数移到超类中。

 

构造函数本体上移:你在各个子类中拥有一些构造函数,他们的本体几乎完全一致,在超类中新建一个构造函数,并在子类构造函数中调用它。

 

函数下移:超类中的某个函数只与部分(而非全部)子类相关,将这个函数移到子类中。

 

字段下移:超类中的某个字段只被部分(而非全部)子类用的,将这个字段移到需要它的子类去。

 

提炼子类:类中的某些特性只被某些(而非全部)的实例用到,新建一个子类,将上面所说的那一部分特性移到子类中。

 

提炼超类:两个类有相似的特性,为这两个类提炼一个超类,将相同部分移至超类。

 

提炼接口:若干客户使用类接口中的同一子集,或者两个类的接口有部分相同。将相同的子集提取到独立的接口中。

 

折叠继承体系:超类和子类没太大区别,将它们合为一体。动机:继承体系总是复杂的。

 

塑造模版函数:你有一些子类,其中相应的某些函数以相同的顺序执行类似的操作,但各个操作的细节上有所不同,将这些操作分别放到独立的函数中,并保存它们相同的签名,于是原来的函数也变得相同了,然后将原函数上移至超类。动机:去重这个在我们部门的集成模版中得到了很好的体现。

 

以委托期待继承:某个子类只使用超类接口中的一部分,或是根本不需要继承而来的数据,在子类中新建一个字段用以保存超类;调整子类函数。令它改为委托超类,然后去掉两者之间的继承关系。

 

以继承取代委托:你在两个类之间使用委托关系,并非常为整个接口编写许多极简单的委托函数,让委托类继承受托类。动机:委托还是继承好好根据情况使用。

 

 

 

 

 

 

 

 

大型重构

梳理并分解继承体系:某个继承体系同时承担两项责任,建立两个继承体系,并通过委托关系让其中一个可以调用另一个。

 

将过程化设计转化为对象设计:你手上有一些传统过程化风格的代码。将数据记录变为对象,将大块的行为分为小块,并将行为移入相关对象中。

 

将领域和表述/显示分离:某些view层包含了领域逻辑,将领域逻辑分离出来,为它们建立独立的领域类。动机:view和model(领域逻辑)的完全分离。Model只含业务的处理相关代码,view只负责显示相关代码。

 

提炼继承体系:你有某个类做了太多的工作,其中一部分工作是以大量条件表达式完成的,建立继承体系,以一个子类表示一种特殊情况。动机:一开始设计了一个类用来实现一个概念,后来你加了两个,三个乃至十个概念。一开始你加一个标记可以给另一个环境使用,后来标记越加越多,一团乱,那么继承体系就来了哈哈哈。

 

分享到:
评论

相关推荐

    31天重构学习笔记中文版

    31天重构学习笔记中文汉化版,非常好的编程规范书籍

    重构 学习笔记 refactoring martin fowler

    “每当我要进行重构的时候, 第一个步骤永远相同: 我得为即将修改的代码建立一组可靠的测试环境. 这些测试是必要的, 因为尽管遵循重构准则可以使我避免绝大多数的臭虫引入机会, 但我毕竟是人, 毕竟有可能犯错误. ...

    31天重构学习笔记.docx

    这个重构在微软的代码库也经常遇到。比如最经典的属性对字段的封装就是一个很好的例子,那么下面我们将看到对集合的封装,如下代码所示,调用端只需要一个集合的信息,而我们则提供了一个IList的集合,大家都知道...

    [免费高清PDF]31天重构系列笔记.rar

    [免费高清PDF]31天重构系列笔记.rar [免费高清PDF]31天重构系列笔记.rar

    《重构》----学习笔记

    重构不是一项靠着天分挥洒的艺术,而是一项工程。重构是一种有纪律的,经过训练的,有条不紊的程序整理方案,可以将整理过程中不小心引入错误的机率降到最低

    JSP_Servlet学习笔记(第2版).pdf

    《JSP & Servlet学习笔记(第2版)》是作者多年来教学实践经验的总结,...《JSP & Servlet学习笔记(第2版)》以“微博”项目贯穿全书,将JSP & Servlet技术应用于实际项目开发之中,并使用重构方式来改进应用程序架构。

    重构:改善既有代码的设计(第2版)学习笔记

    重构:改善既有代码的设计(第2版)学习笔记

    C#学习笔记

    我个人觉得面向对象是本本主义、洁癖的体现、是重构后的最后归属、它可能会矫情、在市场变化老板着急产品狭隘的情况下 快速制作快速上线才是王道,面向对象的基础是对事物的详尽认知,短时间内能做到吗 不好做到,...

    重构知识总结篇

    有关系统重构知识总结,网上浏览学习笔记。

    refactoring-to-patterns-notes:重构-向范式前进(重构为模式)的学习笔记

    重构为模式注释重构-向范式前进(重构为模式)的学习笔记。本站网址: : 本书已绝版,请参考。欢迎到讨论或指正错误。

    docker 学习笔记.docx

    最近老项目重构,打算使用Docker虚拟化技术,踩着坑整理的。希望帮助能够帮助的人。

    W3学习笔记--文献检索与有效阅读1

    标题摘要介绍难度2 详读并 记笔记可转述正标出不理解的名间 提问批判性思考改进重构作者的作到新 不是③献综述 学术观点 t 理论法1批判性归纳与评论堆砌只研究意

    AppFuse学习笔记(J2EE入门级框架)

    Appfuse是Matt Raible 开发的一个指导性的入门级J2EE框架,它对如何集成流行的Spring、Hibernate、iBatis、Struts、xDcolet、Junit、Taperstry、JSF等基础框架给出...AppFuse2.0重构了AppFuse1.0,转到Maven2和Jdk1.5。

    appfuse 学习笔记

    Appfuse 一个开放源码的项目和应用程序,帮助我们快速而高效的地开发。 Appfuse是Matt Raible 开发的一个指导性的入门级J2EE框架,它对如何集成流行的Spring、...AppFuse2.0重构了AppFuse1.0,转到Maven2和Jdk1.5。

    Java学习笔记-个人整理的

    \contentsline {chapter}{Contents}{2}{section*.1} {1}Java基础}{17}{chapter.1} {1.1}基本语法}{17}{section.1.1} {1.2}数字表达方式}{17}{section.1.2} {1.3}补码}{19}{section.1.3} {1.3.1}总结}{23}{...

    angular 学习笔记

    前提 Angular1.5 到 Angular4.0是重写的语言,Angular1简称...在Angular知识学习(一)中有讲述到表单的知识,不过那是最基础的演示,在之后的学习中又了解到模板驱动表单,所以考虑对之前的表单案例进行重构,完善表单

    《xUnitTestPatterns》学习笔记系列

    学习笔记1-TestSmell这本书找来很久了,一直没读。关于软件测试的好书相当少,对于测试代码的重构及模式的书就更加难得了。虽然我才读了前几章,给我的感受是,这本书确实讲的很全面,并且给很多测试中的东西给出了...

    JavaLearnProject:Java 学习笔记测试用例

    Java 学习笔记测试用例 如果没有特别说明,测试代码默认放在 test 目录下面 模块及功能介绍 Module 功能 spring-aop Spring AOP 面向切面编程 spring-ioc Spring IOC 控制反转(类[对象]交由Spring容器管理) ...

Global site tag (gtag.js) - Google Analytics