Refactoring重构改善既有代码的设计

重构:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。 

一、如何、何时重构

1、重复代码 Duplicated Code

同一个类多个函数有相同的表达式时,抽出重复代码,Refactor---Extra---Method;
多个互为兄弟的子类内包含有相同的表达式时,将重复代码抽出移植到父类,Refactor---Extra---Pull Members Up;
多个不相干的类出现相同的表达式时,将重复代码抽出到独立一个类里,Refactor---Extra--Class(Delegate);

2、过长函数 Long Method

对内容过长的函数,要进行分解函数,短函数能带来解释能力、共享能力、选择能力;何时分解过长函数,有条规则:每当觉得需要添加些注解来说明点什么的时候,就把药说明的东西写进一个独立函数中,并以以其用途命名函数名;只要函数名称能够解释其用途,知其名,即可理解该该函数做了什么, 不必去看函数中写了什么,让程序更容易理解;
条件表达式和循环也是常常抽出的信号;

3、过大的类 Large Class

若是想利用单个类做太多事,其内往往就会出现太多的实例变量,很容易出现重复代码,此时,可以将几个变量一起提炼至新类,提炼是应该选择类内彼此相关的变量,将他们放在一起;
类内若有太多代码、也是重复代码、混乱,可进行抽取提炼成多个子函数;
有时,先确定客户端如何使用它们,然后运行Refactor---Extra--Interface为每一种使用的方式提炼出一个接口,然后进行分解这个类;

4、过长的参数列 Long Parameter List

函数的参数过多时,这些参数若都属于同一个宿主类时,可将其实传参改为传递对象,即使所需数据有增减,只在修改对象的set,函数对象参数不用修改;然而当这些参数不是来自同一个宿主类时,可以创建一个“参数对象”来处理;

5、发散式变化 Divergent Change

如果某个类经常因为不同的原因在不同的方向上发生变化, Divergent change就出现了。当你看着一个类说:“呃,如果新加入一个数据库,我必须修改这三个函数;如果新出现一种金融工具,我必须修改这四个函数。”那么此时也许将这个对象
分成两个会更好,这么一来每个对象就可以只因一种变化而需要修改。当然,往往只有在加入新数据库或新金融工具后,你才能发现这一点。针对某一外界变化的所有相应修改,都只应该发生在单一类中,而这个新类内的所有内容都应该反应此变化。为此,你应该找出某特定原因而造成的所有变化,然后运用 Extract class将它们提炼到另一个类中。(标)

6、散弹式修改 Shotgun Surgery

  Shotgun surgery :类似 Divergent change,但恰恰相反。如果每遇到某种变化,你都必须在许多不同的类内做出许多小修改,你所面临的坏味道就是 Shotgun surgery。如果需要修改的代码散布四处,你不但很难找到它们,也很容易忘记某个重要的修改。这种情况下你应该使用 Move method和 Move field把所有需要修改的代码放进同一个类。如果眼下没有合适的类可以安置这些代码,就创造一个。通常可以运用 Inline class把一系列相关行为放进同一个类。这可能会造成少量Divergent change,但你可以轻易处理它。
 Divergent change是指“一个类受多种变化的影响”, Shotgun surgery则是指“种变化引发多个类相应修改”。这两种情况下你都会希望整理代码,使“外界变化”与“需要修改的类”趋于一一对应。 (标)

7、依恋情结 Feature Envy

一个函数往往会用到几个类的功能,那么如何处理呢?原则是:判断哪个类拥有最多被此函数使用的数据,然后就把这个函数和那些数据放在一起。

8、数据泥团 Data Clumps

当多个类中相同的字段,许多函数签名中相同的参数时,这些总是绑在一起出现的数据真应该拥有属于它们自己的对象,首先找出这些数据以及字段形式出现的地方,运用Extract class将它们提炼到一个独立的对象中,然后修改函数参数,将很多参数缩短,简化函数的调用。

9、基本类型偏执 Primitive Obsession

10、Switch Statements

若发现相同switch语句散布在不同地方,若要修改,每个地方都得挨着修改,采取面对象中的多态来解决。

11、平行继承关系

12、冗赘类 Lazy Class

创建的每个类,都得有人去理解它,维护它,这也是成本,若是一个类的价值作用不大,或者是后期修改时,价值缩下,可以干掉该类,将功能迁移到所需处;若是子类的的作用也大时,采用内部类,删除该子类。

13、夸夸其谈未来性 Speculative Generality

有时以各种特殊情况来处理一些没必须的事,认为后期可能用的上,从而造成代码不易理解和维护;若是某个抽象类其实没太大作用,就将于子类合成;若是函数的某个参数未被使用,那直接移除掉;若是函数名称带有多余的抽象意味,那就重新命名。

14、令人迷惑的暂时字段 Temporary Field

有时一个类中某个实例变量仅为某种特殊情况而设定的,而在一个变量在未被使用的情况下去猜测当初的设置目的,这样让人不易理解,可以把和这个变量有关的代码都放在一个新类里,在需要的时候调用。

15、过渡耦合的消息链 Message Chains

A对象中请求B对象,B对象里又请求C对象,C对象里请求D对象.....这样的消息链,意味着客户端代码将与查找过程中的导航结果紧密耦合,一旦对象关系发生变化,客户端就不得不做出相应的修改。

16、中间人 Middel Man

有时看到某类接口有一半的函数都是委托给其他类,这样就是过度运用,可以去掉中间委托,直接真正的负责对象打交道,这样处理可以减少“不干实事”的函数,可以抽取方法把他们放进调用端;若是中间委托还有其他行为的话,可以把它变成实责对象的子类,这样处理的话,你即可扩展原对象的行为,又不必负担那么多的委托行为。

17、亲昵关系 Inappropriate Intimacy

两个类的若是过于亲昵,可以将其移至另一个类内部,作为内部类;或者把共同点移至一个新类,让他们共同使用;继承往往造成过度的亲昵,若是觉得子类行为可以脱离父类的话,就让他离开继承体系。

18、异曲同工的类 Alternative Class with Different Interfaces

若多个函数做同一件事,但却有不同的函数名,应该根据用途重新对函数进行命名,同时将其移至新类;

19、不完美的库类 Incomplete Library Class

20、纯稚的数据类 Data Class

21、被拒绝的遗赠 Refused Bequest

22、过多的注释 Comments

当你看到一段很长的注释的时候,其实可以采用提炼抽取被注释函数的中行为,因为把一大块功能代码,分解为多个函数,并给各自函数添加注释,更容易理解代码;当你觉得需要注释的时候,可以先尝试重构,试着让所有注释变得多余。

二、重新组织函数

1、提炼函数
可以将原函数中的一块代码放进一个独立的函数中,并让函数解释该函数的用途。
无局部变量时,直接提取代码到新函数里;
有局部变量时,被提取的代码只是读取这些变量,并不修改她们,此时可以简单的将这些变量当做参数传个目标函数;
有局部变量时,当布局变量再被赋值的时候,若这个变量只在被提炼代码中使用,可以一起提炼抽取到目标函数中;
但是当被提取的代码之外的代码再次使用这个变量时,又分两种情况:如果这个变量在被提取代码之后未被使用,只需在目标函数里修改即可,如果被提取的代码之后的代码使用这个变量,需要让目标函数返回修改后的值。
2、内联函数
某些函数,其内部代码和函数名称同样清晰易懂,应该去掉这个函数,直接使用其中的代码,去掉中间层。
int  flag=7;
int getRating(){
    return (moreThanFiveVaule()) ? 3:4;
}
private boolean moreThanFiveVaule() {
    return flag>5;
}
改为
int  flag=7;
int getRating(){
    return (flag>5 )? 3:4;
}
3、内联临时变量
一个临时变量,只被一个简单表达式赋值一次,我们可以将所有对该变量的引用动作,替换为对它对赋值的那个表达式自身。
boolean getRating(){
    int  flag=7*getPrice();
    return flag>5000;
}
改为
boolean getRating(){
    return 7*getPrice()>5000;
}
4、以查询取代临时变量
当一个临时变量保存某一个表达式的运算结果时,我们可以将这个表达式提炼到一个独立的函数中,并将这个临时变量的所有引用点替换为对新函数的调用,此后新函数还可被其他函数使用。
double getTotalPrice(){
 
    double departPrice=mPrice*range;//需要独立提炼到新函数的表达式
    if(departPrice>1000){
        return departPrice*0.2;
    }else {
        return departPrice*0.6;
    }
}
Refactor--->Replace Temp with Query改为
double getTotalPrice(){
    if(range()>1000){
        return range()*0.2;
    }else {
        return range()*0.6;
    }
}
private double range() {
    return mPrice+range;
}
5、引入解释性变量
有一个复杂的表达式,将该复杂表达式或其中一部分的结果放进一个临时变量,以此变量名称来解释表达式的用途。
String departPrice= "ddddddsdfdfsdas";
if((departPrice.toLowerCase().indexOf("d")>-1)&&departPrice.toUpperCase().indexOf("a")>-1&&departPrice.length()>20){

}
改为
String departPrice= "ddddddsdfdfsdas";
boolean isD = departPrice.toLowerCase().indexOf("d") > -1;
boolean isA = departPrice.toUpperCase().indexOf("a")>-1;
boolean isMoreLength = departPrice.length() > 20;
if(isD&&isA&&isMoreLength){

}
当表达式太复杂时,不容易理解阅读,可进行拆分,也可继续提炼,将拆分的临时变量表达式提炼到新的函数里,来提供可能其他函数调用的方便性。
6、分解临时变量
程序中某个临时变量赋值超过一次是,它既不是循环变量,也不是被用于收集计算结果,针对每次赋值,应该创造一个独立、对应的临时变量。也就是说临时变量多赋值一次相当于多担任责任一次,一个临时变量赋值多次,担任多个责任时,应该进行分解为多个临时变量,重新给个新发临时变量名称,让每个临时变量只承担一个责任,同一个临时变量担任多个多个事件,不易阅读理解。
7、移除对参数的赋值
void getTotalPrice(int aValue,int bValue){
    if(aValue>10){
        aValue=aValue+1;
    }
}
改为
void getTotalPrice(int aValue,int bValue){
    int result =aValue;//赋值给新变量,再进行其他操作
    if(aValue>10){
        result=result+1;
    }
}
8、以函数对象取代函数
对于大型函数,可以将这个函数放进单独对象中,如此一来局部变量就成了对象内的字段,然后在同一个对象中将这个大型函数分解为多个小型函数。
创建新类,根据待处理用途进行命类名;在新类里保留函数的全部临时变量和每个参数,以及大型函数所在类的对象;通过创建新类的构造函数,接受原大型函数的临时变量和参数;创建新函数,将原大型函数的代码移至此处,以备调用;同时可以对此方法进行分解为小函数。
9、替换算法
将函数本体替换为另一种算法。
String getTotalPrice(String[] listData){
   for (int i=0;i<listData.length;i++){
       if("a".equals(listData[i])){
           return "a";
       }
       if("b".equals(listData[i])){
           return "b";
       }
   }
   return "";
}
改为
String getTotalPrice(String[] listData){
    List<String> flagData = Arrays.asList(listData);
   for (int i=0;i<listData.length;i++){
       if(flagData.contains(listData[i])){
           return listData[i];
       }
   }
   return "";
}
重构可以把复杂的东西分解为比较容易理解的小块,解决问题的方法有好多种,应该选择一种更清晰方式取代复杂方式,提倡大型函数尽量的去进行分解为小函数。

三、在对象之间搬移特性

1、搬移函数
在程序中,有个函数与其所驻类之外的另一个类进行更多的交流:调用后者,或被后者调用。
在该函数最常引用的类中建立一个有着类似行为的新函数,将旧函数变为一个单纯的委托函数,或者是将旧函数完全移除。
public class UserInfo {
    private  HabitInfo habitInfo;
    private  int userId;
    String userHabit(){
        if("moive".equals(habitInfo.getHabit(getUserId()))){
            return "moive";
        }
        return "book";
    }
    int getUserId(){
        return 1101;
    }
}
public class HabitInfo {
    public String getHabit(int userId){
        if(userId>200){
            return "moive";
        }else {
            return "book";
        }
    }
}
改为
public class UserInfo {
    private  HabitInfo habitInfo;
    private  int userId;
    String userHabit(){
        return habitInfo.userHabit(this);
    }
    int getUserId(){
        return 1101;
    }
}
public class HabitInfo {
    public String getHabit(int userId){
        if(userId>200){
            return "moive";
        }else {
            return "book";
        }
    }
    public String userHabit(UserInfo habitInfo){
        if("moive".equals(getHabit(habitInfo.getUserId()))){
            return "moive";
        }
        return "book";
    }
}
检查源类中源函数使用的一切特性,包括字段和函数,考虑他们是否也该搬移;
若源类的子类或父类,它们是否有该函数的其他声明,出现声明,则无法进行搬移,除非目标函数也同样具有多态性;
若目标函数使用源类中的字段或其他方法,可将源类中相关字段以及源类引用作为参数传给目标函数;
修改源函数,使之成为纯委托函数;
决定是否删除源函数,还是处理为委托函数保留下来,若是经常要在源对象中引用目标函数,则将源函数处理为委托函数比较简单,若要删除的话,要把源类中对源函数的所有调用,替换为目标函数调用。
2、搬移字段
在程序中,某个字段被其所驻类之外的另一个类更多的使用到,可在目标类新建一个字段,修改源字段的所有用户,改为新字段。
3、提炼类
某个类做了两个类应该做的事。一个类应该是一个清楚的抽象,因此,可以建立一个新类,按责任,将相关的字段和函数从旧类搬移到新类。
4、将类内联化 
Inline Class正好与Extract Class相反;某个类没有做太多的事情,将这个类的所以特性搬移到另一个类中,然后移除原类。一个类不再承担足够的责任,不再有独立存在的理由,就这样可以干。
5、隐藏委托关系
客户通过一个委托类来调用另一个对象,可在服务类上建立客户所需的所有函数,用以隐藏委托关系。
public class UserInfo {
    private  HabitInfo habitInfo=new HabitInfo() ;
    public HabitInfo getHabitInfo() {
        return habitInfo;
    }
}
public class HabitInfo {
    public String getHabit() {
        return "book";
    }
}
//客户端在调用
UserInfo userInfo=new UserInfo();
userInfo.getHabitInfo().getHabit();
改为
public class UserInfo {
    private  HabitInfo habitInfo=new HabitInfo();
    public String getHabit(){//委托函数
       return habitInfo.getHabit();
    }
}
//客户端在调用
UserInfo userInfo=new UserInfo();
userInfo.getHabit();
通过服务端UserInfo建立委托函数,避免了对HabitInfo工作原理的暴露,同时可以减少客户对HnabitInfo的耦合度。
6、移除中间人
某个类做了过多的简单委托动作,可让客户直接调用受托类。
每当客户要使用受托类的新特性时,就必须在服务端添加委托函数,但是随着受托类的特性越来越多,继续添加服务端委托函数,显得很麻烦,痛苦,此时直接让客户调用受托类。这个和隐藏委托相反。很难说什么程度的隐藏才合适,在随着系统开发过程不断做调整。
7、引入外加函数
你需要为提供服务的类增加一个函数,但是你无法修改这个类,此时,在客户类中建立一个函数,并以第一参数传入一个服务类的实例。
Date date=new Date(song.getDuration(),song.getDuration(),song.getDuration()+1);
改为
Date date=SongDate(song);
private Date SongDate(Song song) {
   return  new Date(song.getDuration(),song.getDuration(),song.getDuration()+1);
}
在客户类中创建一个函数,用来提供你所需要的功能,这个函数不应该调用客户类的任何特性,如果需要值,把该值当做参数传给它;
以服务类的实例作为该函数的第一个参数;将该函数注释为外加函数;
8、引入本地扩展
当你需要为服务类提供额外的函数,但你又无法修改这个类,可以建立一个新类,使它包含这些额外函数,让这个类成为原类的子类或包装类。
在扩展类加入转型构造函数,所谓“转型构造函数”是指“接受原对象作为参数”的构造函数。如果采用子类化方案,那么转型构造函数应该调用适当的超类构造函数;如果采用包装类方案,那么转型构造函数应该将它得到的传入参数以实例变量的形式保存起来,用作接受委托的原对象。
使用子类:
public class UserInfo extends HabitInfo {
    public UserInfo (String freeStyle){
        super(freeStyle);
    }
    public UserInfo (HabitInfo habitInfo){
        super(habitInfo.getFreeStyle());
    }
    //添加额外特性
}
使用包装类:
public class UserInfo  {
    HabitInfo mHabitInfo;
    //转型构造函数则是对其实例变量的赋值而已
    public UserInfo (HabitInfo habitInfo){
        mHabitInfo=habitInfo;
    }
    //添加委托函数
}

四、重新组织数据

1、自封装字段
你直接访问一个字段,但与字段之间的耦合关系逐渐变得笨拙。可为这个字段建立取值/设置函数,并只以这些函数来进行访问字段。
字段访问方式有两种不同观点,一是在该变量定义所在类中,可自由访问,另一种是即使在这个类里也应该采用访问函数间接的访问。
间接访问的好处是,子类可以通过覆写改变获取数据的途径,比如对字段做些处理。
直接访问好处代码比较容易理解。
2、以对象取代数据值
有一个数据项,需要与其他数据和行为一起使用才有意义,可将数据项变成对象。(标)
3、将值对象改为引用对象
从一个类衍生出许多彼此相等的实例,希望将它们替换为同一个对象。可将这个值对象变成引用对象。(标)
4、将引用对象改为值对象(标)
有个引用对象,很小且不可改变,而且不易管理,可将它变成一个值对象。
5、以对象取代数组
有个数组,其中的元素代表各自不同的东西,可进行对象替换数组,对于数组中的每个元素,以一个字段来表示。
数组中的不同元素,改为类中不同的字段,通过set/get进行访问。
6、复制“被监视数据”
有些领域数据置身于GUI控件中,而领域函数需要访问这些数据,可将该数据复制到一个领域对象中,建立一个Observer模式,用以同步领域对象和GUI对象内的重复数据。(标)
7、将单向关联改为双向关联
两个类都需要使用对方特性,但其间只有一条单向链接,可添加一个方向指针,并修改函数能够同时更新两条链接。
在被引用类中增加一个字段,用户保存引用的类所属的类对象,然后在被引用类中,使用该对象进行访问。
8、将双向关联改为单向关联
两个类之间有双向关联,但其中一个类如今不再需要另一个类的特性,可去除不必要的关联。
9、以字面常量取代魔法数
有个字面数值,带有特别含义,可以创建一个常量,根据其含义进行命名,并将上述的字面数值替换为这个常量。
常量不会造成任何性能开销,却可以大大提高代码的可读性,若多处被使用,也方便修改。
魔法数指拥有特殊意义,却又不能明确表现出这种意义的数字。
10、封装字段
类中存在public 字段,将它声明为private,并提供相应的访问函数set/get。
11分装集合
有个函数返回集合,让这个函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数。集合的拥有对象可以控制集合元素的添加和移除。取值函数返回一个只读副本,确保没有任何用户可以通过取值函数修改集合。
public class UserInfo  {
    List<HabitInfo> mHabitInfoList=new ArrayList<>();
    public List<HabitInfo> getmHabitInfoList() {
        return Collections.unmodifiableList(mHabitInfoList);//返回只读副本
    }
    public void setmHabitInfoList(List<HabitInfo> mHabitInfoList) {
        this.mHabitInfoList = mHabitInfoList;
    }
    public void addList(List<HabitInfo> list){
        mHabitInfoList.addAll(list);
    }
    public void removeList(HabitInfo info){
        mHabitInfoList.remove(info);
    }
}
对于分装数组,可以把数组转化为集合处理。
12、以数据类取代记录
你需要面对传统编程环境中的记录结构,可为改记录创建一个“哑”数据对象。
创建个新类表示这个记录,对于记录中的每一项数据,在新类里建立对应的一个private字段,并提供相应的取值/设置函数。
13、以类取代类型码
类之中有一个数值类型码,但它并不影响类的行为,以一个新的类替换该数值类型码。(标)
14、以子类取代类型码
有一个不可变的类型码,它会影响类的行为,以子类取代这个类型码。(标)
像switch语句,可将类型码转化为可拥有多态行为的继承体系。这样的继承体系应该以类型码的宿主类为基类,并针对每一种类型码各建立一个子类。
15、取代类型码
有个类型码,它会影响类的行为,但你无法通过继承手法消除它,需以状态对象取代类型码。(标)
新建一个类,根据类型码的用途为它命名,这就是一个状态对象;
为这个新类添加子类,每个子类对应一种类型码。
在基类中建立一个抽象的查询函数,用以返回类型码,在每个子类中覆写该函数,返回确切的类型码。
16、以字段取代子类
各个子类的唯一差别只在“返回常量数据”的函数身上,需修改这些函数,使它们返回基类中的某个(新增)字段,然后销毁子类。
子类的逻辑处理很简单就返回一些常量,没有十足的存在价值,就这样处理。

四、简化条件表达式

1、分解条件表达式
有个if -then-else语句,可从if、then、else三个段落中分别提炼出独立函数。
条件表达式和条件逻辑进行提炼,以用途命名,提炼到函数中,让整个结构清晰,易于阅读。
2、合并条件表达式
有一系列条件测试,都得到相同的结果,可将这些测试合并为一个条件表达式,并将这个条件表达式提炼成为一个独立函数。
使用逻辑或,逻辑与进行条件处理。
3、合并重复的条件片段
在条件表达式的每个分支上有着相同的一段代码,可将这段重复代码搬移到条件表达式之外。
公共代码位于条件表达式起始处,就将其移至条件表达式开始之前;
公共代码位于条件表达式结尾处,就将其移至条件表达式开始之后;
公共代码不止一条,就提炼到一个独立的函数中;
对于异常,可将公共代码移至final区段;
4、移除控制标记
在一系列布尔表达式中,某个变量带有“控制标记”的作用,可将以break语句或return语句取代控制标记。
public void removeList(String[] userArr){
    boolean foud=false;//控制标记
    for (int i=0;i<userArr.length;i++){
        if(!foud){
            if("apple".equals(userArr[i])){
                sendMessage();
                foud=true;
            }
            if("duerOs".equals(userArr[i])){
                sendMessage();
                foud=true;
            }
        }
    }
}
public void removeList(String[] userArr){
    for (int i=0;i<userArr.length;i++){
            if("apple".equals(userArr[i])){
                sendMessage();
                break;//break 取代控制标记
            }
            if("duerOs".equals(userArr[i])){
                sendMessage();
                break;
            }
    }
}

public String removeList(String[] userArr){
    String name="";
    for (int i=0;i<userArr.length;i++){
        if(name.equals("")){
            if("apple".equals(userArr[i])){
                sendMessage();
                name= "apple";
            }
            if("duerOs".equals(userArr[i])){
                sendMessage();
                name= "duerOs";
            }
        }
    }
    return name;
}
public String removeList(String[] userArr){
    String name="";
    for (int i=0;i<userArr.length;i++){
        if(name.equals("")){
            if("apple".equals(userArr[i])){
                sendMessage();
                return "apple";//return 取代控制标记
            }
            if("duerOs".equals(userArr[i])){
                sendMessage();
                return "duerOs";
            }
        }
    }
    return "";
}
5、以卫语句取代嵌套条件表达式
函数中的条件逻辑使人难以看清正常的执行路径,可使用卫语句表现所以特殊情况。
如果某个条件极端罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回,这样的单独检查常常被称为“卫语句”。
double getPayAmount(){
    double result;
    if(isDead) result=deadPay();
    else {
        if(isSeparated) result=separatedPay();
        else {
            if(isRetired) result=retiredPay();
            else  result=normalPay();
        }
    }
    return result;
}
改为
double getPayAmount() {
    if (isDead) return deadPay();
    if (isSeparated) return separatedPay();
    if (isRetired) return retiredPay();
    return normalPay();
}
也可以将条件反转,
double getPayAmount() {
   double result=0.0;
    if(start>0.0){
        if(end>0.0&&duration>0.0){
            result=(aVaule/bValue)*50.3;
        }
    }
    return result;
}
double getPayAmount() {
    double result = 0.0;
    if (start <= 0.0) return 0.0;//条件反转
    if (end <= 0.0 || duration <= 0.0) return 0.0;
    return (aVaule / bValue) * 50.3;
}
6、以多态取代条件表达式
程序中有个条件表达式,它根据对象类型的不同而选择不同的行为,可将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。
7、引入Null对象
你需要再三检查某对象是否为null,可将null值替换为null对象。(标)
8、引入断言
某一段代码需要对程序状态做出某种假设,可可以用断言明确表现这种假设。(标)
五、简化函数调用
1、函数改名
函数名称未能揭示函数用途,可修改函数名称。
重构当然要让程序更加清晰的理解阅读,肯定得有个好名字。
2、添加参数
某个函数需要从调用端得到更多信息,可为此函数添加一个对象参数,让该对象带进函数所需信息。
3、移除参数
当函数本体不再需要某个参数,可将其移除参数。
4、将查询函数和修改函数分离
某个函数既有返回对象状态值,又修改对象状态,可建立两个不同的函数,其中一个负责查询,另一个负责修改。
5、令函数携带参数
若干函数做了类似的工作,但在函数本体中却包含了不同的值,可建立单一函数,以参数表达那些不同的值。
多个函数根据参数不同,进行适当逻辑处理,合并为一个单一的函数,或是将函数内重复模块,因包含不同的数值,来提到独立函数,根据携带参数进行统一处理。
6、以明确函数取代参数
有一个函数,其中完全取决于参数值而采取不同行为,可针对该参数的每一个可能的值,建立一个独立函数。
将少量数值视为参数,找出重复性代码,建立独立函数。
把在一个函数里因参数值不同而处理的不同情况,抽取到不同的单独函数里。
7、保持对象完整
当你从某个对象中取出若干的值,将它们作为某一次函数调用时的参数,可改为传递整个对象作为函数参数。
这样当参数数改变事,不必去修改被调函数的参数,被调函数可以像参数对象请求它需求的任务信息,这样更容易被使用。
8、以函数取代参数
对象调用某个函数,并将所得结果作为参数,传递给另一个函数,而接受该参数的函数本身也能够调用前一个函数,可以让参数接受者去除该项参数,并直接调用前一个函数。
9、引入参数对象
某些参数总是很自然地同时出现,可以用一个对象取代这些参数。
10、移除设置函数
类中的某个字段应该在创建对象时被设置,然后就不再改变,可以去掉该字段的所有设置函数。
若是给某个字段提供set函数设置,就意味着字段可以被改变,若是希望某个字段不想被修改,给字段加final,去掉提供的设值函数,这样自己的意图就比较清晰。
11、隐藏函数
有个函数,从来没有被其他任何类用到,可将这个函数修改为private。
12、以工厂函数取代构造函数
希望在创建对象时不仅仅是做简单的建构动作,可将构造函数替换为工厂函数。
public HabitInfo(int mode){
    this.mode=mode;
}
public static HabitInfo create(int mode){
    this.mode=mode;
}
13、封装向下转型
某个函数返回的对象,需要由函数调用者执行向下转型,可将向下转型动作移到函数中。
Object getUser(){
    return user.getUserInfo();
}
UserInfo getUser(){
    return (UserInfo)user.getUserInfo();
}
14、以异常取代错误码
某个函数返回一个特定的代码,用以表示某种错误情况,可将改用异常。(标)
15、以测试取代异常
面对一个调用者可以预先检查的条件,你抛出了一个异常,可修改调用者,使它在调用函数之前先做检查。
去调用一个函数时,会抛出一个异常,一般会采用try catch进行处理,但是可以在调用函数之前,对一些条件做下检测判断处理,以此来用检测代码取代异常。

五、处理概括关系(即继承关系)

1、字段上移
两个子类拥有相同的字段,可将字段移至超类。
除去重复的声明,字段名称不同,但是它们以同样的方式被使用时,可将名称进行修改,统一移至基类,来达到去重复作用;若这些字段是private,必须将基类中的字段声明为protected,这样子类才能引用它。
2、函数上移
有些函数,在各个子类中产生完全相同的结果,可将该函数移至基类。
去重复,避免修改其一又必须修改其他二的麻烦;
在子类中待提升的函数的代码移至基类,但是待提升的函数又调用了一个只出现在子类而未出现在基类的函数,可以在基类中为被调用的函数声明一个抽象函数;
如果待提升函数使用了一个子类的一个字段,可以将该字段也提升到基类,或者在基类中把取值函数声明为抽象函数。
3、构造函数本体上移
在各个子类中拥有一些构造函数,他们的本体基本完全一致,可在超类中新建一个构造函数,并在子类构造函数中调用它。
将子类构造函数中的共同代码搬移至基类构造函数,子类构造函数再调用基类构造函数super。
4、函数下移
基类中的某个函数只与部分(而非全部)子类有关,可将这个函数移到相关的那些子类去。
这个和2中相反,可能需要将基类某些字段声明为protected,让子类函数也能够进行访问,以及需要访问的基类提供的函数,若不是public也要声明为protected。
5、字段下移
基类中的某些字段只被部分(而非全部)子类用到,可将这些字段移至需要它的那些子类去。
6、提炼子类
类中的某些特性只被某些(而非全部)实例用到,可新建一个子类,将上面所说的那一部分特性移至子类中。
7、提炼超类
两个类有相似的特性,可为这两个类建立一个超类,将相同特性移至超类。
为原本的类新建一个空白的抽象类;
将子类共同的元素上移到超类;如果相应的子类函数名不同,但用途相同,改名字一致,然后移至基类;如果相应的子类函数名相同,但是函数本体不同,可在基类中把它的公共签名声明为抽象函数;如果子类有不同的函数本体,但用途相同,克将一个函数的函数本体复制到另一个函数中,然提炼到基类中。
8、提炼接口
若干客户使用的类接口中的同一子集,或者两个类的接口有部分相同,可将相同的子集提炼到独立的接口中。
9、折叠继承体系
基类和子类之间无太大区别,可将他们合为一体。
10、塑造模板函数
有些子类,其中相应的某些函数以相同的顺序执行类似的操作,但各个操作的细节上有所不同,可将这些操作分别放进独立的函数中,并保持他们都有相同的签名,于是原函数也就变得相同了,然后将原函数上移至基类。


11、以委托取代继承
某个子类只使用基类接口中的一部分,或者根本不需要继承而来的数据,可在子类中新建一个字段用以保存基类,调整子类函数,令它改而委托基类,然后去掉两者之间的继承关系。
子类之前调用基类的函数,改为基类对象调用函数,客户调用的每个基类函数,为它添加个简单的委托函数;
12、以继承取代委托
在两个类之间使用委托关系,并经常为整个接口编写许多极简的委托函数,可将委托类继承受托类。
这个和11相反,发现自己使用的受托类中的所有函数,都要编写所有极简的委托函数,就按12来处理。

六、大型重构

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


2、将过程化设计转化为对象设计
有一些传统过程化风格的代码,可将数据记录变成对象,将大块的行为分为小块,并将行为移入相关对象之中。
对每个记录类型,将其转变为只含访问函数的哑数据对象,对每一处过程化风格,将其代码提炼到一个独立的类中,
对于每一段过长的程序,进行分解。
3、将领域和表述/显示分离
某些GUI类之中包含了领域逻辑,将领域逻辑分离出来,为它们建立独立的领域类。
借鉴MVC模型,领域逻辑即M,领域类不包含任何与程序外观相关的代码,只包含业务逻辑相关代码。
4、提炼继承体系
有某个类做了太多工作,其中一部分工作是以大量条件表达式完成的,可建立建立继承体系,以一个子类表示一种特殊情况。
七、总结
研读了两个礼拜的时间,来看这本《重构改善既有代码的设计》,但是不少知识点还是没理解,容易理解混淆,尤其那种“A结构重构B结构”,“B结构重构A结构”形式,很难找到相关处理的依据,而依据往往不是很清晰。上文“标”就是没弄懂的,而其他的参考示例,感受到了一些味道。重构是对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。最近刚好有时间,所以在自己项目里尝试小的动作来进行重构,有时回头看自己几个月前写的代码,的确有些地方,自己都觉得很糟糕。看这本书之前,有时也想重构,让代码清晰,容易理解,方便修改,但是还是很难下手,只是简单的修改让代码清晰干净点,此书中提到为什么不肯重构程序的几个可能的原因:
1.你不知道如何重构。
2.如果这些利益是长远的,何必现在付出这些努力呢?长远看来,说不定当项目收获这些利益时,你已经不在职位上了。
3.代码重构是一项额外工作,老板付钱给你,主要是让你编写新功能。
4.重构可能破坏现有程序。 
这些因素也是客观存在的,但是为了能提升下自己的能力,学以致用,同时方便自己和后人阅读代码,修改,我就干。在自己的项目里重构时,出现不少比较长的函数,就分解此函数,比如像订单详情和产品详情类;封装的基类和子类对部分函数进行移动;重复代码的提炼抽取函数;比较多的参数函数;函数命名;责任独立分装;合并表达式等等,这些都是普遍存在的,容易下手可进行重构的。大的方向重构,掌握些设计模式会很有用,此书,后期还得重复几遍,随着工作经验的积累,才能更多的理解重构的内涵东西。






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值