简介
重构的前提:先构建安全网,然后在不改变功能外在行为的前提下重构。
重构的心法:旧的不变,新的创建,一步切换,旧的再见。
代码坏味道列表
有些重构手法没有包含在这份列表中。
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。
应该有一个类是可以去掉继承关系的。
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引入断言:某一段代码需要对程序状态做出某种假设。此时以断言明确表现这种假设。