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

《重构-改善既有代码的设计》第一版

程序员文章站 2024-03-21 19:16:04
...

简介

重构的前提:先构建安全网,然后在不改变功能外在行为的前提下重构。

重构的心法:旧的不变,新的创建,一步切换,旧的再见。

代码坏味道列表

有些重构手法没有包含在这份列表中。

Duplicated cod 重复代码

重构方式

  • Extract Method:同一个类中有重复代码,则提取公共函数;
  • Extract Class:如果某个类做了应该由两个类做的事情,那么创建一个新类,将相关的字段和函数从旧类搬移到新类;
  • Pull up Method:如果兄弟类中有重复代码,则提取公共函数后,再pull up method到父类;
  • Form Template Method:如果兄弟类之间只是代码类似,并非完全相同,则运用Extract Method将相似部分和差异部分分隔开,然后使用模板方法设计模式,将共同的部分放在父类中,差异部分在子类中分别实现;
  • 如果是不相关的两个类有重复的代码,那么可以抽取到第三个类中,如果放在两个类中的某个类,具体视情况而定。

Long Method过长函数

  • Extract Method:拆分成若干函数。每当感觉需要以注释来说明的时候,我们就把需要说明的东西写进一个独立函数中,并以其用途(而非实现手法)命名。
  • Replace Temp With Query以查询取代临时变量:有时临时变量或表达式可能在类中的多个函数中被使用,那么此时可以将变量或者表达式抽取为了类中的查询函数,供多个函数一起使用。
  • Replace Method with Method Object以函数对象取代函数:如果有一个大型函数,其中对局部变量的使用使得无法采用Extract Method。那么就将这个函数放进一个单独对象中,此时局部变量就成了对象内的字段。然后就可以在同一个对象中将这个大型函数分解为多个小型函数。
  • Decompose Conditional分解表达式:如果有一个复杂的条件(if-then-else)语句,那么就将3个段落分别提炼为独立函数。

Large Class 大类

  • Extract Class:如果某个类做了应该由两个类做的事情,那么创建一个新类,将相关的字段和函数从旧类搬移到新类;
  • Extract subClass:如果类中的某些特征只被某些实例用到,那么就新建一个子类,将部分实例使用的特征移到子类中。
  • Extract Interface:如果有多个类使用某个类接口中的同一个子集,或者两个类的接口有部分相同,那么就将相同的子集提炼到一个独立接口中。
  • Replace Data Value with Object以对象取代数据值:如果数据项要和其他数据以及行为一起使用才有意义,那么就应该将数据项封装为对象。

Long Parameter List 过长参数列表

将参数封装成结构或者类。

  • Replace Parameter with Method以函数取代参数:对象调用某个函数,并将所得结果作为参数,传递给另一个函数,而接受该参数的函数本身也能够调用前一个函数。此时让参数接受者去掉该参数,并直接调用前一个函数。
int basePrice = _quantity * _itemPrice;
discountLevel = getDisscountLevel();
double finalPrice = discountedPrice(basePrice, discountLevel);

改为

int basePrice = _quantity * _itemPrice;

double finalPrice = discountedPrice(basePrice);

private double getDisscountLevel(int basePrice){
    discountLevel = getDisscountLevel();
}
  • Introduce Parameter Object引入参数对象:某些参数总是很自然地同时出现,此时以一个对象取代这些参数。
  • Preserve Whole Object保持对象完整:从某个对象中取出若干值,将它们作为某一次函数调用的参数;此时改为传递整个对象。

Divergent Change:发散式变化

发散式修改,是指一个类受多种变化的影响。改其他需求时,都会动它,说明该类承担了过多的职责,需要采用Extract Class方法,将其拆成多个类,分离成不变和可变部分,将总是一起变化的东西放在一块儿。多个Fan in.

Shotgun Surgery:散弹式修改

改某个需求的时候,要改很多类。将各个修改点,集中起来,抽象成一个新类。多个Fan out.

  • Move Method:某个函数与其所在类之外的另一个类镜像更多交流:调用后者或者被后者调用;此时在该函数最常引用的类中建立一个有着类似行为的新函数,将旧函数变成一个单纯的委托类,或是将旧函数完全移除。
  • Move Field:某个字段被其所在类之外的另一个类更多地使用,此时在目标类新建一个字段,修改源字段的所有使用者,令它们改用该新字段。
  • Inline class将类内联化:某个类没有做太多事情,此时将该类的所有特性搬到另一个类中,然后移除原类。和Extract Class刚好相反。

Feature Envy 依恋情结

使用了大量其他类的成员.
将这个函数挪到那个类⾥里里⾯面。

  • Move Method:
  • Move Field:
  • Extract Method:

Data Clumps 数据团

经常一起出现的一组数据,给它们创建⼀个新的类。

  • Extract Class:如果某个类做了应该由两个类做的事情,那么创建一个新类,将相关的字段和函数从旧类搬移到新类;
  • Introduce Parameter Object:某些参数总是很自然地同时出现,此时以一个对象取代这些参数。
  • Preserve Whole Object:从某个对象中取出若干值,将它们作为某一次函数调用的参数;此时改为传递整个对象。

Primitive Obsession 基本类型偏执

热衷于使用int,long,String等基本类型。反复出现的一组参数,有关联的多个字段转换成类。

  • Replace Data Value with Object:如果数据项要和其他数据以及行为一起使用才有意义,那么就应该将数据项封装为对象。
  • Extract Class:如果某个类做了应该由两个类做的事情,那么创建一个新类,将相关的字段和函数从旧类搬移到新类;
  • Introduce Prameter Object:某些参数总是很自然地同时出现,此时以一个对象取代这些参数。
  • Replace Array with Object:如果有一个数组,其中的元素各自代表不同的内容。那么以对象替换数组,对于数组中的每个元素,以一个字段来表示。
String []row = new  String[2];
row[0]="Liverpool";
row[1]="15";

改为
Performance row = new Performance();
row.setName("Liverpool");
row.setWins("15");
  • Replace Type code with class以类取代类型码:类之中有一个数值类型码,但它并不影响类的行为。此时以一个新类替换该数值类型码。
class Person{
    int O;
    int A;
    int B;
    int AB;
}

其实用枚举更合适点。

  • Replace Type Code with Subclasses以子类取代类型码:如果有一个不可变的类型码,它会影响类的行为。那么以子类取代这个类型码。
class Employee{
    int ENGINEER;
    int SALESMAN
}

重构为:
class Employee{
    
}

class Engineer extends Employee{
    
}

class Salesman extends Employee{
    
}
  • Repalce Type code with State/Strategy以State/Strategy取代类型码:
class Employee{
    int ENGINEER;
    int SALESMAN
}

重构为:
class Employee{
    void setType(int arg){
        _type = EmployeeType.newType(arg);
    }
}

class EmployeeType{

    static EmployeeType newType(int code){
        switch(code){
            case ENGINEER: return new Engineer();
            case SALESMAN: return new Salesman();
            default:
            throw new IllegalArgumentException("Incorrent Employee Code");
        }
    }
    
    static final ENGINEER = 0;
    static final SALESMAN = 1;
}

class Engineer extends EmployeeType{
    
}

class Salesman extends EmployeeType{
   
}

Switch Statements switch语句

  • Replace Conditional with Polymorphisrn以多态取代条件表达式:将switch表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。
  • Replace Type Code with Subclasses:
  • Replace Type Code with State/Strategy:
  • Replace Parameter with Explicit Methods以明确函数取代参数:一个函数完全取决于参数值而采取不同行为,那么针对该参数的每一个可能值,建立一个独立的函数。
void setValue(String name,int value){
    if (name.equals("height")){
        _height = value;
        return ;
    }
    
    if(name.equals("width")){
        _width = value;
        return;
    }
}

重构为:
void setHeight(int value){
    _height = value;
}

void setWidth(int value){
    _width = value;
}
  • Introduce Null Object引入Null对象:将null值替换为null对象。

Lazy Class:冗赘类

如果它不干活了,就炒掉它。把这些不再重要的类里面的逻辑,合并到相关类,删掉旧的。

  • Inline Class:
  • Collapse Hierarchy折叠继承体系:如果超类和子类之间无太大区别,就将它们合为一体。

Parallel Inheritance Hierarchies:平行继承体系

增加A类的子类ax,B类也需要相应的增加一个bx。
应该有一个类是可以去掉继承关系的。

  • Move Method:
  • Move Field:

Speculative Generality:夸其谈未来性

过度设计,兴奋需求,直接删除。

  • Inline Class:
  • Collapse Hierarchy:
  • Remove Parameter:函数本体不再需要某个参数,此时将该参数去除。
  • Rename Method:函数的名称未能揭示函数的用途,此时修改函数名称。

Message Chains:过度耦合的消息链

过度耦合的才是坏的。

  • Hide Delegate隐藏"委托关系":客户通过一个委托类来调用另一个对象。此时在服务类上建立客户所需的所有函数,用以隐藏委托关系。
client class -> Person和Deparment
Person -> Department
重构为:
Client class -> Person -> Department

Temporary Field:令人迷惑的临时字段

仅在特定环境下使用的变量。将这些临时变量集中到一个新类中管理。

  • Extract Class:
  • Introduce Null Object:

Middle Man 中间人

大部分都交给中间人来处理了。用继承替代委托。

  • Remove Middle Man
  • Inline Method:
  • Replace Delegation with Inheritance以继承取代委托:你在两个类之间使用委托关系,并经常为整个接口,编写许多极简单的委托函数。此时让委托类继承受拖类。

Inappropriate Intimacy太亲密

两个类彼此使用对方的私有的东西。
划清界限拆散,或合并,或改成单项联系。

  • Move Method:
  • Move Field:
  • Change Bidirectional Association to Unidirectional将双向关联改为单向关联:两个类之间有双向关联,但其中一个类如今不再需要另一个的特征。此时去掉不必要的关联。
  • Replace Inheritance with Delegation以委托取代继承:某个子类只使用超类接口中的一部分,或是根本不需要继承而来的数据。此时在子类中新建一个字段用以保存超类;调整子类函数,令它改而委托超类;然后去掉两者之间的继承关系。
  • Hide Delegate:

Alternative Classes with Different Interfaces异曲同工的类

重命名,移动函数,或抽象子类。

  • Rename Method:
  • Move Method:

Incomplete Library Class:不完善的类库

包一层函数或包成新的类。

  • Introduce Foreign Method:
  • Introduce Local Extension:

Data Class:纯数据类

类很简单,仅有公共成员变量,或简单操作函数。将相关操作封装进去,减少public成员变量。

  • Move Method:
  • Encapsulate Field:
  • Encapsulate Collection:

Refused Bequest 被拒绝的遗赠

父类里面方法很多,子类只用有限几个。用代理理替代继承关系。

  • Replace Inheritance with Delegation:

Comments 太多注释

这里指代码太难懂了,不得不用注释解释。|避免用注释解释代码,而是说明代码的目的,背景等。好代码会说话。

  • Extract Method:
  • Introduce Assertion引入断言:某一段代码需要对程序状态做出某种假设。此时以断言明确表现这种假设。
相关标签: 重构