目录

代码重构

转自:维基百科中的代码重构

一开始我写点啥,但是wiki上写的已经非常完美了。我发现我可以做的只有抄过来了。。

代码重构(code refactoring)指对软件代码做任何更动以增加可读性或者简化结构而不影响输出结果。

软件重构需要借助工具完成,重构工具能够修改代码同时修改所有引用该代码的地方。在极限编程的方法学中,重构需要单元测试来支持。

重构代码

在软件工程学裡,重构代码一词通常是指在不改变代码的外部行为情况下而修改源代码,有时非正式地称为「清理乾淨」。在极限编程或其他敏捷方法学中,重构常常是软件开发循环的一部分:开发者轮流增加新的测试和功能,并重构代码来增进内部的清晰性和一致性。自动化的单元测试保证了重构不至于让代码停止工作。

重构既不修正错误,又不增加新的功能性。反而它是用于提高代码的可读性或者改变代码内部结构与设计,并且移除死代码,使其在将来更容易被维护。重构代码可以是结构层面抑或是语意层面,不同的重构手段施行时,可能是结构的调整或是语意的转换,但前提是不影响代码在转换前后的行为。特别是,在现有的程序的结构下,给一个程序增加一个新的行为可能会非常困难,因此开发人员可能先重构这部分代码,使加入新的行为变得容易。

一个重构的小范例是修改一个变量的名称使其具有更明确的含义,例如从单个字母的「i」重构为「interestRate」。较复杂的重构是把一段if区块中的代码变为一个子程序。更复杂一点的重构是用多态性来替换if 条件式。「清理」代码已经发生了几十年,重构中最关键的认知是有意地「清理」代码,透过从已知的纪录里一些常用的重构方法清理代码,把它从增加新功能分开,然后个别的对代码进行测试(任何的行为改变都可能带来错误)。新的实现切合实际需要以改善现有设计,并且不改变原软件的意图或行为。

重构面对业界调适接受方面的挑战。首先,对重构长远的影响需要更深入研究追踪。再者,重构存于资料库轮廓(database schema)的商业逻辑层几乎是不可能或者非常困难的。最后,对介面造成影响的重构可能造成程序开发上的困境,除非程序员有对所有使用者介面的存取權。例如,程序员若改变某实体中的方法名称,他要么必须对整个专案里头所有连结到旧名的参考都加以编辑,要么屈服于继续维护使用旧名的残株残瓦介面。而该旧名的介面于内部呼叫该方法的新名。

源流

重构这个术语是从数字与多项式的因式分解类比而来。如,x**2 − 1可以被分解为(x + 1)(x − 1),这样揭示了前面的形式不可见的内部结构(如两个根+1和−1)。同样,在软件重构中,在可见结构上的改变通常会揭示原代码中「隐藏」的内部结构。

上面数学的例子展示了「重构」的问题。一个表示式不尽然客观地或者处处比另一个更好。它们每个强调不同的方程序的观点,故其实用便多多少少随着个别不同使用情况,以及各个数学家个性与风格变动。这个问题于软件开发领域亦然;个别程序员可能对某既定演算法理想的程序形式实现会有不同的意见。

为了简化测试,重构是分步骤完成的。当重构结束后,任何行为上的变化无疑都是错误并可透过除错该程序的新行为个别修正。

马丁·福勒的著作《重构》是一个经典参考书。虽然重构已经非正式的使用了很多年了,威廉·歐普迪克在1993年的博士论文却是第一篇著名的关于的重构的文章,即使所有的理论与机制长久以来就以程序转换系统存在。所有这些资源提供了一种常用重构方法的型录索引──所有重构方法需要描述,包括怎样辨识你需要(或者不需要)套用该方法,以及如何套用的问题。

重构方法简单列表

下面是不完整的代码重构清单。长一点的清单可以在福勒的重构书找到。因为研究者们继续努力不懈的发明以及实现重构,完整清单可能永远都不存在。

封装成员变量(Encapsulate Field)

将仅限于本类使用的变量重写成私有(private)成员变量,并提供访问方法(accessor method)。这种重构方式可以将与外部调用者无关的变量隐藏起来,减少代码的耦合性,并减少意外出错的概率。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 重构前
class SomeClass {
  public int memberA;
  ...
}

// 重构后
class SomeClass { 
  private int memberA; 
  public int getMemberA();
  public void setMemberA(int a);
  ...
}

提取方法(Extract Method)

意思是将大段代码中的一部分提取后,构成一个新方法。这种重构可以使整段程序的结构变得更清晰,从而增加可读性。这也对函数(Function)通用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 重构前
void Process(MyDataSet mds) 
{   
 // Step 1 ... 
 int result = 0;  
 if (mds.isReady) 
 {  
  int data1 = mds.param[0]; 
  int data2 = mds.param[1];  
  // Preprocess... 
  result = data1 % data2;  
 } 
 // Step 2... 
}

// 重构后
void Process(MyDataSet mds)
{  
 // Step 1 ... 
 int result = 0;  
 if (mds.isReady) 
  result = CalculateMDS(mds.param[0], mds.param[1]);  
 // Step 2 ... 
}      

int CalculateMDS(int data1, int data2)  
{ 
  // Preprocess...  
  return data1 % data2; 
}

类一般化(Generalize Type)

将多个类/函数共用的类型抽象出可以公用的基类(base class),然后利用多态性追加每个类/函数需要的特殊函数。这种重构可以让结构更加清晰,同时可以增加代码的可维护性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 重构前
class Rectangle {
  private:
  int w, h;
  public:
  double Area(){
    return w*h;
  }
}
class Triangle {
  private:
  int w, h;
  public:
  double Area(){
    return w*h/2;
  }
}

// 重构后
class Polygon {
  private:
  int w, h;
  public:
  virtual double Area() = 0;
}
class Rectangle : public Polygon {
  double Area(){
  return w*h;
  }
}
class Triangle : public Polygon {
  double Area() {
    return w*h/2;
  }
}

函数归父(Pull Up)

或译函数上移,指的是方法从子类移动到父类。

函数归子(Push Down)

或译函数下移,指的是方法从父类移动到子类。

方法更名(Rename Method)

将方法名称以更好的表达它的用途。

1
2
3
4
5
// 重构前
public double f(double m, double a);

// 重构后
public double calculateForce(double mass, double acceleration);

辞源

首先使用「重构」一辞于出版文献是于一篇文章:《Refactoring: An Aid in Designing Application Frameworks and Evolving Object-Oriented Systems, Proceedings of the Symposium on Object Oriented Programming Emphasizing Practical Applications (SOOPPA)》,1990年9月,由William F. Opdyke与Ralph E. Johnson联名出版[3]。William Opdyke的博士论文于「重构:物件导向框架」,伊利诺大学,1992年出版[2]。「重构」术语几乎至其后确定。

就英文新造字学来说,代码重构(Refactoring)清楚地来自数学上的分解(factoring)。