ivaneeo's blog

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

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

#

范例(Examples):有局部变量(Using Local Variables)

是的,就在局部变量,包括传进源函数的参数和源函数所声明的临时变量。局部变量的作用域仅限于源函数,所以当我使用Extract Method(110)时,必须花费额外工夫去处理这些变量.某些时候它们甚至可能妨碍我,使我根本无法进行这项重构.

局部变量最简单的情况是:被提炼码只是读取这些变量的值,并不修改它们.这种情况下我可以简单地将它们当作参数传给目标函数.所以如果我面对下列函数:
void printOwing() {
    Enumeration e = _orders.elements();
    double outstanding = 0.0;

    printBanner();

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

    //print details
   
System.out.println("name:" + _name);
   
System.out.println("amount" + outstanding);
}

我就可以将[打印详细信息]这一部分提炼为[带一个参数的函数]:

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 printDetails(double outstanding) {
    System.out.println("name:" + _name);
   
System.out.println("amount" + outstanding);
}

必要的话,你可以用这种手法处理多个局部变量.

posted @ 2005-08-24 15:13 ivaneeo 阅读(213) | 评论 (0)编辑 收藏

范例(Examples):无局部变量(No Local Variables)
在最简单的情况下,Extract Method(110)易如反掌。请看下列函数:

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

    //print banner
    System.out.println("********************************");
    System.out.println("********* Customer Owes **********");
    System.out.println("********************************");

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

    //print details
   
System.out.println("name:" + _name);
   
System.out.println("amount" + outstanding);
}

我们可以轻松提炼出[打印banner]的代码。我只需要剪切、粘贴、再插入一个函数调用动作就行了:

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

    printBanner();

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

    //print details
   
System.out.println("name:" + _name);
   
System.out.println("amount" + outstanding);
}

void printBanner() {
   
//print banner
    System.out.println("********************************");
    System.out.println("********* Customer Owes **********");
    System.out.println("********************************");
}
posted @ 2005-08-24 14:59 ivaneeo 阅读(190) | 评论 (0)编辑 收藏

作法(Mechanics)
    • 创造一个新函数,根据这个函数的意图来给它命名(以它[做什么]来命名,而不是以它[怎么做]命名)。 --》即使你想要提炼(extract)的代码非常简单,例如只是一条消息或一个函数调用,只要新函数的名称能够以更好方式昭示代码意图,你也应该提炼 它。但如果你想不出一个更有意义的名称,就别动。
    • 将提炼出的代码从源函数(source)拷贝到新建的目标函数(target)中。
    • 仔细检查提炼出的代码,看看其中是否引用了[作用域(scope)限于源函数]的变量(包括局部变量和源函数参数)。
    • 检查是否有[仅用于被提炼码]的临时变量(temporary variables)。如果有,在目标函数中将它们声明为临时变量。
    • 检查被提炼码,看看是否有任何局部变量(local-scope variables)的值被它改变。如果一个临时变量值被修改了,看看是否可以被提炼码处理为一个查询(query),并将结果赋值给相关变量。如果很难 这样做,或如果被修改的变量不止一个,你就不能仅仅将这段代码原封不动地提炼出来。你可能需要先使用Split Temporary Variable(128),然后再尝试提炼。也可以使用Replace Temp with Query(120)将临时变量消灭掉。
    • 将被提炼码中需要读取的局部变量,当作参数传给目标函数。
    • 处理完所有局部变量之后,进行编译。
    • 在源函数中,将被提炼码替换为[对目标函数的调用]。--》如果你将任何临时变量移到目标函数中,请检查它们原本的声明式是否在被提炼码的外围。如果是,现在你可以删除这些声明式了。
    • 编译,测试。
posted @ 2005-08-24 11:04 ivaneeo 阅读(193) | 评论 (0)编辑 收藏

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

有数个原因造成我喜欢简短而有良好命名的函数。首先,如果每个函数的粒度都很小(finely grained),那么函数之间彼此复用的机会就更大;其次,这会使高层函数码读起来就像一系列注释;再者,如果函数都是细粒度,那么函数的覆写 (override)也会更容易些。

一个函数多长才算合适?在我看来,长度不是问题,关键在于函数名称和函数本体之间的语义距离(semantic distance)。如果提炼动作(extracting)可以强化代码的清晰度,那就去做,就算函数名称比提炼出来的代码还长也无所谓。
posted @ 2005-08-24 10:54 ivaneeo 阅读(234) | 评论 (0)编辑 收藏

Extract Method

将这段代码放进一个独立函数中,并让函数名称解释该函数的用途。

void printOwing(double amount) {
    printBanner();

    //print details
    System.out.println("name:" + _name);
    System.out.println("amount" + amount);
}

                         |  |
                         |  |
                        \  /

void printOwing(double amount) {

    printBanner();

    printDetails(amount);
}

void printDetails(double amount) {
    System.out.println("name:" + _name);

    System.out.println("amount" + amount);
}
posted @ 2005-08-24 10:33 ivaneeo 阅读(194) | 评论 (0)编辑 收藏

 随着人们对动态语言兴趣的日益浓厚,越来越多的人都遇到了闭包(Closures )和或块(Blocks)等概念。有着C/C++/Java/C#等语言背景的人因为这些语言本身没有闭包这个概念,所以可能不太了解闭包。本文将简单的介绍一下闭包的概念,那些有大量支持闭包语言编程经验的人也许觉得本文不会太有意思。

    闭包的概念已经提出很长时间了。我第一次碰到这它是在smalltalk中,那时候还叫做块(blocks)。Lisp语言中用的很多。Ruby中也有同样的功能-这也是Ruby用户喜欢Ruby的一个原因。

    本质上来说,一个闭包是一块代码,它们能作为参数传递给一个方法调用。我将通过一个简单的例子来阐述这个观点。假设我们有一个包含一些雇员对象的列表,然后我想列出职位为经理的员工,这样的员工可以通过IsManager判断。在C#里,我们可能会写出下面类似的代码:

  public static IList Managers(IList emps) {
    IList result = new ArrayList();
    foreach(Employee e in emps)
      if (e.IsManager) result.Add(e);
    return result;
  }

    在一种支持闭包的语言中,比如Ruby,我们可以这样写:

  def managers(emps)
	return emps.select {|e| e.isManager}
  end
  

    select是Ruby中定义的集合结构中的一个方法,它接受一个block,也就是闭包,作为一个参数。在Ruby中,闭包写在一对大括号中(不止这一种方法,另一种为do .. end)。如果这个块也接受参数,你可以将这些参数放到两个竖线之间。select方法循环迭代给定的数组,对每个元素执行给定的block,然后将每次执行block返回true的元素组成一个新的数组再返回。

    现在,如果你是C程序员你也许要想,通过函数指针也可以实现,如果你是JAVA程序员,你可能回想我可以用匿名内类来实现,而一个C#者则会想到代理(delegate)。这些机制和闭包类似,但是它们和闭包之间有两个明显得区别。

    第一个是形式上的不同(The first one is a formal difference)。闭包可以引用它定义时候可见的变量。看看下面的方法:

def highPaid(emps)
	threshold = 150
	return emps.select {|e| e.salary > threshold}
end
  

    注意select的block代码中引用了在包含它的方法中的局部变量,而其它不支持真正闭包的语言使用其它方法达到类似功能的方法则不能这样做。闭包还允许你做更有趣的事情,比如下面方法:

def paidMore(amount)
	return Proc.new {|e| e.salary > amount}
end

    这个方法返回一个闭包,实际上它返回一个依赖于传给它的参数的闭包。我可以用一个参数创建一个这样的方法,然后再把它赋给另一个变量。

highPaid = paidMore(150)

    变量 highPaid 包含了一段代码(在Ruby中是一个Proc对象),这段代码将判断一个对象的salary属性是否大于150。我们可以这样使用这个方法:

john = Employee.new
john.salary = 200
print highPaid.call(john)
  

      表达式highPaid.call(john)调用我之前定义的代码,这时候此代码中的amount已经在创建这方法的时候绑定为150。即使现在我执行print 的时候,150已经不在它的范围内了,但是amount和150之间的绑定依然存在。

    所以,闭包的第一个关键点是闭包是一段代码加上和定义它的环境之间的绑定(they are a block of code plus the bindings to the environment they came from)。这是闭包和函数指针等其它相似技术的不同点(java匿名内类可以访问局部变量,但是只有当这些内类是final的时候才行)。

    第二个不同点不是定义形式的不同,但是也同样重要。(The second difference is less of a defined formal difference, but is just as important, if not more so in practice)。支持闭包的语言允许你用很少的语法去定义一个闭包,尽管这点可能不是很重要的一点,但我相信这点是至关重要的-这是使得人们能很自然的使用闭包的关键点。看看Lisp,Smalltalk和Ruby,闭包遍布各处-比其它语言中类似的使用多很多。绑定局部变量是它的特点之一,但我想最大的原因是使用闭包的语法和符号非常简单和清楚。

    一个很好的相关例子是从Smalltalk程序员到JAVA程序员,开始时很多人,包括我,试验性的将在Smalltalk中使用闭包的地方在Java中使用匿名内类来实现。但结果使得代码变得混乱难看,所以我们不得不放弃。

   我在Ruby经常使用闭包,但我不打算创建Proc对象,然后传来传去。大多数时间我用闭包来处理前面我提到的select等基于集合对象的方法。闭包另一个重要用途是'execute around method',比如处理一个文件:

File.open(filename) {|f| doSomethingWithFile(f)}

   这里open方法打开一个文件,然后执行给定的block,然后关闭它。这样处理非常方便,尤其是对事务(要求commit或者rollback),或者其它的你需要在处理结束时候作一些收尾处理的事情。我在我的xml文档转换中广泛使用这个优点。

   闭包的这些用法显然远不如用Lisp语言的人遇到的多,即使我,在使用没有闭包支持的语言的时候,也会想念这些东西。闭包就像一些你第一眼见到觉得不怎么样的东西,但你很快就会喜欢上它们。

posted @ 2005-08-23 17:06 ivaneeo 阅读(242) | 评论 (0)编辑 收藏

如果你需要注释来解释一块代码做了什么,试试Extract Method(110);如果method已经提炼出来,但还是需要注释来解释其行为,试试Rename Method(273);如果你需要注释说明某些系统的需求规格,试试Introduce Assertion(267)。

如果你不知道该做什么,这才是注释的良好运用时机。除了用来记述将来的打算之外,注释还可以用来标记你并无十足把握的区域。你可以在注释里写下自己[为什么做某某事]。这类信息可以帮助将来的修改着,尤其是那些健忘的家伙。
posted @ 2005-08-19 17:35 ivaneeo 阅读(285) | 评论 (0)编辑 收藏

subclass应该继承superclass的函数和数据。但如果它们不想或不需要继承,又该怎么办呢?它们得到所有礼物,却只从中挑选几样来玩!

按传统说法,这就意味继承系统设计错误。你需要为这个subclass新建一个兄弟(sibling class),再运用Push Down Method(328)和Push Down Field(329)把所有用不到的函数下推给那兄弟。这样一来superclass就持有所有subclasses共享的东西。常常你会听到这样的建议:所有superclasses都应该是抽象的(abstract)。

既然使用[传统说法]这个略带贬义的词,你就可以猜到,我们不建议你这么做,起码不建议你每次都这么做。我们经常利用subclassing手法来复用一些行为,并发现这可以很好地应用于日常工作。这也是一种坏味道,我们不否认,但气味通常并不强烈。所以我们说:如果Refused Bequest引起困惑和问题,请遵循传统忠告。但不必认为你每次都得那么做。十有八九这种坏味道很淡,不值得理睬。

如果subclass复用了superclass的行为(实现),却又不原意支持superclass得接口,Refused Bequest的坏味道就会变得浓烈。拒绝继承superclass的实现,这一点我们不介意;但如果拒绝继承superclass的接口,我们不以为然。不过即使你不原意继承接口,也不要胡乱修改继承体系,你应该运用Replace Inheritance with Delegation(352)来达到目的。
posted @ 2005-08-19 17:26 ivaneeo 阅读(372) | 评论 (0)编辑 收藏

所谓Data Class是指:它们拥有一些值域(fields),以及用于访问(读写)这些值域的函数,除此之外一无长物。这样的classes只是一种[不会说话的 数据容器],它们几乎一定被其他classes过分细琐地操控着。这些classes早期可能拥有public值域,果真如此你应该在别人注意到它们之 前,立刻运用Encapsulate Field(206)将它们封装起来。如果这些classes内含容器类的值域(collection fields),你应该检查它们是不是得到了恰当的封装;如果没有,就运用Encapsulate Collection(208)把它们封装起来。对于那些不该被其他classes修改的值域,请运用Remove Setting Method(300)。

然后,找出这些[取值/设值]函数(getting and setting methods)被其他classes运用的地点。尝试以Move Method(142)把那些调用行为搬移到Data Class来。如果无法搬移整个函数,就运用Extract Method(110)产生一个可被搬移的函数。不久之后你就可以运用Hide Method(303)把这些[取值/设值]函数隐藏起来了。
posted @ 2005-08-19 17:05 ivaneeo 阅读(519) | 评论 (0)编辑 收藏

幸好我们有两个专门应付这种情况的工具。如果你只想修改library classes内的一两个函数,可以运用Introduce Foreign Method(162);如果想要添加一大堆额外行为,就得运用Introduce Local Extension(164)。
posted @ 2005-08-19 16:50 ivaneeo 阅读(265) | 评论 (0)编辑 收藏

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