ivaneeo's blog

自由的力量,自由的生活。

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  669 Posts :: 0 Stories :: 64 Comments :: 0 Trackbacks

#

作法(Mechanics)

首先是简单情况:

    • 找出只被赋值一次的临时变量==>如果某个临时变量被赋值超过一次,考虑使用Split Temporay Variable(128)将它分割成多个变量.
    • 将该临时变量声明为final.
    • 编译.==>这可确保该临时变量的确只被赋值一次.
    • 将[对临时变量赋值]之语句的等号右侧部分提炼到一个独立函数中.
        • ==>首先将函数声明为private.日后你可能会发现有更多class需要使用它,彼时你可轻易放松对它的保护.
        • ==>确保提炼出来的函数无任何连带影响(副作用),也就是说该函数并不修改任何对象内容.如果它有连带影响,就对它进行Separate Query from Modifier(279).
    • 编译,测试.
    • 在该临时变量身上实施Inline Temp(119).

我们常常使用临时变量保存循环中的累加信息.在这种情况下,整个循环都可以被提炼为一个独立函数,这也使原本的函数可以少掉几行扰人的循环代码.有时候,你可能会用单一循环累加好几个值.这种情况下你应该针对每个累加值重复一遍循环,这样就可以将所有临时变量都替换为查询式(query).当然,循环应该很简单,复制这些代码时才不会带来危险.

运用此手法,呢可能会担心性能问题.和其他性能问题一样,我们现在不管它,因为它十有八九根本不会造成任何影响.如果性能真的出了问题,你也可以在优化时期解决它.如果代码组织良好,那么你往往能够发现更有效的优化法案;如果你没有进行重构,好的优化法案就可能与你失之交臂.如果性能实在太糟糕,要把临时变量放回去也是很容易的.

posted @ 2005-08-25 15:04 ivaneeo 阅读(219) | 评论 (0)编辑 收藏

动机(Motivation)
临时变量的问题在于:它们是暂时的,而且只能在所属函数内使用.由于临时变量只有在所属函数内才可见,所以它们会驱使你写出更长的函数,因为只有这样你才能访问到想要访问的临时变量.如果把临时变量替换为一个查询式(query method),那么同一个class中的所有函数都将可以获得这份信息.这将带给你极大帮助,使你能够为这个class编写更清晰的代码.

Replace Temp with Query(120)往往是你运用Extract Method(110)之前必不可少的一个步骤.局部变量会使代码难以被提炼,所以你应该尽可能把它们替换为查询式.

这个重构手法较为直率的情况就是:临时变量只被赋值一次,或者赋值给临时变量的表达式不受其他条件影响.其他情况比较棘手,但也有可能发生.你可能需要先运用Split Temporary Variable(128)或Separate Query from Modifier(279)使情况变得简单一些,然后再替换临时变量.如果你想替换的临时变量是用来收集结果的(例如循环中的累加值),你就需要将某些程序逻辑(例如循环)拷贝到查询式(query method)去.
posted @ 2005-08-25 14:43 ivaneeo 阅读(258) | 评论 (0)编辑 收藏

你的程序以一个临时变量(temp)保存某一表达式的运算结果.

将这个表达式提炼到一个独立函数中.将这个临时变量的所有[被引用点]替换为[对新函数的调用].新函数可被其他函数使用.

double basePrice = _quantity * _itemPrice;
if(basePrice > 1000)
   return basePrice * 0.95;
else
   return basePrice * 0.98;
                                    |   |
                                    |   |
                                   \    /
if(basePrice() > 1000)
   return basePrice() * 0.95;
else
   return base() * 0.98;
...
double basePrice() {
   return _quantity * _itemPrice;
}
posted @ 2005-08-25 14:22 ivaneeo 阅读(164) | 评论 (0)编辑 收藏

    • 作法(Mechanics)
    • 如果这个临时变量并未被声明为final,那就将它声明为final,然后编译.==>这可以检查该临时变量是否真的只被赋值一次.
    • 找到该临时变量的所有引用点,将它们替换为[为临时变量赋值]之语句中的等号右侧表达式.
    • 每次修改后,编译并测试.
    • 修改完所有引用电之后,删除该临时变量的声明式和赋值语句.
    • 编译,测试.
posted @ 2005-08-25 14:15 ivaneeo 阅读(144) | 评论 (0)编辑 收藏

动机(Motivation)
Inline Temp(119)多半是作为Replace Temp with Query(120)的一部分来使用,所以真正的动机出现在后者那儿.唯一单独使用Inline Temp(119)的情况是:你发现某个临时变量被赋予某个函数调用的返回值.一般来说,这样的临时变量不会有任何危害,你可以放心地把它留在那儿.但如果这个临时变量妨碍了其他的重构手法--例如Extract Method(110),你就应该就它inline化.

posted @ 2005-08-25 13:53 ivaneeo 阅读(176) | 评论 (0)编辑 收藏

你有一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其他重构方法.

将所有对该变量的引用动作,替换为对它赋值得那个表达式自身.

double basePrice = anOrder.basePrice();
return (basePrice > 1000);
                              |   |
                              |   |
                             \    /
return (anOrder.basePrice() > 1000);
posted @ 2005-08-25 13:52 ivaneeo 阅读(216) | 评论 (0)编辑 收藏

    • 作法(Mechanics)
    • 检查函数,确定它不具多态性(is not polymorphic).==>如果subclass继承了这个函数,就不要将此函数inline化,因为subclass无法覆写(override)一个根本不存在的函数.
    • 找出这个函数的所有被调用点.
    • 将这个函数的所有被调用点都替换为函数本体(代码).
    • 编译,测试.
    • 删除该函数的定义.

被我这样一写,Inline Method(117)似乎很简单.但情况往往并非如此.对于递归调用,多返回点,inline至另一个对象中而该对象并无提供访问函数(accessors)......,每一种情况我都可以写上好几页.我之所以不写这些特殊情况,原因很简单:如果你遇到了这样的复杂情况,那么就不应该使用这个重构手法.

posted @ 2005-08-24 17:28 ivaneeo 阅读(186) | 评论 (0)编辑 收藏

动机(Motivation)
有时候你会遇到某些函数,其内部代码和函数名称同样清晰易读.

另一种需要使用Inline Method(117)的情况是:你手上有一群组织不甚合理的函数.你可以将它们都inline到一个大型函数中,再从中提炼出组织合理的小型函数.Kent Beck发现,实施Replace Method with Method Object(135)之前先这么做,往往可以获得不错的效果.你可以把你所要的函数(有着你要的行为)的所有调用对象的函数内容都inline到method object(函数对象)中.比起既要移动一个函数,又要移动它所调用的其他所有函数,[将大型函数作为单一整体来移动]会比较简单.

如果别人使用了太多间接层,使得系统中的所有函数都似乎只是对另一个函数的简单委托(delegation),造成我在这些委托动作之间晕头转向,那么我通常都会使用Inline Method(117).当然,间接层有其价值,但不是所有间接层都有价值.试着使用inlining,我可以找出那些有用的间接层,同时将那些无用的间接层去除.
posted @ 2005-08-24 17:12 ivaneeo 阅读(182) | 评论 (0)编辑 收藏

一个函数,其本体(method body)应该与其名称(method name)同样清楚易懂.

在函数调用点插入函数本体,然后移除该函数.

int getRating() {
   return (moreThanFiveLateDeliveries()) ? 2 : 1;
}
boolean moreThanFiveLateDeliveries() {
   return _numberOfLateDeliveries > 5;
}
                                     |   |
                                     |   |
                                    \    /

int getRating() {
   return (_numberOfLateDeliveries > 5) ? 2 : 1;
}
posted @ 2005-08-24 16:55 ivaneeo 阅读(177) | 评论 (0)编辑 收藏

范例(Examples):局部变量再赋值(Reassigning)
如果被提炼码对局部变量赋值,问题就变得复杂了.这里我们只讨论临时变量的问题.如果你发现源函数的参数被赋值,应该马上使用Remove Assignments to Parameters(131).

被赋值的临时变量也分两种情况.较简单的情况是:这个变量只在被提炼码区段中使用.果真如此,你可以将这个临时变量的声明式移到被提炼码中,然后一起提炼出去.另一种情况是:被提炼码之外的代码也使用了这个变量.这又分为两种情况:如果这个变量在被提炼码之后未再被使用,你只需直接在目标函数中修改它就可以了;如果被提炼码之后的代码还使用了这个变量,你就需要让目标函数返回该变量改变后的指.我以下列代码说明这几种不同情况:
void printOwing() {
    Enumeration e = _orders.elements();
    double outstanding = 0.0;

   printBanner();

    // calculate outstanding
    while(e.hasMoreElements()) {
       Order each = (Order) e.nextElement();
       outstanding += each.getAmount();
    }

   printDetails(outstanding);
}

现在我把[计算]代码提炼出来:

void printOwing() {
   printBanner();
   double outstanding = getOutstanding();
   printDetails(outstanding);
}

double getOutstanding() {
       Enumeration e = _orders.elements();
       double outstanding = 0.0;

       while(e.hasMoreElements()) {
          Order each = (Order) e.nextElement();
          outstanding += each.getAmount();
       }
      return outstanding;
}

Enumeration变量e只在被提炼码中用到,所以我可以将它整个搬到新函数中.double变量outstanding在被提炼码内外都被用到,所以我必须让提炼出来的新函数返回它.编译测试完成后,我就把传值改名,遵循我的一贯命名原则:

double getOutstanding() {
       Enumeration e = _orders.elements();
       double result = 0.0;

       while(e.hasMoreElements()) {
          Order each = (Order) e.nextElement();
          result += each.getAmount();
       }
      return result;
}

本例中的outstanding变量只是很单纯地被初始化为一个明确初值,所以我可以只在新函数中对它初始化.如果代码还对这个变量做了其他处理,我就必须将它的值作为参数传给目标函数.对于这种变化,最初代码可能是这样:

void printOwing(double previousAmount) {
    Enumeration e = _orders.elements();
    double outstanding = previousAmount * 1.2;

   printBanner();

    // calculate outstanding
    while(e.hasMoreElements()) {
       Order each = (Order) e.nextElement();
       outstanding += each.getAmount();
    }

   printDetails(outstanding);
}

提炼后的代码可能是这样:

void printOwing(double previousAmount) {
   double outstanding = previousAmount * 1.2;

   printBanner();
   double outstanding = getOutstanding(outstanding);
   printDetails(outstanding);
}
double getOutstanding(double initialValue) {
       double result = initialValue;
       Enumeration e = _orders.elements();
       

       while(e.hasMoreElements()) {
          Order each = (Order) e.nextElement();
          result += each.getAmount();
       }
      return result;
}
编译并测试后,我再将变量outstanding的初始化过程整理一下:
void printOwing(double previousAmount) {
   printBanner();
   double outstanding = getOutstanding(previousAmount * 1.2);
   printDetails(outstanding);
}

这时候,你可能会问:[如果需要返回的变量不止一个,又该怎么办?]

你有数种选择.最好的选择通常是:挑选另一块代码来提炼.我比较喜欢让每个函数都只返回一个值,所以我会安排多个函数,用以返回多个值.如果你使用的语言支持[输出式参数](output parameters),你可以使用它们带回多个回传值.但我还是尽可能选择单一返回值.

临时变量往往为数众多,甚至会使提炼工作举步维艰.这种情况下,我会尝试先运用Replace Temp with Query(120)减少临时变量.如果即使这么做了提炼依旧困难重重,我就会动用Replace Method with Method Object(135),这个重构手法不在乎代码中有多少临时变量,也不在乎你如何使用它们.

posted @ 2005-08-24 16:14 ivaneeo 阅读(206) | 评论 (0)编辑 收藏

仅列出标题
共67页: First 上一页 48 49 50 51 52 53 54 55 56 下一页 Last