2 重新组织函数

1 Extract Method(提炼函数)

概念

Extract Method应该是最常用的重构手法了,当遇到一个过长的代码或者需要添加注释才能让人理解其用途的代码时,就可以运用Extract Method将代码提炼到一个函数上。该方法的一个重点时函数命名,只有给函数起个适当的名字时,他们才能真正起作用。对于是否使用该重构手法,代码长度不是问题,关键在于函数名称和函数本体之间的语义距离。

如果提炼可以强化代码的清晰度,那就去做,就算函数名称比提炼出来的代码还长也无所谓。

重构实例1

1
2
3
4
5
6
7
8
9
// 重构前
void PrintOwing(double amount)
{
PrintBanner();

// print details
cout << "name: " << m_name << endl;
cout << "amount: " << amount << endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
// 重构后
void PrintOwing(double amount)
{
PrintBanner();
PrintDetails(amount);
}

void PrintDetails(double amount)
{
cout << "name: " << m_name << endl;
cout << "amount: " << amount << endl;
}

当重构的代码中带有临时变量时,可以将局部变量作为提炼函数的入参(参见重构示例1)。如果涉及到对临时变量的再赋值,可能还需要返回处理结果(参见重构示例2)

重构实例2

1
2
3
4
5
6
7
8
9
10
11
12
// 重构前
void PrintOwing(double amount)
{
PrintBanner();

double outstanding = amount * 1.2;
for (auto& order : m_orders) {
outstanding += order.Amount();
}

PrintDetails(outstanding);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 重构后
void PrintOwing(double amount)
{
PrintBanner();
double outstanding = amount * 1.2;
outstanding = CalOutstanding(outstanding);
PrintDetails(outstanding);
}

double CalOutstanding(double val)
{
double result = val;
for (auto& order : m_orders) {
result += order.Amount();
}
return result;
}

在临时变量过多的情况下,可以采用Replace Temp with Query减少临时变量。

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

由于临时变量只能在所属函数内可见,因此会驱使你写出更长的函数。如果把临时变量替换为一个查询函数,那么同一个类中的所有函数都将可以获得这份信息。除此之外,Replace Temp with Query还可以使代码可维护性更好,如果临时变量的计算方式改变了,只需修改查询函数即可。

重构示例3

1
2
3
4
5
6
7
8
9
10
// 重构前

double basePrice = m_quantity + m_itemPrice;
if (basePrice > 1000)
{
return basePrice * 0.95;
} else
{
return basePrice * 0.98;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 重构后

if (BasePrice() > 1000)
{
return BasePrice() * 0.95;
} else
{
return BasePrice() * 0.98;
}

double BasePrice()
{
return m_quantity + m_itemPrice;
}

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

Introduce Explaining Variable也是一个很常用的重构手法,如果表达式非常复杂而难以阅读,引入临时变量可以将表达式分解成容易管理的形式,可读性更强。该手法在条件逻辑中特别有价值——将每个条件子句提炼出来,以一个良好命名的临时变量来解释对应条件子句的意义。

重构示例4

1
2
3
4
5
6
// 重构前
if ((platform.find("MAC") != platform.end()) &&
(browser.find("IE") != browser.end()) &&
IsInit() && resize > 0) {
// do something
}
1
2
3
4
5
6
7
// 重构后
bool isMacOs = platform.find("MAC") != platform.end();
bool isIEBrowser = browser.find("IE") != browser.end();
bool isResized = resize > 0;
if (isMacOs && isIEBrowser && IsInit() && isResized) {
// do something
}

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

如果有某个临时变量被赋值超过一次,则可以使用Split Temporary Variable进行重构,针对每一次赋值,创造一个独立、对应的临时变量。

重构示例5

1
2
3
4
5
6
7
8
9
10
11
// 重构前
double temp = 2 * (m_height + m_width);
cout << temp << endl;
temp = m_height * m_width;
cout << temp << endl;

// 重构后
double perimeter = 2 * (m_height + m_width);
cout << perimeter << endl;
double area = m_height * m_width;
cout << area << endl;

在软件开发过程中应该避免使用temp/tmp作为变量名。

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

对于一个大型的函数,如果有太多的临时变量导致无法采用Extract Method,而采用Replace Temp with Query

又产生过多的查询函数时,就可以采用Replace Method with Method Object对函数进行重构。

将函数放进一个单独对象中,如此一来局部变量就成了对象内的字段。然后你可以在同一个对象中将这个大型函数分解成多个小型函数。

重构示例6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 重构前
class Account {
...
int GetGamma(int inputVal, int quantity, int yearToDate)
{
int importantVal1 = (inputVal * quantity) + Delta();
int importantVal2 = (inputVal * yearToDate) + 100;
if ((yearToDate - importantVal1) > 100) {
importantVal2 -= 20;
}
int importantVal3 = importantVal2 * 7;
...
return importantVal3 - 2 * importantVal1;
}
}
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
// 重构后
class Account {
// ...
int GetGamma(int inputVal, int quantity, int yearToDate)
{
Gamma gamma(this, inputVal, quantity, yearToDate);
return gamma.Compute();
}
}

class Gamma {
public:
Gamma(Account* account, int inputVal, int quantity, int yearToDate);
int Compute()
{
importantVal1 = (inputVal * quantity) + account->Delta();
importantVal2 = (inputVal * yearToDate) + 100;
if ((yearToDate - importantVal1) > 100) {
importantVal2 -= 20;
}
importantVal3 = importantVal2 * 7;
// ...
return importantVal3 - 2 * importantVal1;
}
private:
Account* account;
int inputVal;
int quantity;
int yearToDate;
int importantVal1;
int importantVal2;
int importantVal3;
}