第2章 重新组织函数:引入解释性变量分解临时变量和移除对参数的赋值

Posted 浅墨浓香

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第2章 重新组织函数:引入解释性变量分解临时变量和移除对参数的赋值相关的知识,希望对你有一定的参考价值。

5. 引入解释性变量(Introduct Explaining Variable)

//引入解释性变量
//重构前
if((platform.toUpperCase().indexOf("MAC") > -1) &&
   (browser.toUpperCase().indexOf("IE") > -1) &&
    wasInitialized() && resize > 0)
{
    //do something
}

//重构后
const bool isMacOs     = platform.toUpperCase().indexOf("MAC") > -1;
const bool isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
const bool wasResized = resize > 0; 

if (isMacOs && isIEBrowser && wasInitialized() && wasResized())
{
    //do something
}

5.1 动机

(1)将复杂表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式的用途

(2)引入临时变量可以帮助将表达式分解为比较容易管理的形式。如条件逻辑中,可以将每个条件子句提炼出来,以一个良好命名的临时变量来解释对应条件子句的意义。

5.2 做法

(1)声明一个const临时变量,将待分解的复杂表达式中的一部分动作的运算结果赋值给它。

(2)将表达式中的“运算结果”这一部分,替换为上述临时变量

(3)编译,测试。

(4)重复上述过程,处理表达式的其他部分。

5.3 范例

//重构前(其中的_quantity和_itemPrice为类的成员变量)
double price()
{
    //价格 = 底价(basePrice) - 批发折扣(quantity discount) + 运费(shipping)
    //底价 = 数量(quantity) * 单价(itemPrice) 
    return  _quantity * _itemPrice -  //底价
            Math.max(0, _quantity - 500) * _itemPrice * 0.05 +
            Math.min(_quantity * _itemPrice * 0.1, 100.0);            
}

//重构中(引入解释性变量)
double price()
{
    const double basePrice = _quantity * _itemPrice;
    const double quantityDiscount = Math.max(0, _quantity - 500) * _itemPrice * 0.05;
    const double shipping = Math.min(basePrice * 0.1, 100.0);
    
    return basePrice - quantityDiscount + shipping;
}

//运用ExtractMethod方法对上述范例进行重构(去除上述的解释性变量,即临时变量)
double price()
{
    return basePrice() - quantityDiscount() + shipping();
}

//底价
double basePrice()
{
    return _quantity * _itemPrice;
}
//批发折扣
double quantityDiscount()
{
    return Math.max(0, _quantity - 500) * _itemPrice * 0.05;
}

//运费
double shipping()
{
    return Math.min(basePrice * 0.1, 100.0);
}

5.4 思考

(1)当要处理一个拥有大量局部变量的算法时,使用ExtractMethod比较困难。这种情况下可以使用Introduce Explaining Variable来理解代码。

(2)搞清代码逻辑后,总可以运用Replace Temp with Query把中间引入的那些解释性临时变量去掉。如果使用Replace Method with Method Object,那么中间引入的那些解释性临时变量也可以作为成员变量,即体现其用途所在。

6. 分解临时变量

//分解临时变量
//重构前
double temp = 2 * (_height + _width);  //周长
cout << temp << endl;

temp = _height * _width; //面积
cout << temp << endl;

//重构后
const double perimeter = 2 * (_height + _width);  //周长
cout << perimeter << endl;

const double area = _height * _width; //面积
cout << area << endl;

6.1 动机

代码中如果某个临时变量被赋值超过一次(循环变量除外),就意味着在该函数中它承担了一个以上的职责。这时可以分解个多个临时变量,每个变量只承担一个责任

6.2 做法

(1)在待分解临时变量的声明及其第1次被赋值处,修改其名称

(2)将上述新的临时变量声明为const变量

(3)以该临时变量的第2次赋值动作为界,修改此前对该临时变量的所有引用,让它们引用新的临时变量。

(4)在第2次赋值处,重新声明原先那个临时变量。

(5)编译测试,重复上述过程。每个都在声明处对该临时变量改名,并修改下次赋值之前的引用点。

6.3 范例

//分解临时变量
//场景:根据牛顿第二定律计算物理从静止开始运动,在
//指定时间内的运动距离(分两个阶段)
//第1阶段:在一个力的作用下。第2个阶段在两个力的共同作用下)
double getDistanceTravelled(int time)
{
    double result = 0.0;
    double acc = _primaryForce / _mass; //F = ma;注意acc被赋值两次。
    int primaryTime = Math.min(time, _delay);
    result = 0.5 * acc * primaryTime * primaryTime;
    
    int secondaryTime = time - _delay;
    if(secondaryTime > 0)
    {
        double primaryVel = acc * _delay; //初始速
        acc = (_primaryForce + _secondaryForce) / _mass;
        
        result += primaryVel * secondaryTime +
                  0.5 * acc * secondaryTime * secondaryTime;
    }
    
    return result;
}

//重构中(对第1次赋值重构)
double getDistanceTravelled(int time)
{
    double result = 0.0;
    const double primaryAcc = _primaryForce / _mass; //F = ma;注意acc被赋值两次。
    int primaryTime = Math.min(time, _delay);
    result = 0.5 * primaryAcc * primaryTime * primaryTime;
    
    int secondaryTime = time - _delay;
    if(secondaryTime > 0)
    {
        double primaryVel = primaryAcc * _delay; //初始速
        double acc = (_primaryForce + _secondaryForce) / _mass;
        
        result += primaryVel * secondaryTime +
                  0.5 * acc * secondaryTime * secondaryTime;
    }
    
    return result;
}
//重构后
double getDistanceTravelled(int time)
{
    double result = 0.0;
    const double primaryAcc = _primaryForce / _mass; //F = ma;注意acc被赋值两次。
    int primaryTime = Math.min(time, _delay);
    result = 0.5 * primaryAcc * primaryTime * primaryTime;
    
    int secondaryTime = time - _delay;
    if(secondaryTime > 0)
    {
        double primaryVel = primaryAcc * _delay; //初始速
        const double secondaryAcc = (_primaryForce + _secondaryForce) / _mass;
        
        result += primaryVel * secondaryTime +
                  0.5 * secondaryAcc * secondaryTime * secondaryTime;
    }
    
    return result;
}

7. 移除对参数的赋值(Remove Assignments to Parameters)

7.1 动机

(1)注意“对参数赋值”的意思如果把一个名为foo的对象指针作为参数传给某个函数,那么“对参数赋值”意味改变foo,使它指向了另一个对象。如果是在“被传入对象”上调用某个函数而改变对象内部的状态,这不叫“对参数赋值”。如

void aMethod(Object* foo)
{
    foo->modifyInSomeWay(); //没问题
    foo = anotherObject;    //对参数赋值!
}

(2)注意传值和传地址的区别。按值传递的情况下,对参数的任何修改,不会对调用端造成影响。

(3)对面那些使用“出参数”的语言,不必遵循这条规则。

(4)为了防止对参数赋值,也可以将其声明为const类型,如上例void aMethdo(Object* const foo);

7.2 做法

(1)建立一个临时变量,把待处理的参数值赋予它。

(2)以“对参数的赋值”为界,将所有其后对此参数的引用点,全部替换为“对此临时变量的引用”

(3)修改赋值语句,使其改为对新建之临时变量赋值。

7.3 范例

//对参数赋值
//重构前(注意第1个参数为引用类型)
int discount (int& inputVal, int quantity, int yearToDate)
{
    if(inputVal > 50) inputVal = -2;
    if(quantity > 100) inputVal = -1;
    if(yearToDate > 10000) inputVal = -4;
    return inputVal;
}

//重构后
int discount (int& inputVal, int quantity, int yearToDate)
{
    int result = inputVal;
    if(inputVal > 50) result = -2;
    if(quantity > 100) result = -1;
    if(yearToDate > 10000) result = -4;
    return result;
}

以上是关于第2章 重新组织函数:引入解释性变量分解临时变量和移除对参数的赋值的主要内容,如果未能解决你的问题,请参考以下文章

第2章 重新组织函数:内联函数和内联临时变量

重构 重构手法

第2章 重新组织函数:函数对象替换算法

重构列表

代码重构之引入解释性变量

第1章 重构,第一个案例:分解并重组statement函数