《重构:改善既有代码的设计》代码实践 之 第六章

版权声明:请注明转发出处 https://blog.csdn.net/mafucun1988/article/details/89349751

重新组织函数的使用方法

  • 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
}

猜你喜欢

转载自blog.csdn.net/mafucun1988/article/details/89349751
今日推荐