31天重构指南

本文源自:31天重构指南

本文源自 infoQ 上的一篇文章,发现该篇文章非常实用,便记录于此。

该文列出了Sean Chambers在博客中编写的一系列描述重构方式的文章。Sean指出,这些重构方式主要来源于Martin的重构资源站点、代码大全(第二版)以及平时由他自己收集的互联网资源,他的目的是为各种重构方式提供了一些额外的描述及相关的讨论。

以下是这31篇文章列表:

  1. 封装集合
  2. 移动方法
  3. 提升(pull up)方法层级
  4. 降低(push down)方法
  5. 提升字段
  6. 降低字段
  7. 改名(方法,类,参数)
  8. 使用委派替换继承
  9. 提取接口
  10. 提取方法
  11. 使用策略类
  12. 分解依赖
  13. 提取方法对象
  14. 分离职责
  15. 移除重复内容
  16. 封装条件
  17. 提取父类
  18. 使用条件判断代替异常
  19. 提取工厂类
  20. 提取子类
  21. 合并继承
  22. 分解方法
  23. 引入参数对象
  24. 分解嵌套条件判断
  25. 引入契约式设计
  26. 避免双重否定
  27. 去除上帝类
  28. 为布尔方法命名
  29. 去除中间人对象
  30. 尽快返回
  31. 使用多态代替条件判断

第1天. 封装集合

某些场景下,我们并不希望直接将集合暴露给客户类,这时我们可以将集合进行封装,例如只提供对集合的增加/删除:

public class Order {
    private List _orderLines;

    public Iterator orderLines() {
        return _orderLines.iterator();
    }

    public void addOrderLine(OrderLine orderLine) {
        _orderLines.add(orderLine);
    }

    public void removeOrderLine(OrderLine orderLine) {
         _orderLines.remove(orderLine);
    }
}

第2天. 移动方法

如果一个方法在某个类中所使用的次数要远远多于它本身所在的类,那么很可能该方法应隶属于前一个类。

第3天. 提升方法层级

当多个子类都需要某个方法时,那么需要将这个方法从各子类级别提升到该继承体系中的上一级。

第4天. 降低方法层级

与第3天重构相反,当只有个别子类需要某方法时,不妨将该方法从父类中移至该子类。

第5天. 提升字段

与提升方法层级类似,这里需要提升的是字段。

第6天. 降低字段

与降低方法层级类似,这里需要降低的是字段。

第7天. 改名(方法,类,参数)

当方法名,类名,或参数名称不具有描述性,没有含义,或表达不恰当时,需要对这些名称进行重构。

第8天. 使用委派代替继承

这是老生常谈了,has-a is better than is-a。继承打破了类之间的封装性,某些情况下,基类的改变将导致子类行为异常(<Effective Java)。据说,James Gosling对于他所设计的Java语言最后悔的一件事就是允许继承。所以,只有真正是 is-a 的关系时,方可使用继承。

第9天. 提取接口

当不止一个类使用同一个类的方法集合时,那么最好为后者提取出一个接口,以实现解耦。

第10天. 提取方法

简单地来讲,就是一个方法只做一件事,不要在一个方法中糅合多个逻辑。

第11天. 使用策略模式代替switch

如果 switch-case 中的 case 条件经常发生变动(如增加或移除),那么不妨使用策略模式代替之。详见 Switch to Strategy

第12天. 分解依赖

引入中间类分解依赖。

第13天. 提取方法对象

与提取方法类似,只不过要把这些方法放在适合它们的类中。

第14天. 分离职责

单一责任原则,即一个类只做它该做的事情。如果该类所提供的各接口之间不相关,那么把这些不相关的接口分离出去。

第15天. 移除重复内容

一段类似代码多次重复出现,那么最好将它们整合到一个方法中,实现代码共享。

第16天. 封装条件

如果一个条件检查涉及到多个判断,那么最好将这个条件检查提取出来成为一个属性或一个方法,这样有利于程序阅读。

第17天. 提取父类

与第3天的<提升方法层级>类似,区别是在本重构中父类尚不存在,这时需新建父类,然后将方法提升级别至这个父类,以便于父类的其它子类也能共享这些方法。

第18天. 使用条件判断代替异常

本重构是指不要滥用异常处理来控制程序流。

第19天. 提取工厂类

抽象工厂或工厂方法。

第20天. 提取子类

与第4天<降低方法层级>类似,区别是将方法降低到的低级别子类尚不存在,需新建。

第21天. 合并继承

今日重构方法是指,当子类功能其实是可以合并到父类中,那么何必还要多出一个子类呢?

第22天. 分解方法

将一个大方法分成若干小方法进行调用,而不是将逻辑都放在一个大方法里,难以阅读。

第23天. 引入参数对象

当参数个数太多时,是非常不利于客户使用该 API 的,因为这样的方法难以阅读和理解。重构办法是,引入参数对象,将参数组织成适当的对象作为方法参数。

第24天. 分解嵌套条件判断

典型的嵌套条件判断是多层的 if-else 判断,这使得程序不那么易读。典型场景是在方法开始时检查参数,只有所有参数都符合需求时,才开始执行方法功能。不妨逐个检查参数,不符合要求时,马上退出方法。

第25天. 引入契约式设计

Design By Contract or DBC。本条重构个人觉得纯属扯蛋,不能称之为重构。

第26天. 避免双重否定

双重否定难以理解,比如

if ( !customer.IsNotFlagged ) {
...
}

改为

if (customer.IsFlagged) {
...
}

更易于理解

第27天. 去除上帝类

上帝类是指那些融合了各种功能的类,往往造成该类职责不清,该有的有,不该有的也有。典型的如以manager,util 结尾的类。重构方法是,将这些方法分解到适合它们的类中。

第28天. 为布尔方法重命名

还是见原文吧。

第29天. 去除中间人对象

中间人对象是指某对象只是简单的将方法调用转发给目标对象,而其本身并没有做任何工作,留之纯属多余,干脆去掉。

第30天. 尽快返回

与第24天<分解嵌套条件判断>类似,条件不符合则马上返回结果。

第31天. 使用多态代替条件判断

如果需要根据对象所属类型进而做某些工作,那么最好改用多态。

By javafuns on September 7, 2009 at 22:55 · Views: 212 · Permalink · Leave a comment
Categorized in: Java · Tagged with: ,

关于Arrays.asList()

Arrays.asList()所返回的结果是Arrays的一个静态内部类,该静态内部类只override了AbstractList的有限的几个方法,且对该结果的修改将直接反应到原数组上。

比如,今天我在将数组通过Arrays.asList()转化为list后企图对该list进行remove(index),结果导致UnsupportedOperationException。原因就在于这个静态内部类并没有override父类AbstractList的remove(index)方法。

By javafuns on August 31, 2009 at 13:55 · Views: 274 · Permalink · Leave a comment
Categorized in: Java · Tagged with: 

Groovy 学习笔记 (四)

  1. Groovy 支持操作符重载, 每个操作符对应一个方法签名, 如‘+’对应的是’plus’.
  2. Groovy 支持多种字符串表示, 单引号, 双引号(支持GString), 3个单引号(支持多行), 3个双引号(支持多行, 支持GString).
  3. 在 Groovy 中, 方法的括号是可选的, 如果一行只有一个语句, 那么语句后的分号也可省略不写.
  4. Groovy range, 可用于: 数字, 日期, 字符串. 只要数据类型实现了 next(++), previous(–), 和 java.lang.Comparable 接口, 就可以使用 range .
  5. Groovy list 默认使用 ArrayList, 欲使用 LinkedList 等其它类型 List, 需要明确声明.
  6. List 可以使用负值作为index值进行访问, 例如 list[-1]返回的就是list的最后一个值, list[-2]则返回倒数第二个值. 也可以指定倒序的 range, 如list[4..0].
  7. 需注意: list[0..<-2] 等价于 list[0..-1] 而非 list[0..-3].
  8. 声明空的map: [:]
  9. ['a':1] 等价于 [a:1]
By javafuns on August 25, 2009 at 22:22 · Views: 258 · Permalink · Leave a comment
Categorized in: Scripts · Tagged with: , ,

Groovy 学习笔记 (三)

1. == 是判断是否 equals, 而判断是否是同一对象则用 is

2. list 和 map 的一些方法:

def x  = 1..10
assert x.contains(5)
assert x.contains(15) == false
assert x.size()    == 10
assert x.from      == 1
assert x.to        == 10
assert x.reverse() == 10..1

3. 以下对象都有 size() 方法
Array, String, StringBuffer, Collection, Map, File

4. 数据类型:12 – Integer, 100L – Long, 1.23F – Float, 1.23D – Double, 123G – BigInteger, 1.23,1.23G – BigDecimal

如果一个小数, 末尾什么字符都没加, 那么这个小数使用的是 BigDecimal.

5. 操作符重载

Operator Name Method Works with
a + b Plus a.plus(b) Number,string,collection
a – b Minus a.minus(b) Number,string,collection
a * b Star a.multiply(b) Number,string,collection
a / b Divide a.div(b) Number
a % b Modulo a.mod(b) Integral number
a++ Post increment a.next() Number,string,range
++a Pre increment
a– Post decrement a.previous() Number,string,range
–a Pre decrement
a**b Power a.power(b) Number
a | b Numerical or a.or(b) Integral number
a & b Numerical and a.and(b) Integral number
a ^ b Numerical xor a.xor(b) Integral number
~a Bitwise complement a.negate() Integral number,string
(the latter returning a regular expression pattern)
a[b] Subscript a.getAt(b) Object,list,map,string、Array
a[b] = c Subscript assignment a.puAt(b,c) Object,list,map,StringBuffer,
Array
a << b Left shift a.leftShift(b) Integral number, also used like “append” to StringBuffers, Writers, Files, Sockets, Lists
a >> b Right shift a.rightShift(b) Integral number
a >>> b Right shift unsigned a.rightShiftUnsigned(b) Integral number
switch(a){
case b:
}
Classification b.isCase(a) Object, range, list, collection, pattern, closure; also used with collection c in c.grep(b), which returns all items of c where b.isCase(item)
a == b Equals a.equals(b) Object, consider hashCode()
a != b Not equals !a.equals(b) Object
a <=> b Spaceship a.compareTo(b) java.lang.Comparable
a > b Greater than a.compareTo(b) > 0
a >= b Greater than or equal to a.compareTo(b) >= 0
a < b Less than a.compareTo(b) < 0
a <= b Less than or equal to a.compareTo(b) <= 0
a as type Enforced coercion a.asType(typeClass) Any type
class Money {
  private int    amount
  private String currency
  Money (amountValue, currencyValue) {
    amount   = amountValue
    currency = currencyValue
  }

  boolean equals (Object other) {
    if (null == other)              return false
    if (! (other instanceof Money)) return false
    if (currency != other.currency) return false
    if (amount    != other.amount)  return false
    return true
  }

  int hashCode() {
    amount.hashCode() + currency.hashCode()
  }

  Money plus (Money other) {
    if (null == other)    return null
    if (other.currency != currency) {
      throw new IllegalArgumentException("cannot add $other.currency to $currency")
    }
    return new Money(amount + other.amount, currency)
  }
}
def buck = new Money(1, 'USD')
d Use overridden ==
assert buck
assert buck == new Money(1, 'USD')
e Use overridden +
assert buck + buck == new Money(2, 'USD')

6.

By javafuns on August 5, 2009 at 22:21 · Views: 294 · Permalink · Leave a comment
Categorized in: Scripts · Tagged with: , ,

The interesting but useful mock assertion in Java

在某开源项目里, 发现它使用了一种很有意思的 assertion 机制. 这是一种仿 assertion 的方式来实现对输入参数或特定表达式的判断.

伪代码如下:

  public final class Assert {
    private Assert() {}

    public static void notNull(Object o) {
      notNull("Null object is used", o);
    }

    public static void notNull(String description, Object o) {
      if(o == null) {
        throw new AssertionException(description);
      }
    }

    ...... // other assertion methods
  }

AssertionException 继承自 RuntimeException, 因此是可以不捕获的.

在某方法里可以如此使用这个 Assert :

  public void someMethod(String arg1 ...) {
    Assert.notNull(arg1);
    ...
  }

好处是:
1. 类似 Java 已有的 Assertion 机制, 使用起来非常直观.
2. 减少了大量对参数进行 if-else 等的判断, 代码看起来更加整洁, 易懂.
3. 对类似错误可以进行统一处理, 对错误信息进行统一描述.

By javafuns on July 30, 2009 at 10:55 · Views: 266 · Permalink · Leave a comment
Categorized in: Java · Tagged with: ,

XML 中的 schemaLocation 属性究竟是什么意思?

在 XML 实例文档中有时会发现有 schemaLocation 属性。很多人对此非常疑惑,搞不清这个属性究竟是什么意思,究竟该如何使用。

schemaLocation 属性用来引用(schema)模式文档,解析器可以在需要的情况下使用这个文档对 XML 实例文档进行校验。它的值(URI)是成对出现的,第一个值表示命名空间,第二个值则表示描述该命名空间的模式文档的具体位置,两个值之间以空格分隔。当然,在必要情况下,可以为 schemaLocation 属性指派多个这样的值对。

<p:Person
  xmlns:p="http://contoso.com/People"
  xmlns:v="http://contoso.com /Vehicles"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation=
    "http://contoso.com/People

http://contoso.com/schemas/people.xsd

http://contoso.com/schemas/Vehicles

http://contoso.com/schemas/vehicles.xsd

http://contoso.com/schemas/People

    http://contoso.com/schemas/people.xsd">
  <name>John</name>
  <age>28</age>
  <height>59</height>
  <v:Vehicle>
    <color>Red</color>
    <wheels>4</wheels>
    <seats>2</seats>
  </v:Vehicle>
</p:Person>

如果为没有目标命名空间的模式文档指定位置,需用 noNamespaceSchemaLocation 属性.


By javafuns on July 23, 2009 at 13:43 · Views: 586 · Permalink · Leave a comment
Categorized in: SOA · Tagged with: ,

Java Thread 注意事项

  1. 该同步要同步
  2. 同步块尽可能的小
  3. 循环内使用wait()
  4. synchronized (obj) {
        while (condition) {
            obj.wait();
        }
    }
    因为唤醒后,条件是否满足还不一定,所以还需要再次检查。
  5. notifyAll()优先于notify()
  6. yield()不可靠
  7. 在持有锁的时候, 尽量不要调用其它对象的方法(这些方法可能也是同步过的), 因为这很可能是死锁的源头.
  8. 对象锁:使用对方法进行synchronized时,线程进入该方法前需先获得该对象上的同步锁。所以,即使另外一个线程调用的是该对象上的另一个方法,因为此时该对象锁已被另一线程占有,所以该线程还是要排队等候以占有对象锁。
    由于线程进入非同步方法并不需要占有锁,所以非同步方法并不会被该对象上的同步方法所阻塞。

    对象锁:使用对方法进行synchronized时,线程进入该方法前需先获得该对象上的同步锁。所以,即使另外一个线程调用的是该对象上的另一个方法,因为此时该对象锁已被另一线程占有,所以该线程还是要排队等候以占有对象锁。由于线程进入非同步方法并不需要占有锁,所以非同步方法并不会被该对象上的同步方法所阻塞.

  9. 类锁:对静态方法进行 synchronized,那么就决定了进入该静态同步方法前,线程必须先获得类锁。类锁其实是并不存在的,原因在于类锁是基于类的class对象,每个类都有唯一的一个class对象与之对应,该类的所有实例也都拥有该唯一class对象的引用,因此,静态同步方法所使用的锁其实就是类的class对象,本质上还是一个对象锁.
  10. 基于以上两点,对象锁与类锁互不相干,因为它们的锁是完全不同的.
  11. ThreadDeath 是 Error 的子类. 调用 Thread 类中带有零参数的 stop 方法(该方法已不推荐使用)时,受害线程将抛出一个 ThreadDeath 实例.
  12. 线程优先级取决于底层实现, 应尽量避免使用.
  13. 线程结束后,再次调用该线程对象的 start() 方法会抛出异常,也就是不要重新开始一个已经执行完毕的线程.
  14. 守护线程与非守护线程的区别:当进程中所有非守护线程已(结束或)退出时,即使仍有守护线程在运行,进程仍将结束.
By javafuns on July 22, 2009 at 22:07 · Views: 206 · Permalink · Leave a comment
Categorized in: Java · Tagged with: 

SMTP Transport Binding for SOAP

SOAP 除了可以 bind 到 HTTP 上外, 也可以 bind 到 SMTP 上. 其实个人感觉, SOAP bind 到 SMTP 上的场景很少会使用到, 通常限于 one-way operation (比如通知等不需要响应). 如果需要实现 request/response, 那么是需要多做一些工作的(下文会有叙述).

soap:binding element 的 transport 属性需指定一个 http://xxx(例如:http://schemas.xmlsoap.org/soap/smtp) URL 来表明所要 binding 的 protocol. 似乎这个 URL 是任意形式, 只要能让调用方知道要使用什么协议即可. 在 soap:address 要给定 mailto:xxxx@xxx.xxx 这样形式的 URI.

    <service name="StockQuoteServiceBinding_service">
        <port name="StockQuoteServiceBinding_port"
                binding="binding:StockQuoteServiceBinding">
            <soap:address location="mailto:getQuote@test.com"/>
        </port>
    </service>

注意: 在 soap:address element 中, 可能会有一些扩展元素, 例如smtp server, smtp username, smtp password, etc.

在需要 request / response 语义情况下, 需使用标准的 Message-Id 和 In-Reply-To SMTP header 来实现这一目标. 请求包含一个 Message-Id header, 相应的响应则在 In-Reply-To header 中使用请求的 Message-Id header 值, 同时也创建一个新的 Message-Id header. 这样, 在请求与响应之间便建立了关联.

请求:

To: <soap@example.org>
 From: <soap@client.com>
 Reply-To: <soap@client.com>
 Date: Tue, 15 Nov 2001 23:27:00 -0700
 Message-Id: <1F75D4D515C3EC3F34FEAB51237675B5@client.com>
 MIME-Version: 1.0
 Content-Type: text/xml; charset=utf-8
 Content-Transfer-Encoding: QUOTED-PRINTABLE

 <?xml version=3D"1.0" encoding=3D"UTF-8"?>
 <SOAP-ENV:Envelope SOAP-ENV:encodingStyle=3D"http://schemas.xmlsoap.org/soap/encoding/"
 xmlns:SOAP-ENC=3D"http://schemas.xmlsoap.org/soap/encoding/"
 xmlns:SOAP-ENV=3D"http://schemas.xmlsoap.org/soap/envelope/"
 xmlns:xsd=3D"http://www.w3.org/2001/XMLSchema"
 xmlns:xsi=3D"http://www.w3.org/2001/XMLSchema-instance">
 <SOAP-ENV:Body>
 <m:echoString xmlns:m=3D"http://soapinterop.org/">
 <inputString>get your SOAP over SMTP here !</inputString>
 </m:echoString>
 </SOAP-ENV:Body>
 </SOAP-ENV:Envelope>

响应:

To: <soap@client.com>
 From: <soap@example.org>
 Date: Tue, 13 Nov 2001 23:27:00 -0700
 In-Reply-To: <1F75D4D515C3EC3F34FEAB51237675B5@client.com>
 Message-Id: <FF75D4D515C3EC3F34FEAB51237675B5@soap.example.org>
 MIME-Version: 1.0
 Content-Type: TEXT/XML; charset=utf-8
 Content-Transfer-Encoding: QUOTED-PRINTABLE

 <?xml version=3D"1.0" encoding=3D"UTF-8"?>
 <SOAP-ENV:Envelope SOAP-ENV:encodingStyle=3D"http://schemas.xmlsoap.org/soap/encoding/"
 xmlns:SOAP-ENC=3D"http://schemas.xmlsoap.org/soap/encoding/"
 xmlns:SOAP-ENV=3D"http://schemas.xmlsoap.org/soap/envelope/"
 xmlns:xsd=3D"http://www.w3.org/2001/XMLSchema"
 xmlns:xsi=3D"http://www.w3.org/2001/XMLSchema-instance">
 <SOAP-ENV:Body>
 <m:echoStringResponse xmlns:m=3D"http://soapinterop.org/">
 <return>get your SOAP over SMTP here !</return>
 </m:echoStringResponse>
 </SOAP-ENV:Body>
 </SOAP-ENV:Envelope>

Resources:

  1. http://www.pocketsoap.com/specs/smtpbinding/
  2. http://people.apache.org/~pzf/SMTPBase64Binding.html
By javafuns on July 19, 2009 at 23:37 · Views: 229 · Permalink · Leave a comment
Categorized in: SOA · Tagged with: , , , ,
  • Highest Rated

  • My PicasaPhotos

    IMG_0659.JPG

    xibeTotem.jpg

    IMG_0535.JPG

  • RSS My del.icio.us

  • My RSS