zoukankan      html  css  js  c++  java
  • 重构改善既有代码设计--重构手法01:Extract Method (提炼函数)

    背景:

    你有一段代码可以被组织在一起并独立出来。将这段代码放进一个独立函数,并让函数名称解释该函数的用途。      

    void PrintOwing(double amount)

            {

                PrintBanner();

                //print details

                Console.WriteLine("name:"+_name);

                Console.WriteLine("amount:"+_amount);

            }

                               

          

    void PrintOwing(double amount)

            {

                PrintBanner();

                //print details

                PrintDetails();

            }

            private void PrintDetails()

            {

                Console.WriteLine("name:" + _name);

                Console.WriteLine("amount:" + _amount);

            }

    动机:

    Extract Method (提炼函数)是最常用的重构手法之一。当看见一个过长的函数或者一段需要注释才能让人理解用途的代码,就应该将这段代码放进一个独立函数中。

           简短而命名良好的函数的好处:首先,如果每个函数的粒度都很小,那么函数被复用的机会就更大;其次,这会使高层函数读起来就想一系列注释;再次,如果函数都是细粒度,那么函数的覆写也会更容易些。

           一个函数多长才算合适?长度不是问题,关键在于函数名称和函数本体之间的语义距离。如果提炼可以强化代码的清晰度,那就去做,就算函数名称必提炼出来的代码还长也无所谓。

    做法:

    1、创造一个新函数,根据这个函数的意图对它命名(以它“做什么“命名,而不是以它“怎样做”命名)。

    即使你想要提炼的代码非常简单,例如只是一条消息或一个函数调用,只要新函数的名称能够以更好方式昭示代码意图,你也应该提炼它。但如果你想不出一个更有意义的名称,就别动。

    2、将提炼出的代码从源函数复制到新建的明白函数中。

    3、仔细检查提炼出的代码,看看其中是否引用了“作用域限于源函数”的变量(包括局部变量和源函数参数)。

    4、检查是否有“仅用于被提炼代码段”的临时变量。如果有,在目标函数中将它们声明为临时变量。

    5、检查被提炼代码段,看看是否有任何局部变量的值被它改变。如果一个临时变量值被修改了,看看是否可以将被提炼代码处理为一个查询,并将结果赋值给修改变量。如果很难这样做,或如果被修改的变量不止一个,你就不能仅仅将这段代码原封不动提炼出来。你可能需要先使用 Split Temporary Variable (分解临时变量),然后再尝试提炼。也可以使用 Replace Temp with Query (以查询取代临时变量)将临时变量消灭掉。

    6、将被提炼代码段中需要读取的局部变量,当做参数传给目标函数。

    7、处理完所有局部变量后,进行编译。

    8、在源函数中,将被提炼代码段替换给对目标函数的调用。

         如果你将如何临时变量移到目标函数中,请检查它们原本的声明式是否在被提炼代码段的外围。如果是,现在可以删除这些声明式了。

    9、编译,测试。

    代码演示:

    实例代码如下:

     1 private string myName;
     2 public void printPeople(int Age)
     3 {
     4     printFamily();
     5     //无数代码//
     6 
     7     //打印个人信息
     8     Console.WriteLine("Name:" + myName);
     9         Console.WriteLine("Age:" + Age);
    10 }


    重构后的代码如下:

     1 private string myName;
     2 public void printPeople(int Age)
     3 {
     4     printFamily();
     5     //无数代码//
     6     printMyInfo(Age);
     7 }
     8 
     9 void printMyInfo(int Age)
    10 {
    11     Console.WriteLine("Name:" + myName);
    12         Console.WriteLine("Age:" + Age);
    13 }


    为什么要这样重构?当一个函数很大的时候,第一对代码的修改起来非常的不方便.
    第二,会对你读代码有障碍,试想一下当你看到一个很多行代码的方法,你还有心情看下去吗?
    第三,方法与方法之间的复用性会非常的好,方法的重写也会更容易些.

    那么我们应该怎么做呢?
    看第一个例子:
    无局部变量的方法提炼.

     1 void printOwing()
     2 {
     3     ArrayList al = myOrders.GetOrderList();
     4     double outstanding = 0.0;
     5 
     6     //打印头部信息
     7     Console.WriteLine("*****************");
     8     Console.WriteLine("**Customer Owes**");
     9     Console.WriteLine("*****************");
    10 
    11     //计算
    12     foreach(Object o in al)
    13     {
    14         Order each = (Order)o;
    15         outstanding += each.Amount;
    16     }
    17 
    18     //打印具体信息
    19     Console.WriteLine("Name:" + myName);
    20     Console.WriteLine("Age:" + age);
    21 }


    好了我们开始先提最简单的部分.提出后的代码如下:

     1 void printOwing()
     2 {
     3     ArrayList al = myOrders.GetOrderList();
     4     double outstanding = 0.0;
     5 
     6     printBanner();
     7 
     8     //计算
     9     foreach(Object o in al)
    10     {
    11         Order each = (Order)o;
    12         outstanding += each.Amount;
    13     }
    14 
    15     //打印具体信息
    16     Console.WriteLine("Name:" + myName);
    17     Console.WriteLine("Age:" + age);
    18 }
    19 
    20 void printBanner()
    21 {
    22     //打印头部信息
    23     Console.WriteLine("*****************");
    24     Console.WriteLine("**Customer Owes**");
    25     Console.WriteLine("*****************");
    26 }


    最简单的提炼方法结束了.
    下来我们看有局部变量的方法提炼.就拿上面的的代码开刀.

     1 void printOwing()
     2 {
     3     ArrayList al = myOrders.GetOrderList();
     4     double outstanding = 0.0;
     5 
     6     printBanner();
     7 
     8     //计算
     9     foreach(Object o in al)
    10     {
    11         Order each = (Order)o;
    12         outstanding += each.Amount;
    13     }
    14 
    15     printInfo(outstanding);
    16 }
    17 
    18 void printBanner()
    19 {
    20     //打印头部信息
    21     Console.WriteLine("*****************");
    22     Console.WriteLine("**Customer Owes**");
    23     Console.WriteLine("*****************");
    24 }
    25 
    26 void printInfo(double OutStanding)
    27 {
    28     //打印具体信息
    29     Console.WriteLine("Name:" + myName);
    30     Console.WriteLine("Age:" + age);   
    31 }


    我们再来看下对局部变量再赋值方法的提炼.继续拿上面代码开刀.

     1 void printOwing()
     2 {
     3     double outstanding = GetOutStanding();
     4 
     5     printBanner();
     6 
     7     printInfo(outstanding);
     8 }
     9 
    10 void printBanner()
    11 {
    12     //打印头部信息
    13     Console.WriteLine("*****************");
    14     Console.WriteLine("**Customer Owes**");
    15     Console.WriteLine("*****************");
    16 }
    17 
    18 void printInfo(double OutStanding)
    19 {
    20     //打印具体信息
    21     Console.WriteLine("Name:" + myName);
    22     Console.WriteLine("Age:" + age);   
    23 }
    24 
    25 double GetOutStanding()
    26 {
    27     ArrayList al = myOrders.GetOrderList();
    28     double outstanding = 0.0;
    29     //计算
    30     foreach(Object o in al)
    31     {
    32         Order each = (Order)o;
    33         outstanding += each.Amount;
    34     }
    35     return outstanding
    36 }


    Extract Method方法讲解玩了.有人会问为什么要这样写?这样写的好处我没有看到啊.
    那么现在有个这样的需求,我要设置outstanding的初始值,那么我们只要修改GetOutStanding方法,代码

    如下:

     1 double GetOutStanding(double previousAmount)
     2 {
     3     ArrayList al = myOrders.GetOrderList();
     4     double outstanding = previousAmount;
     5     //计算
     6     foreach(Object o in al)
     7     {
     8         Order each = (Order)o;
     9         outstanding += each.Amount;
    10     }
    11     return outstanding
    12 }


    主要方法修改如下:

    1 void printOwing()
    2 {
    3     double outstanding = GetOutStanding(500.5);

    5     printBanner();

    7     printInfo(outstanding);
    8 }


    如果需求继续增加,我们修改起来是不是方便了许多?

    读后感:

    1.如果说没有任何局部变量,那么这个函数提炼就非常容易提炼.

    2.如果说提炼的时候有局部变量,即用到了提炼函数之外的局部变量,那么如果仅仅是内部函数使用的,直接放到内部函数中;第二种,如果提炼的函数内部没有对此变量赋值的情况,仅仅是读取使用,那么直接从外面作为参数传递进来。

    3.如果提炼的函数,不仅仅有局部变量,并且还要对其赋值,那么同样要看,这个局部变量是不是只是内部使用,如果只是内部使用,直接放进来,如果不是,那就说外面还要用到,那么需要经过提炼函数运算后,将值返回去.

  • 相关阅读:
    POJ 2923 Relocation (状态压缩,01背包)
    HDU 2126 Buy the souvenirs (01背包,输出方案数)
    hdu 2639 Bone Collector II (01背包,求第k优解)
    UVA 562 Dividing coins (01背包)
    POJ 3437 Tree Grafting
    Light OJ 1095 Arrange the Numbers(容斥)
    BZOJ 1560 火星藏宝图(DP)
    POJ 3675 Telescope
    POJ 2986 A Triangle and a Circle
    BZOJ 1040 骑士
  • 原文地址:https://www.cnblogs.com/pony1223/p/7512741.html
Copyright © 2011-2022 走看看