欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

重构笔记

程序员文章站 2022-04-27 16:20:29
...

最近刚看完了《重构-改善既有代码的设计》([美] Martin Fowler)这本书,里面介绍了很多的重构手法,

挺实用的,看的时候简单的记录了一下,不是很详细,还是分享一下吧。

 

 
重构笔记
            
    
    博客分类: 重构 重构代码改善 
 

1)提炼函数(Extract Method)  

使用场景

1、函数过长

2、全部或某一段代码块重复性太强,copy的过多

好处

1、颗粒度小,整体被复用的机会大,即不需要再多次重复内部逻辑

2、规范一个好的命名(命名长度可以很长,但一定要更好的解释此段的语义),

     可以更加清晰此块代码的实际作用

实际运用

1、不需要传参,即直接提取函数段,并放入一个新的函数中即可。

2、需要传参:

      1)按值传递(单值 / 多值)【JAVA规范】

            单值问题上直接传递即可。

            若参数值过多,可以将其封装成一个临时对象(内部类)来传递。

            注意:值传递时,不要直接对参数赋值,将其当做被final修饰过

                       的值来处理 。 

            做法:

                       (1) 将待处理的参数赋值给一个临时变量。

                       (2) 中间的处理部分也以指定的临时来进行运作。

                       (3) 在有必要时需要返回这个临时变量。

            (移除对参数的赋值【Remove Assignments to Parameters】)

      2)按引用传递

备注

 

 

 

2)内联函数(Inline Method) 

使用场景

1、一般为源函数的函数体很短,很清晰易懂,且不被重复调用

好处

1、其语义本身就已经很清晰了,没必要添加成新的函数

实际运用

1、直接将源函数的函数体放入目标函数的对应位置即可

备注

 

      

3)以函数对象取代函数(Replace Method with Method Object)     

使用场景

1、一般为长函数中,局部变量过多,而无法很好的提炼成小函数

好处

1、将该函数存放至函数对象中之后,局部变量可转换成对象的字段来处理,

      在字段的处理上整体会更加清晰一些,面向对象的概念性更强。

实际运用

1、新建一个类,类名的语义符合待处理函数的用途。

2、新建一个final修饰的字段,用来保存源对象(即源函数所处的那个对象),

      并将源函数中的变量 + 参数都设置成该函数对象的字段。

3、为该函数对象新建一个构造函数,接受源对象 + 源函数本身的所有参数作为参数。

4、新建一个compute()函数,并将源函数中的函数体移植到此函数中。

5、在源函数的函数体中调用该函数对象的compute()即可。

备注

 

  

4)以查询取代临时变量(Replace Temp with Query)       

使用场景

1、一般为函数体中的临时变量(为另一个函数所函数的值)基本上只使用1次

好处

1、减少临时变量的使用,让代码更加清晰一些

实际运用

1、直接使用另一个函数来进行操作,而不是先使用临时变量来接收返回值

备注

 

 

5)分解临时变量(Split Temporary Variable)

使用场景

1、某个临时变量被使用超过一个,且即不是循环变量,也不用于收集计算结果

好处

1、对临时变量进行分解后,可对函数体进行其他形式的重构,而不必受限于临

时变量在该函数体中的变化

实际运用

1、针对每一次赋值,创建一个独立、对应的临时变量

备注

 

 

6)将查询函数与修改函数分离(separate Query from Modifier)   

使用场景

1、某个函数即返回对象的状态值,又修改该对象状态

好处

1、符合单一原则,不会在执行此函数的时候出现副作用

实际运用

1、建立两个函数,一个查询,一个修改

备注

在并发问题上的考量:

       在高并发的时候,不仅分离成两个不同的函数,还需要保留第三个函数来

       同时执行这两个函数,并将这两个函数分别声明为synchronized,且限制

他们在包(protected)级别 或 private级别

    

7)引入解释性变量(Introduce Explaining Variable)       

使用场景

1、复杂的表达式

好处

1、让表达式清晰

实际运用

1、将该复杂表达式(或其中一部分)的结果放进一个临时变量,以此便令名称来解释表达式的意图

备注

也可使用提炼函数的方法来处理

  

8)替换算法(Substitute Algorithm)   

使用场景

1、将某段复杂的逻辑转换成清晰的表达式

好处

1、理解清晰

实际运用

需要更具具体的业务逻辑来处理。

如有些部分可以使用guava / JDK8 等工具来处理会使语义更加明确

备注

也可使用提炼函数的方法来处理

    

9)搬移函数(Move Method)       

使用场景

1、在程序执行中,某个函数与其所驻类之外的另一个类有更多交流,调用后者,或被后者调用。

好处

1、降低相关类之间的耦合度

实际运用

通俗的来说就是:

       在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,

       或是直接将旧函数移除掉。

 

具体做法:

        1)检查源类中被源函数所使用的一切特性(字段 或 函数),是否也可一起被搬移,

             有时候搬移一组函数比搬移单个要更好。

        2)检查源类的子类或父类中是否存在对该函数的其他声明。

        3)在目标类中声明这个函数。

        4)将源函数的代码复制到目标函数中,调整目标函数。

              (1)目标函数引用了源函数的一些特性,且不能被一起搬移,则

                       可以将源类的引用作为参数传递至目标函数中,或新创建函

                       数来取得目标对象的这些特性。

              (2)若源函数中有异常的处理,需要考虑是否需要将异常带到目

                       标函数中处理还是继续留在原地。

        5)修改源函数 或 将源函数移除掉:

              若需要经常在源函数中引用目标函数,则尽量保留源函数。

备注

 

  

10)自封装字段(Self Encapsulate Field)       

使用场景

1、若某个字段提供直接访问方式,在函数中或许会因为过多的来调用而增加耦合度,

将其变得笨拙。

好处

1、面向对象概念更加清晰

实际运用

1、直接在该类中将该字段设置为private,并提供get/set方法

备注

 

  

11)搬移字段(Move Field)   

使用场景

1、在程序执行中,某个字段与其所驻类之外的另一个类有更多交流

好处

1、降低相关类之间的耦合度

实际运用

通俗的来说就是:

       在目标类中新建此字段,用新字段替换掉所有引用了源字段函数中的源字段部分。

       需要注意的是:

        1)如何在源类中获取到目标字段,可在源类中添加一个目标类的引用。

备注

 

 

12)提炼类(Extract Class)       

使用场景

1、一个类做了本应该由两个类来做的事

好处

1、抽象更加明确,满足单一原则

实际运用

1、建立一个新类,将相关函数和字段搬移至新类中

2、如有必要,可添加新类与源类之间的对象引用

备注

 

  

13)将类内联化(Inline Class)     

使用场景

1、两个相关类都没有处理过多的事,可将其合并为一个类

好处

1、清理掉萎缩类,让结构变得干净

实际运用

1、建立一个新类,分别将两个类中的函数和字段搬移至新类中

备注

 

 

14)隐藏“委托关系”(Hide Delegate)    

使用场景

1、客户通过委托类来调用另一个对象

好处

1、隐藏其中的细节,避免客户知道其实现逻辑

实际运用

在服务类上建立客户所需要的所有函数,用以隐藏委托关系

备注

例如: 类A、B、C

需求:A不能直接获取C信息,A能直接获取B信息,B能直接获取C信息

处理:在B中委托一个函数来获取C信息,则A可直接调用B中的函数来间 

           接获取了C信息。

    

15)移除中间人(委托函数)(Remove Midden Man)   

使用场景

1、某个类包含了太多的委托函数

好处

1、让客户直接调用委托类,清晰

实际运用

客户直接调用委托类,不必走中间过程

备注

这和Hide Delegate并不冲突,当委托函数只有一两个时可使用隐藏委托关系,

但多了就没必要使用中间过程了,这里面有一个度需要控制。

    

16)引入外加函数(Introduce Foreign Method)     

使用场景

1、当需要为一个服务类增加一个函数时,但又修改不了服务类,一般在源码类中会遇到,

 不能修改源代码,只能对其进行扩展,或重写特定函数

好处

1、根据业务需求扩展源类

实际运用

在客户类中建立一个新函数,并将服务类作为参数进行传递,且函数的返回值也为该服务类。

备注

外加函数只是权宜之计,在权限的允许下可将其加入服务类中

  

17)引入本地扩展【子类 + 包装类】(Introduce Local Extension)       

使用场景

1、同引入外加函数的使用场景一致,但不同的是当外加函数过多时即使用此重构方法

好处

1、在本地新建一个类来包含这些额外函数,让此类作为源类的子类或包装类

实际运用

1、建立一个扩展类,作为源类的子类或包装类。

2、在扩展类中加入转型构造函数:

      即“接收源对象作为参数”的构造函数。

       1)子类化方案:

              转型构造函数需要调用父类的构造函数。

        2)包装类方案:(不是太明白)

              转型构造函数需要将其得到的传入参数以实例变量的形式保存下

              来,用作委托对象的原对象。

3、将外加函数搬移至此类中

备注

 

  

18)以对象取代数据值(Replace Data Value with Object)     

使用场景

1、某个类中需要使用到另一个类的部分信息

如:在用户类User中需要知道用户所阅读的书籍信息,若需要详细了解书籍信息

可以直接将书籍类Book的引用作为User的一个字段。

好处

1、可查看更多信息,后续扩展Book类时,User类也不需要修改

实际运用

将Book的引用作为User的一个字段

备注

 

  

19)以工厂函数取代构造函数(Replace Constructor with Factory Method)       

使用场景

1、你希望在创建对象时不仅仅是做简单的建构动作

好处

1、将“对象创建请求的接收者”和“被创建对象所属的类”分开了,

如:Date date = DateFactory.create();  

 

对象创建请求的接收者:原为Date,现为DateFactory.create

被创建对象所属的类:原Date类

实际运用

1、将构造函数替换为工厂函数

       private Book(String name){ this.name = checkNull(name); }

 

       public Book create(String name){

             return new Book(name);    //原构造函数设为私有的

       }

备注

1、当此工厂函数创建的子类只有少数几个时,可使用明确函数来创建子类

      即在工厂类中添加类似如下创建函数:

      public Book createBook(String name){

             return new Book(name);

      }

2、当需要创建的子类数量多的时候,可以使用字符串类型的类型码 + 反

      射机制来控制:

      static Book create(String name){

             try{

                    return (Book) Class.forName(name).newInstance();

             } catch(Exception e){}

      }

 

      //部分代码

      switch(name){

           case XXX:  return create(“XXX”);

           ……

           default: throws new Exception(“xxxxxxx”);

      }

      

     可使用类(枚举类也可以)取代类型码

  

20)将对象替代数组(Replace Array with Object)

使用场景

1、有一个数组,但数组中每个元素又代表着不同的东西

好处

1、对象化,让所存储的每一个元素都能清晰的表达自己

实际运用

以对象替换数组,数组中的每一个元素,用字段来表示

备注

 


21)将单向关联改为双向关联 

使用场景

1、两个类都需要使用对方的特性,但其中只有一条单向连接

好处

1、可以双向连接

实际运用

添加一个反向指针,并使修改函数能够同时更新两条连接

备注

例如订单类 + 客户类(一个客户可以有多个不同的订单)

public class Order{

    private Customer _customer;

    public void setCustomer(Customer customer){

          if(this._customer != null)   this.customer.getOrders().remove(this);

          this._customer = customer;

          if(this._customer != null) this._customer.getOrders().add(this);

    }

    public Customer getCustomer(){

          return this._customer;

    }

}

 

 

public class Customer{

      private Set orders = Sets.newHashSet();

      public Set getOrders(){

          return this.orders;

      }

}

  

22)以字面常量取代魔法数(Replace Magic Number with Symbolic Constant) 

使用场景

1、有一个带有特别含义的字面数值

好处

1、修改、查找时都会方便

实际运用

创造一个静态常量,命名很重要,其值则为那个字面数值

备注

 

 

23)封装字段(Encapsulate Field)

使用场景

1、将Public定义的字段变成private的

好处

1、对象化字段

实际运用

将字段改成private的修饰符,再提供其get/set函数

备注

自封装字段和这一个概念

 

24)封装集合(Encapsulate Collection)

使用场景

1、函数返回一个集合

好处

1、避免直接去操作所返回的那个集合

实际运用

新建一个集合用作所返回集合的一个只读副本,并在这个类中提供增加/移除集合元素的函数。

List list = service.findAll();

List res = Collections.unmodifiableList(list);

之后的操作都在只读集合res中操作,避免破坏原list

备注

Collections.unmodifiableList(list);  //得到集合的只读副本

 

25)以子类取代类型码(Replace Type Code with Subclasses)

 

使用场景

1、有一个不可变的类型码,但它会影响宿主类的行为

好处

 

实际运用

以子类取代这个类型码。

1、使用自封装字段方式将类型码封装起来,如果类型码为构造函数的参   

      数,则需要将构造函数换成工厂函数。

2、为类型码的每个数值建立一个对应的子类,并复写父类获取类型码的函

      数,返回对应的类型码

备注

此方法仍不够好,仍需要switch表达式的介入,下面介绍的的重构手法将完全去除掉switch语句

 

26)以多态取代条件表达式(Replace Conditional with Polymorphism)

使用场景

1、有一个条件表达式,需要根据对象类型的不同而选择不同的行为

好处

简化了条件表达式

实际运用

将这个条件表达式的每个分支放进一个子类的覆写函数中,然后将原始函数声明为抽象函数。

备注

 

 

27)以字段取代子类(Replace Subclass with Fields)

使用场景

1、当各个子类的唯一差别仅是“返回常量数据”不同时可使用

好处

较少子类

实际运用

1、在所有子类中通过工厂函数的方式重构其构造函数

2、其他直接引用了子类的地方,都改换成引用超类

3、针对子类中的每一个常量函数,在超类中对应声明一个final字段

4、在超类中声明一个protected构造函数,用来初始化新增的字段

5、使用内联函数方法将子类构造函数内联到超类的工厂函数中

6、再在超类中实现所有的常量函数,且返回对应的字段

7、删除子类

备注

 

 

28)分解条件表达式(Decompose Conditional)

使用场景

1、if、then、else条件语句过多

好处

结构清晰

实际运用

将if、then、else段落中分别提炼出独立的函数

备注

 

 

29)合并条件表达式(Consolidate Conditional Expression)

使用场景

1、有一系列的条件测试,但都得到相同的结果

好处

结构清晰

实际运用

将这个测试合并为一个条件表达式,并将这个条件表达式提炼成一个函数

备注

三目运算符在重构方面使用也挺好的

 

29)合并重复的条件片段(Consolidate Duplicate Conditional Fragments)

使用场景

1、在条件表达式的每个分支上有着相同的一段代码

好处

去重

实际运用

将重复代码搬移到条件表达式之外

备注

 

 

30)移除控制标记(Remove Control Flag)

使用场景

1、在一系列布尔表达式中,某个变量带有“控制标记”的作用

好处

当使用控制标记时,其他使用者不能一下看明白这个标记的意思,

因此使用此重构方式来简化代码结构

实际运用

找出对标记变量赋值的语句,适当的使用break、continue或return来取代控制标记

备注

 

 

31)使用卫语句来取代嵌套条件表达式(Replace Nested Conditional with Guard Clauses)

使用场景

1、函数中的条件逻辑太多使人难以看清正常的执行路径

好处

结构清晰

实际运用

对于每个检查,放进一个卫语句(卫语句要不就从函数中返回,要不就抛出一个异常)

备注

卫语句:

如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回。

这样的单独检查就是“卫语句”。

 

32)以多态取代条件表达式(Replace Conditional with Polymorphism)

使用场景

1、以多态取代条件表达式

好处

减少switch、if、else等语句的出现,更重要的是多态可降低系统各部分之间的依赖,

在碰到新的修改时,不必直接去修改条件表达式,而是以新增子类的形式来完善即可

实际运用

将这个条件表达式的每个分支放进一个子类内的复写函数中,然后将原始函数声明为抽象函数

备注

 

 

33)函数重命名(Rename Method)

使用场景

1、函数的命名不能直接看出函数的用途

好处

看其名,知其意

实际运用

为该删除命一个好的名字。

1、先为这段函数写一段注释

2、根据注释来为其命名

备注

 

 

34)添加参数(Add Parameter)

使用场景

1、某个函数需要从嗲用端得到更多的信息

好处

 

实际运用

若函数参数过多,可自己封装一个参数对象来进行传递,或使用现成的对象来进行传递

备注

既然有添加参数,那当然也有移除参数,和此重构相反,就不多说了

 

35)将查询函数和修改函数分离(Separate Query from Modifier)

使用场景

1、某个函数既返回对象状态值,又修改对象状态

好处

减小因某次修改会产生副作用的概率

实际运用

建立两个不同的函数,其中一个负责查询,另一个负责修改

备注

 

 

36)令函数携带参数(Parameterize Method)

使用场景

1、若干个函数做了类似的工作,但在函数本体中却包含了不同的值

好处

简化重复代码

实际运用

建立单一函数,以参数表达那些不同的值

备注

将少量数值看做参数,来找出有重复性的代码

 

37)以明确函数取代参数(Replace Parameter with Explicit Methods)

使用场景

1、存在一个函数,其中完全取决于参数值而采取不同行为

好处

 

实际运用

针对该参数的每一个可能的值,建立一个独立的函数

备注

 

 

38)保持对象完整(Preserve Whole Object)

使用场景

1、从对象中取出若干值来当做参数进行传递

好处

 

实际运用

直接传递整个对象

备注

和引入参数对象(Introduce Parameter Object)重构方式一样

 

39)以函数替代临时参数(Replace Parameter with Methods)

使用场景

1、对象调用某个函数,并将所得结果作为参数,传递给另一个函数。

而接受该函数的函数本身也能够调用前一个函数。

好处

减少临时变量的使用

实际运用

让参数接受者去除该项参数,并直接使用前一个函数

备注

也要分情况来考虑:

当原函数返回的值在当前函数的很多地方都会使用时,可使用变量先接住原函数的返回值。

当其他地方使用很少时并且函数的执行不会消耗提多资源时,可直接使用函数来替代此参数。

 

40)移除设值函数(Remove Setting Method)

使用场景

1、类中的某个字段应该在对象创建时被设值,然后就不再改变

好处

 

实际运用

去掉该字段的所有设值函数,在构造函数中进行赋值

备注

 

 

41)隐藏函数(Hide Method)

使用场景

1、未使用的函数

好处

 

实际运用

使用private来进行隐藏,降低函数的可见度

备注

 

 

42)以异常取代错误码(Replace Error Code with Exception)

使用场景

1、某个函数返回一个特定的代码,用来表示某种错误情况

好处

 

实际运用

改为返回异常

备注

 

  

43、字段上移

44、函数上移

45、字段下移

46、函数下移

47)构造函数本体上移(Pull Up Constructor Body)

使用场景

1、在多个子类的构造函数中,其本体几乎都一致

好处

 

实际运用

在超类中新建一个构造函数,并在子类构造函数中调用它

备注

 

 

48、提炼子类

49、提炼父类

50、提炼接口

51)塑造模板函数(Form Template Method)

使用场景

1、在一些子类中,其中相应的某些函数以相同的顺序执行类似的操作,

但各个操作的细节上又有所不同

好处

 

实际运用

将这些操作分别放进独立的函数中,并保持他们都有相同的签名,

于是原函数也就变得相同了,然后将原函数上移至超类中。

说白了,就是将不同的地方通过分解/封装转换成相同的部分,再将相同之处提炼至父类中。

备注

分解目标函数时需要注意

 

52)以委托取代继承(Replace Inheritance with Delegation)

使用场景

1、某个子类只能使用超类接口中的一部分,或是根本不需要继承而来的数据

好处

 

实际运用

在子类中新建一个字段用以保存超类;调整子类函数,令它改而委托超类;然后去掉继承。

1、在子类中新增一个字段,并new一个超类的实例

2、在有调用超类函数的地方修改为使用新增的字段来调用

备注

同时,也有以继承取代委托的重构方法,当然了,其用法也正好相反

 

 

 

最近也在看秦小波的《设计模式之禅》,写的也挺好的

 

 

 

 

  • 重构笔记
            
    
    博客分类: 重构 重构代码改善 
  • 大小: 178.5 KB
相关标签: 重构 代码改善