31天重构指南
本文源自:31天重构指南
本文源自 infoQ 上的一篇文章,发现该篇文章非常实用,便记录于此。
该文列出了Sean Chambers在博客中编写的一系列描述重构方式的文章。Sean指出,这些重构方式主要来源于Martin的重构资源站点、代码大全(第二版)以及平时由他自己收集的互联网资源,他的目的是为各种重构方式提供了一些额外的描述及相关的讨论。
以下是这31篇文章列表:
- 封装集合
- 移动方法
- 提升(pull up)方法层级
- 降低(push down)方法
- 提升字段
- 降低字段
- 改名(方法,类,参数)
- 使用委派替换继承
- 提取接口
- 提取方法
- 使用策略类
- 分解依赖
- 提取方法对象
- 分离职责
- 移除重复内容
- 封装条件
- 提取父类
- 使用条件判断代替异常
- 提取工厂类
- 提取子类
- 合并继承
- 分解方法
- 引入参数对象
- 分解嵌套条件判断
- 引入契约式设计
- 避免双重否定
- 去除上帝类
- 为布尔方法命名
- 去除中间人对象
- 尽快返回
- 使用多态代替条件判断
第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天. 使用多态代替条件判断
如果需要根据对象所属类型进而做某些工作,那么最好改用多态。
Categorized in: Java · Tagged with: Java, Refactoring
关于Arrays.asList()
Arrays.asList()所返回的结果是Arrays的一个静态内部类,该静态内部类只override了AbstractList的有限的几个方法,且对该结果的修改将直接反应到原数组上。
比如,今天我在将数组通过Arrays.asList()转化为list后企图对该list进行remove(index),结果导致UnsupportedOperationException。原因就在于这个静态内部类并没有override父类AbstractList的remove(index)方法。
Categorized in: Java · Tagged with: Java
Groovy 学习笔记 (四)
- Groovy 支持操作符重载, 每个操作符对应一个方法签名, 如‘+’对应的是’plus’.
- Groovy 支持多种字符串表示, 单引号, 双引号(支持GString), 3个单引号(支持多行), 3个双引号(支持多行, 支持GString).
- 在 Groovy 中, 方法的括号是可选的, 如果一行只有一个语句, 那么语句后的分号也可省略不写.
- Groovy range, 可用于: 数字, 日期, 字符串. 只要数据类型实现了 next(++), previous(–), 和 java.lang.Comparable 接口, 就可以使用 range .
- Groovy list 默认使用 ArrayList, 欲使用 LinkedList 等其它类型 List, 需要明确声明.
- List 可以使用负值作为index值进行访问, 例如 list[-1]返回的就是list的最后一个值, list[-2]则返回倒数第二个值. 也可以指定倒序的 range, 如list[4..0].
- 需注意: list[0..<-2] 等价于 list[0..-1] 而非 list[0..-3].
- 声明空的map: [:]
- ['a':1] 等价于 [a:1]
Categorized in: Scripts · Tagged with: Groovy, Java, Scripts
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.
Categorized in: Scripts · Tagged with: Groovy, Java, Scripts
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. 对类似错误可以进行统一处理, 对错误信息进行统一描述.
Categorized in: Java · Tagged with: Java, Open Source
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 属性.
Categorized in: SOA · Tagged with: schema, XML
Java Thread 注意事项
- 该同步要同步
- 同步块尽可能的小
- 循环内使用wait()
- notifyAll()优先于notify()
- yield()不可靠
- 在持有锁的时候, 尽量不要调用其它对象的方法(这些方法可能也是同步过的), 因为这很可能是死锁的源头.
-
对象锁:使用对方法进行synchronized时,线程进入该方法前需先获得该对象上的同步锁。所以,即使另外一个线程调用的是该对象上的另一个方法,因为此时该对象锁已被另一线程占有,所以该线程还是要排队等候以占有对象锁。由于线程进入非同步方法并不需要占有锁,所以非同步方法并不会被该对象上的同步方法所阻塞。
对象锁:使用对方法进行synchronized时,线程进入该方法前需先获得该对象上的同步锁。所以,即使另外一个线程调用的是该对象上的另一个方法,因为此时该对象锁已被另一线程占有,所以该线程还是要排队等候以占有对象锁。由于线程进入非同步方法并不需要占有锁,所以非同步方法并不会被该对象上的同步方法所阻塞.
- 类锁:对静态方法进行 synchronized,那么就决定了进入该静态同步方法前,线程必须先获得类锁。类锁其实是并不存在的,原因在于类锁是基于类的class对象,每个类都有唯一的一个class对象与之对应,该类的所有实例也都拥有该唯一class对象的引用,因此,静态同步方法所使用的锁其实就是类的class对象,本质上还是一个对象锁.
- 基于以上两点,对象锁与类锁互不相干,因为它们的锁是完全不同的.
- ThreadDeath 是 Error 的子类. 调用 Thread 类中带有零参数的 stop 方法(该方法已不推荐使用)时,受害线程将抛出一个 ThreadDeath 实例.
- 线程优先级取决于底层实现, 应尽量避免使用.
- 线程结束后,再次调用该线程对象的 start() 方法会抛出异常,也就是不要重新开始一个已经执行完毕的线程.
- 守护线程与非守护线程的区别:当进程中所有非守护线程已(结束或)退出时,即使仍有守护线程在运行,进程仍将结束.
synchronized (obj) {
while (condition) {
obj.wait();
}
}
因为唤醒后,条件是否满足还不一定,所以还需要再次检查。
Categorized in: Java · Tagged with: Java
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:
Categorized in: SOA · Tagged with: SOAP, UDDI, Webservice, WSDL, XML

(
(4.00 out of 5)