《重构》手法之 重新组织函数

重新组织函数

extract method(提炼方法)

当一个函数过长,或者需要注释才能够让人理解用途的代码,需要使用extract method;
extract method 需要做到细化函数粒度、提炼函数名;

TODO:

  1. 细化原先函数粒度。创造一个新函数、提炼函数名;
  2. 将提炼出的代码从源函数复制到新函数;
  3. 处理局部变量、临时变量(作用域限于源函数)(Replace temp with query / Replace method with method object);
  4. 检查是否有“仅用于被提炼函数代码段”的临时变量,如果有,在目标函数中声明为临时变量。
  5. 检查被提炼代码段是否可以被处理为一个查询,并将结果复制给源函数相关的临时变量;或者存在多个被改变的临时变量,首先使用split temporary variable处理临时变量。
  6. 将被提炼代码段中需要使用的变量, 当作参数传递给新函数;
  7. 在目标函数中将对被提炼代码段的调用替换成对目标函数的调用;

优点:降低函数的粒度,提高函数复用机会;使高层函数读起来像一系列注释;

inline method(内联函数)

当一个函数的本体和它的名称一样清晰易懂时,将这个函数本体添加到调用它的目标函数中, 同时删除这个函数。

另一种使用inline method的情况是:当你受伤有一些函数的组织不合理,此时把通过inline method把这些函数内联,然后再通过extract method提炼出合理的新函数。

一般通过提炼函数的手法重构之后发现如果有这样的情况,应该撤销提炼:“提炼的函数名称与其本体的逻辑皆清晰易懂”

TODO:

  1. 检查函数,确定它不具有多态性;
  2. 找出这个函数的所有被调用点;
  3. 将这个函数的所有被调用点都替换成函数本体;

inline temp(内联临时变量)

内联临时变量,举个例子吧:

// old
int c = 2, d = 3;
int a = c *d;
return a;

// new
int c = 2, d = 3;
return c * d;

TODO:

  1. 检查给临时变量赋值的语句,确保等号右边的表达式没有副作用;
  2. 如果这个临时变量没有被声明为final,声明为final(此步骤是为了验证这个变量是否只被赋值一次)
  3. 找出这个临时变量,(return expr, delete the temp variable)

inline temp的概念很简单,我已经有了这个编程理念

replace temp with query(以查询替代临时变量)

源函数的某一个临时变量存储某一代码段的逻辑执行结果,则此时把该代码段使用extract method 提炼到一个新的函数,然后使用相应的临时变量接受该新函数的返回值;

扫描二维码关注公众号,回复: 12350276 查看本文章

TODO:

  1. 找到一个只被赋值一次的临时变量;
  2. 将该临时变量的计算逻辑提炼到一个新的函数中;
  3. 使用inline temp理念收尾此次重构;

《重构》6.4节,电子书147页,纸质书123页,有一个简单却很牛逼的示例;

introduce explaining variable(引入解释性变量)

将复杂的表达式的结果使用命名有明确意义的临时变量存储;比如在较长的算法中使用临这样的临时变量,有助于很好地的诠释算法的逻辑意义。

这种重构的技巧与extract method类似。当有很多的局部变量的时候,考虑使用introduce explaining variable;当临时变量较少,extract method看起来更不错;

TODO:

  1. 声明一个final变量,将复杂表达式的结果赋值给这个变量;
  2. 替换所有用到这个临时变量代表的复杂表达式的地方;

split temporary variable(分解临时变量)

“循环收集结果变量”,“结果收集变量”,“某些把一些复杂的中间运算结果存储到一个临时变量中”,这些都是典型的导致一个变量被多次赋值的情况。应该遵循每个变量承担一个单独的责任的原则,如果一个临时变量在一个函数中承担了多个责任,那么这个责任就要重新分担给多个临时变量;

TODO:

  1. 在待分解临时变量第一次赋值处,修改其名称;
  2. 替换所有使用此临时变量的地方,除了其被再次赋值的地方;
  3. 在待分解临时变量第二次赋值处,声明新的final变量;
  4. 在待分解的临时变量的第二次赋值处,将其赋值给新的临时变量;

remove assignments to parameters(移除对参数的赋值)

你有没有发现,我们经常会对一个带有参数的函数传入的变量参数进行修改?

int discount (int inputVal, int quantity, int yearToDate){
    
    
    if(inputVal > 50) inputVal -= 2;
}

在上面这段代码中,对函数传入的参数赋值了。
而在下面这段代码中,改变了这种情况。
其实这个重构手法似乎有点多余,但是对于一个作为引用传入的参数来说,要注意到这一点,尤其是在一些弱类型语言中,很有可能会修改原先变量的值,此时还要注意在使用这个变量的时候,仅仅是使用 =赋值不一定会正确。

int discount (int inputVal, int quantity, int yearToDate){
    
    
    int result = inputVal;
    if (inputVal > 50) result -= 2;
}

TODO:

  1. 建立临时变量;
  2. 把传入的参数赋值给临时变量(对于一一些弱类型语言来说,比如高python的list,可能需要使用列表生成的写法来为一个临时变量赋值)

replace method with method object(以函数对象取代函数)

对于一个巨大的函数,无法使用extract method的手法进行重构,而且之中还穿插着很多的临时变量,此时可以考虑使用当前讲述的重构手法进行重构。

把函数替换成函数对象,也就是以类的方式去重构这个函数,这个函数中使用到的临时变量就成为了此类的属性,此时再使用extract method的重构手法进行重构,将一个大函数分解成多个小功能的函数。

TODO:

  1. 建立一个新类,为这个类起一个可读性友好的类名称。
  2. 在新类中创建一个final字段,用以保存原先大型函数所在的对象,我们将这个对象称之为源对象。同时,针对于原来函数的每个临时变量和参数,在新类中建立一个与之对应的字段保存。
  3. 在新类中创建一个构造函数,接受源对象以及原函数的所有参数作为参数。
  4. 在新类中建立一个compute()函数
  5. 将原函数的代码赋值到compute()函数中。如果需要调用源对象的任何函数,请通过源对象字段调用。
  6. 将旧函数的本体替换成这样一条语句:“创建上诉新类的一个新对象,而后调用其中的compute()函数”

实例:电子书160页,实体书136页,有一个很牛逼的实例,一看就懂;范例在文章后面有补充。

substitute algorithm(替换算法)

将某一个算法替换成一个更清晰的算法。

TODO:

  1. 准备好一个替换算法
  2. 针对现有测试,执行上诉算法,如果与原先的算法结果一直,则重构结束。

replace method with method object范例

class Account
{
    
    
      int 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;
           //and so on...
           return importantValue3 - 2 * importantValue1;
       }
       public int Delta()
       {
    
    
           return 100;
       }
}

为了把这个函数变成函数对象,首先声明一个新类。在新类中,提供一个字段用于保存原对象,同时也对函数的每个参数和每个临时变量,提供字段用于保存。

class Gamma
{
    
    

        private readonly Account _account;

        private readonly int _inputVal;

        private readonly int _quantity;

        private readonly int _yearToDate;

        private int _importantValue1;

        private int _importantValue2;

        private int _importantValue3;
}

接下来,加入一个构造函数:

public Gamma(Account account, int inputVal, int quantity, int yearToDate)
{
    
    
       _account = account;
       _inputVal = inputVal;
       _quantity = quantity;
       _yearToDate = yearToDate;
}

接下来,将原本的函数搬到Compute()中。

public int Compute()
{
    
    
    _importantValue1 = _inputVal * _quantity + _account.Delta();
    _importantValue2 = _inputVal * _yearToDate + 100;
    if (_yearToDate - _importantValue1 > 100)
    {
    
    
       _importantValue2 -= 20;
    }
    _importantValue3 = _importantValue2 * 7;
    //and so on...
    return _importantValue3 - 2 * _importantValue1;
}

完整的Gamma函数如下:

class Gamma
{
    
    

   private readonly Account _account;

   private readonly int _inputVal;

   private readonly int _quantity;

   private readonly int _yearToDate;

   private int _importantValue1;

   private int _importantValue2;

   private int _importantValue3;
public Gamma(Account account, int inputVal, int quantity, int yearToDate)
   {
    
    
       _account = account;
       _inputVal = inputVal;
       _quantity = quantity;
       _yearToDate = yearToDate;
   }
   public int Compute()
   {
    
    
       _importantValue1 = _inputVal * _quantity + _account.Delta();
       _importantValue2 = _inputVal * _yearToDate + 100;
       if (_yearToDate - _importantValue1 > 100)
       {
    
    
         _importantValue2 -= 20;
       }
       _importantValue3 = _importantValue2 * 7;
       //and so on...
       return _importantValue3 - 2 * _importantValue1;
   }
}

最后,修改旧函数,让它的工作委托给刚完成的这个函数对象。

int Gamma(int inputVal, int quantity, int yearToDate)
{
    
    
    return new Gamma(this, inputVal, quantity, yearToDate).Compute();
}

这就是本项重构的基本原则。它的好处是:现在我们可以轻松地对Compute()函数采取 Extract Method,不必担心参数传递的问题。

比如说我们对Compute进行如下重构:

public int Compute()
{
    
    
    _importantValue1 = _inputVal * _quantity + _account.Delta();
    _importantValue2 = _inputVal * _yearToDate + 100;
    GetImportantThing();
    _importantValue3 = _importantValue2 * 7;
    //and so on...
    return _importantValue3 - 2 * _importantValue1;
}

void GetImportantThing()
{
    
    
    if (_yearToDate - _importantValue1 > 100)
    {
    
    
        _importantValue2 -= 20;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_39378657/article/details/110391397
今日推荐