重新组织函数的使用方法
- extract method: 把一段代码从原先函数中提取出来,放进一个单独函数中
- inline method: 将一个函数调用动作更换为该函数本体
- replace temp with query: 去掉所有可去掉的临时变量
- split temporary variable: 比较容易的替换某个临时变量
- replace method with method object: 使用新类来替换临时变量
- remove assignments to parameters: 不要在函数内赋值给参数
- substitute algorithm 引入更清晰的算法,使代码工作更好
1. 提炼函数 Extract method
- 名称:提炼函数 Extract method
- 概要:有一段代码可以被组织在一起并独立出来。将这段代码放进一个独立函数中,并让函数名称解释该函数的用途
- 动机:每个函数的粒度越小,被复用的机会就会越多
- 做法:
- 创造一个新函数,根据函数用来“做什么”来命名,而不是以“怎么做”命名
- 将提炼出的代码从源函数复制到新建的目标函数中
- 查看局部变量和源函数参数是否引用了“作用域限于源函数”
- 查看是否有仅用于被提炼代码段的临时变量,在目标函数中将其声明为临时变量
- 查看是否有任何源函数的局部变量值被它改变。如果有一个,可以作为返回值。如果有多个,可以先使用split temporary variable,然后再尝试提炼。也可以使用replace temp with query将临时变量消灭掉。
- 将被提炼代码段中需要读取的局部变量,当作参数传给目标函数
- 处理完所有局部变量之后,进行编译
- 在源函数中,将被提炼代码段替换为对目标函数的调用。
- 删除源函数中临时变量的声明。
- 编译测试
- 代码演示
修改之前的代码:
///.h
#ifndef REFACTORMETHOD_H
#define REFACTORMETHOD_H
#include <QString>
#include <QVector>
class RefactorMethod
{
public:
RefactorMethod(QString name);
void PrintOwing(double amount);
private:
QString m_Name;
QVector<double> m_Order;
};
#endif // REFACTORMETHOD_H
///.cpp
#include "RefactorMethod.h"
#include <QDebug>
RefactorMethod::RefactorMethod(QString name)
{
m_Name = name;
}
void RefactorMethod::PrintOwing(double amount)
{
double outstanding = amount * 1.2;
//print banner
qDebug() << "**********************" << m_Name;
qDebug() << "****customer owes*****" << m_Name;
qDebug() << "**********************" << m_Name;
//calculate outstanding
for (double e : m_Order)
{
outstanding += e;
}
//print detail
qDebug() << "name = " << m_Name;
qDebug() << "amount = " << amount;
}
修改之后的代码:
///.h
#ifndef REFACTORMETHOD_H
#define REFACTORMETHOD_H
#include <QString>
#include <QVector>
class RefactorMethod
{
public:
RefactorMethod(QString name);
void PrintOwing(double amount);
void PrintDetails(double amount);
void PrintBanner();
double GetOutstanding(double initValue);
private:
QString m_Name;
QVector<double> m_Order;
};
#endif // REFACTORMETHOD_H
///.cpp
#include "RefactorMethod.h"
#include <QDebug>
RefactorMethod::RefactorMethod(QString name)
{
m_Name = name;
}
void RefactorMethod::PrintOwing(double amount)
{
//对局部变量再赋值
double outstanding = amount * 1.2;
outstanding = GetOutstanding(outstanding);
//无局部变量
PrintBanner();
//有局部变量
PrintDetails(amount);
}
void RefactorMethod::PrintDetails(double amount)
{
qDebug() << "name = " << m_Name;
qDebug() << "amount = " << amount;
}
void RefactorMethod::PrintBanner()
{
//print banner
qDebug() << "**********************" << m_Name;
qDebug() << "****customer owes*****" << m_Name;
qDebug() << "**********************" << m_Name;
}
//input: initValue
//output: result
double RefactorMethod::GetOutstanding(double initValue)
{
double result = initValue;
for (double e : m_Order)
{
result += e;
}
return result;
}
2. 内联函数 inline method
- 名称:内联函数 inline method
- 概要:一个函数的本体与名称同样清楚易懂。在函数调用点插入函数本体,然后移除该函数
- 动机:函数本体比名称更易读
- 做法:
- 检查函数,确定它不具有多态性。如果子类继承了这个函数,就不要将此函数内联,因为子类无法覆写一个根本不存在的函数
- 找出这个函数的所有被调用点,都替换为函数本体
- 编译,测试
- 删除该函数的定义
- 代码演示:
修改之前的代码:
///.h
#ifndef REFACTORMETHOD_H
#define REFACTORMETHOD_H
#include <QString>
#include <QVector>
class RefactorMethod
{
public:
int GetRating();
bool MoreThanFiveLateDeliveries();
private:
int m_NumberOfLateDeliveries;
};
#endif // REFACTORMETHOD_H
///.cpp
int RefactorMethod::GetRating()
{
return (MoreThanFiveLateDeliveries()) ? 2 : 1;
}
bool RefactorMethod::MoreThanFiveLateDeliveries()
{
return m_NumberOfLateDeliveries > 5;
}
修改之后的代码
///.h
#ifndef REFACTORMETHOD_H
#define REFACTORMETHOD_H
#include <QString>
#include <QVector>
class RefactorMethod
{
public:
int GetRating();
private:
int m_NumberOfLateDeliveries;
};
#endif // REFACTORMETHOD_H
///.cpp
int RefactorMethod::GetRating()
{
return (m_NumberOfLateDeliveries > 5) ? 2 : 1;
}
3. 内联临时变量 inline temp
- 名称:内联临时变量 inline temp
- 概要:有一个临时变量,只被一个简单表达式赋值一次。将所有对该变量的引用动作,替换为对它赋值的那个表达式自身。
- 动机:这个临时变量妨碍了其他重构手法
- 做法:
- 检查给临时变量赋值的语句,确保等号右边的表达式没有副作用
- 找到该临时变量的所有引用点,将它们替换为临时变量赋值的表达式
- 编译,测试
- 删除该临时变量的声明和赋值语句
- 再编译,测试
- 代码演示:
修改之前的代码:
double RefactorMethod::GetPrice()
{
return 100;
}
bool RefactorMethod::IsPriceCheap()
{
double price = m_Fruit.GetPrice();
return (price > 1000);
}
修改之后的代码:
double RefactorMethod::GetPrice()
{
return 100;
}
bool RefactorMethod::IsPriceCheap()
{
return (m_Fruit.GetPrice() > 1000);
}
4. 以查询取代临时变量 replace temp with query
- 名称:以查询取代临时变量 replace temp with query
- 概要:以一个临时变量保存某一个表达式的运算结果。将这个表达式提炼到一个独立函数中,将这个临时变量的所有引用点替换为对新函数的调用。此后,这个新函数就可以被其他函数使用。
- 动机:临时变量会导致函数变的更长。而新函数可以被其他函数复用
- 做法:
- 找出只被赋值一次的临时变量。如果临时变量被赋值超过一次,先使用split temporary variable 将它分割成多个变量。
- 将对该临时变量赋值的语句的等号右侧部分提炼到一个独立函数中。首先将函数声明为private,将来修改叶可以。
- 编译,测试
- 在该临时变量身上实施 inline temp
- 代码演示:
修改之前的代码:
double BasePrice = m_Quantity * m_ItemPrice;
double DiscountFactor = 0;
if (BasePrice > 100)
{
BasePrice = 0.95;
}
else
{
BasePrice = 0.98;
}
return DiscountFactor * BasePrice;
修改之后的代码:
double RefactorMethod::GetPrice()
{
return GetBasePrice() * GetPrice();
}
double RefactorMethod::GetBasePrice()
{
return m_Quantity * m_ItemPrice;
}
double RefactorMethod::DiscountFactor()
{
if (GetBasePrice() > 100)
{
return 0.95;
}
else
{
return 0.98;
}
}
5. 引入解释性变量 introduce explaining value
- 名称:引入解释性变量 introduce explaining value
- 概要:将该复杂表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途
- 动机:表达式有时候非常复杂且难以阅读,运用变量来解释对应条件子句的意义。另外,在较长算法中,可以运用临时变量来解释每一步运算的意义。
- 做法:
- 将待分解之复杂表达式的一部分运算结果赋值给一个临时变量
- 用临时变量替换表达式
- 编译,测试
- 代码演示:
修改之前的代码:
double RefactorMethod::Price()
{
return m_Quantity * m_ItemPrice -
std::max(0,m_Quantity - 500) * m_ItemPrice * 0.05 +
std::min(m_Quantity * m_ItemPrice * 0.1, 100.0);
}
修改之后的代码:
double RefactorMethod::Price()
{
double QuantityDiscount = std::max(0,m_Quantity - 500) * m_ItemPrice * 0.05;
double Shipping = std::min(m_Quantity * m_ItemPrice * 0.1, 100.0);
return GetBasePrice() - QuantityDiscount + Shipping;
}
继续重构:使用extract method将 QuantityDiscount 和 shipping变成函数。
6. 分解临时变量 split temporary variable
- 名称:分解临时变量 split temporary variable
- 概要:有个临时变量被赋值超过一次,它既不是循环变量,也不被用于收集计算结果。针对每次赋值,创造一个独立和对应的临时变量
- 动机:该变量承担了一个以上的责任,会令代码阅读者糊涂
- 做法:
- 在待分解临时变量的声明及其第一次被赋值处,修改其名称
- 以该变量的第二次赋值动作为界,修改此前对该临时变量的所有引用点,使用新的临时变量
- 在第二次赋值处,重新声明原先那个临时变量
- 编译,测试
- 逐次重复上述过程
- 代码演示:
修改之前的代码:
double temp = 2* (m_Height + m_Width);
qDebug() << "perimeter is " << temp;
temp = m_Height * m_Width;
qDebug() << "area is " << temp;
修改之后的代码:
double perimeter = 2* (m_Height + m_Width);
qDebug() << "perimeter is " << perimeter ;
double area = m_Height * m_Width;
qDebug() << "area is " << area ;
7. 移除对参数的赋值 remove assignments to parameters
- 名称:移除对参数的赋值 remove assignments to parameters
- 概要:代码对一个参数(函数参数)进行赋值,以一个临时变量取代该参数的位置。
- 动机:传值不会有太大影响。如果传入指针,而且不小心修改了指针地址,就会有大麻烦。尽量不要对函数传入参数值只参与计算,不进行赋值。
- 做法:
- 建立一个临时变量,把待处理的参数值赋予它
- 以“对参数的赋值”为界,将其后所有对此参数的应用点,全部替换为“对此临时变量的引用”
- 修改赋值语句,使其改为对新建之临时变量赋值
- 编译,测试
- 如果代码的语义是按引用传递的,请在调用端检查调用后是否还使用了这个参数。也要检查有多少个按引用传递的参数被赋值后又被使用。请尽量只以return方式返回一个值。如果需要返回的值不止一个,看看可否把需返回的大堆数据变成单一对象,或干脆为每个返回值设计对应的一个独立函数。
- 代码演示:
修改之前的代码:
int Discount(int InputVal, int Quantity, int YearToDate)
{
if (InputVal > 50)
{
InputVal -= 2;
}
return InputVal;
}
修改之后的代码:
int Discount(int InputVal, int Quantity, int YearToDate)
{
int Result = InputVal;
if (InputVal > 50)
{
Result -= 2;
}
return Result;
}
8. 以函数对象取代函数 replace method with method object
- 名称:以函数对象取代函数 replace method with method object
- 概要:有一个大型函数,其中对局部变量的使用,使你无法采用extract method。将这个函数放进一个单独对象中,局部变量就变成了对象内的字段,然后将这个大型函数分解为多个小型函数。
- 动机:有一个大型函数,其中对局部变量的使用,使你无法采用extract method。
- 做法:
- 新建一个类,根据待处理函数的用途,为类命名
- 针对源函数的每个临时变量和每个参数,在新类中建立一个对应的字段保存
- 在新类中建立一个构造函数,接收源对象及原函数的所有参数作为参数
- 在新类中建立一个compute() 函数
- 将源函数的代码赋值到compute()函数中,如果需要调用源对象的任何函数,请通过源对象字段调用。
- 编译
- 将旧函数的函数本体替换为这样一条语句:“创建上述新类的一个新对象,而后调用其中的compute()函数”
- 代码演示:
修改之前的代码:
///.h
#ifndef REFACTORMETHOD_H
#define REFACTORMETHOD_H
#include <QString>
#include <QVector>
class RefactorMethod
{
public:
int Gamma(int InputVal, int Quantity, int YearToDate);
int Delta();
};
#endif // REFACTORMETHOD_H
///.cpp
int RefactorMethod::Gamma(int InputVal, int Quantity, int YearToDate)
{
int ImportantValue1 = (InputVal * Quantity) + Delta();
int ImportantValue2 = (InputVal * YearToDate) + 100;
if ((YearToDate - ImportantValue1) > 100)
{
ImportantValue2 -= 20;
}
int ImportantValue3 =ImportantValue2 * 7;
return ImportantValue3 - 2 * ImportantValue1;
}
int RefactorMethod::Delta()
{
return 10;
}
修改1:创建新类,所有临时变量改为成员变量,构造函数用来传递原函数参数
修改2:在compute()函数中实现gamma的功能
修改3:修改旧函数,将它的工作委托给新的类对象
修改之后的代码
/.h
class GammaClass
{
public:
GammaClass(RefactorMethod *prm, int InputVal, int Quantity, int YearToDate);
int Compute();
private:
RefactorMethod *m_prm;
int m_InputVal;
int m_Quantity;
int m_YearToDate;
int m_ImportantValue1;
int m_ImportantValue2;
int m_ImportantValue3;
};
/.cpp
int RefactorMethod::Gamma(int InputVal, int Quantity, int YearToDate)
{
GammaClass *pgamma = new GammaClass(this, InputVal, Quantity, YearToDate);
return pgamma->Compute();
}
int RefactorMethod::Delta()
{
return 10;
}
GammaClass::GammaClass(RefactorMethod *prm, int InputVal, int Quantity, int YearToDate)
{
m_prm = prm;
m_InputVal = InputVal;
m_Quantity = Quantity;
m_YearToDate = YearToDate;
}
int GammaClass::Compute()
{
m_ImportantValue1 = (m_InputVal * m_Quantity) + m_prm->Delta();
m_ImportantValue2 = (m_InputVal * m_YearToDate) + 100;
if ((m_YearToDate - m_ImportantValue1) > 100)
{
m_ImportantValue2 -= 20;
}
int ImportantValue3 =m_ImportantValue2 * 7;
return ImportantValue3 - 2 * m_ImportantValue1;
}
针对Compute()还可以继续进行重构。
9. 替换算法 substitute algorithm
- 名称:替换算法 substitute algorithm
- 概要:将函数本体替换为另一个算法
- 动机:想要把某个算法替换为另一个更清晰的算法
- 做法:
- 准备好另一个(替换用)的算法,让它通过编译
- 针对现有测试,执行上述新算法。如果结果与原本结果相同,重构结束。
- 代码演示:
修改之前的代码:
QString FoundPerson(QStringList PersonList)
{
QString result;
//算法1
return result
}
修改之后的代码
QString FoundPerson(QStringList PersonList)
{
QString result;
//算法2
return result
}