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

  • 相关阅读:
    2019/5/15 写题总结
    CodeForces 804C Ice cream coloring
    CodeForces 367 C Sereja and the Arrangement of Numbers 欧拉回路
    CodeForces 464 B Restore Cube
    CodeForces 402 E Strictly Positive Matrix
    CodeForces 628 D Magic Numbers 数位DP
    CodeForces 340E Iahub and Permutations 错排dp
    CodeForces 780 E Underground Lab
    BZOJ 1010 [HNOI2008]玩具装箱toy 斜率优化dp
    CodeForces 715B Complete The Graph 特殊的dijkstra
  • 原文地址:https://www.cnblogs.com/pony1223/p/7512741.html
Copyright © 2011-2022 走看看