Sung in Blog
一些技术文章 & 一些生活杂碎
BlogJava
首页
新随笔
新文章
管理
WebSite ConnecTionS
常用链接
我的随笔
我的评论
我的参与
最新评论
随笔档案
2006年2月 (1)
2005年11月 (2)
2005年10月 (18)
文章分类
Amusement(2)
Database
Eclipse(14)
Everyday life(10)
FrameWork(2)
In GuangZhou
In NanJing
In Xi‘an(2)
Java(55)
Job & Interview(53)
Keep fits(1)
Linux(4)
Management(1)
Network(3)
software Development(17)
Struts(28)
Testing(2)
Thinking in Design(8)
Tomcat(18)
人间五十年(15)
唐诗宋词(7)
天下美食(7)
嵌入式技术
数码时尚(2)
积分与排名
积分 - 252606
排名 - 223
最新评论
1. re: java对象序列化
很好,转走了!谢谢!
--et2007
2. re: Java的Web框架对比
<javascript>alert("略懂")</javascript>
--kara
3. re: C# 与 C 、 C++ 、 D 、 Java 的性能比较[第一部分]
另外c++的流的性能也是烂到了一定境界
我用mmx优化过的读取文件内整数字符串数组的性能是c++循环用流读取的速度几十倍
--zmy
4. re: C# 与 C 、 C++ 、 D 、 Java 的性能比较[第一部分]
c#1.0 不值得拿来说
4.0速度会让你满意的
不知道5.0怎样
--zmy
5. re: C# 与 C 、 C++ 、 D 、 Java 的性能比较 [第二部分]
第一部分呢
--糖糖
阅读排行榜
1. 一些腾讯笔试题目 (16818)
2. [强烈推荐]IBM公司面试题(附答案)——病狗问题(7511)
3. 2005各大公司薪水民间版本(4897)
4. Java的Web框架对比 (3783)
5. 用微软试题膨胀你的思维(3336)
评论排行榜
1. [强烈推荐]IBM公司面试题(附答案)——病狗问题(19)
2. 一些腾讯笔试题目 (17)
3. Java的Web框架对比 (8)
4. 2005各大公司薪水民间版本(4)
5. Software Engineer -2006 Graduate Intake(HP2006校园招聘)(2)
Eclipse中自动重构实现探索
本文用eclipse的自动重构功能对一个程序实例进行重构,目的是探索Eclipse自动重构可以在多大程度上辅助重构这个过程。程序实例使用《Refactoring:Improving the Design of Existing Code》一书中的例子。
Eclipse的自动重构功能能够很好地支持各种程序元素的重命名,并自动更新相关的引用。Eclipse能够支持方法、字段在类之间移动,并自动更新引用。Eclipse较好地支持内联字段、函数的更新替换。Eclipse较好地支持抽取方法、变量等程序元素。
重构的过程是一个不断尝试和探索的过程。Eclipse的重构支持撤销和重做,并且能够预览重构结果,这些是很实用的功能。
Eclipse的重命名、抽取方法、移动、内联功能、更改方法特征符等代码结构级别的重构方法,是比较成熟同时也值得使用的功能。至于设计结构上的重构,eclipse还不能很好地支持。但是作者相信,自动重构的理念应该是"工具辅助下的重构工作",人仍然承担大部分重构工作。
一、预备工作
本文使用《Refactoring:Improving the Design of Existing Code》一书第一章的例子。重构前的代码及每一步重构后的代码见附件。读者最好配合《Refactoring:Improving the Design of Existing Code》一书阅读本文。
Eclipse使用如下版本:
同时安装了中文语言包。
二、重构第一步:分解并重组statement()
目的:
1、 把statement()函数中的swich语句提炼到独立的函数amountFor()中。
2、 修改amountFor()参数命名
重构方法:
Extract Method
Rename Method
方法:
1、选中swich语句的代码块,在右键菜单中选择"重构/抽取方法",出现参数对话框。Eclipse自动分析代码块中的局部变量,找到了两个局部变量:each和thisAmount。其中,each只是在代码块中被读取,但thisAmount会在代码块中被修改。按照重构Extract Method总结出来的规则,应该把each当作抽取函数的参数、thisAmount当作抽取函数的返回值。然而Eclipse并不做区分,直接把这两个变量当作抽取新方法的参数,如图。
我们的目的是把在抽取函数中不会被修改的each作为参数;会被修改的thisAmount作为返回值。解决的办法是,把 double thisAmount = 0; 这行代码移到switch语句的上面,变成这样:
double thisAmount = 0;
switch(each.getMovie().getPriceCode()){
case Movie.REGULAR:
thisAmount += 2;
if(each.getDaysRented()>2)
thisAmount += (each.getDaysRented()-2)*1.5;
break;
case Movie.NEW_RELEASE:
thisAmount += each.getDaysRented()*3;
break;
case Movie.CHILDRENS:
thisAmount += 1.5;
if(each.getDaysRented()>3)
thisAmount += (each.getDaysRented()-3)*1.5;
break;
}
选中这段代码,在右键菜单中选择"重构/抽取方法",eclipse这次变得聪明点了,如图。
选择"预览"按钮预先查看重构后的结果,符合我们最初的目的。
选择"确定"按钮,重构后的代码片断如下:
public String statement() {
double totalAmount = 0;
int frequentRenterPoints = 0;
Enumeration rentals = _rentals.elements();
String result = "Rental Record for " + getName() + "\n";
while(rentals.hasMoreElements()){
Rental each = (Rental)rentals.nextElement();
double thisAmount = amountFor(each);
frequentRenterPoints ++;
if((each.getMovie().getPriceCode())==Movie.NEW_RELEASE &&each.getDaysRented()>1)
frequentRenterPoints ++;
result += "\t" + each.getMovie().getTitle() + "\t" +String.valueOf(thisAmount) + "\n";
totalAmount += thisAmount;
}
result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points";
return result;
}
/**
* @param each
* @return
*/
private double amountFor(Rental each) {
double thisAmount = 0;
switch(each.getMovie().getPriceCode()){
case Movie.REGULAR:
thisAmount += 2;
if(each.getDaysRented()>2)
thisAmount += (each.getDaysRented()-2)*1.5;
break;
case Movie.NEW_RELEASE:
thisAmount += each.getDaysRented()*3;
break;
case Movie.CHILDRENS:
thisAmount += 1.5;
if(each.getDaysRented()>3)
thisAmount += (each.getDaysRented()-3)*1.5;
break;
}
return thisAmount;
}
2、选中amountFor()的参数each,在右键菜单中选择"重构/重命名",在对话框中输入新的名称:aRental,选择确定,amountFor()中所有each的引用全部被替换成新的名称。用同样的办法修改amountFor()中的局部变量thisAmount为result。重构后的amountFor()代码如下:
/**
* @param aRental
* @return
*/
private double amountFor(Rental aRental) {
double result = 0;
switch(aRental.getMovie().getPriceCode()){
case Movie.REGULAR:
result += 2;
if(aRental.getDaysRented()>2)
result += (aRental.getDaysRented()-2)*1.5;
break;
case Movie.NEW_RELEASE:
result += aRental.getDaysRented()*3;
break;
case Movie.CHILDRENS:
result += 1.5;
if(aRental.getDaysRented()>3)
result += (aRental.getDaysRented()-3)*1.5;
break;
}
return result;
}
三、重构第二步:搬移"金额计算"代码
目的:
1、 将函数amountFor()转移到Rental类中,并更名为getCharge()。
2、 更新并替换所有对amountFor()的引用。
重构方法:
Move Method
Change Method signatrue
Inline Method
Inline Temp
方法:
1、选中函数amountFor()的定义,在右键菜单中选择"重构/移动",显示参数设置对话框。把新方法名改成getCharge。按下"确定"按钮,Customer Class中的amountFor()函数被移动到Rental Class中,并更名为:getCharge()。
同时eclipse自动在Customer的amountFor()函数中添加一行对新函数的"委托"代码:
private double amountFor(Rental aRental) {
return aRental.getCharge();
}
这行代码会产生编译错误,原因是amountFor()的private型被传递到了新的方法中:
/**
* @param this
* @return
*/
private double getCharge() {
……
}
2、继续重构!选中getCharge()方法,在右键菜单中选择"重构/更改方法特征符",弹出参数选择对话框,把访问修饰符从private改成public。Eclipse的编译错误提示自动消失。
3、回到Customer类,把所有对amountFor()引用的地方替换成直接对getCharge()的引用。选中Customer类的函数amountFor(Rental aRental),在右键菜单中选择"重构/内联",出现参数选择对话框。
选择"确认"按钮,引用amountFor()的地方被替换成对getCharge()的引用。
public String statement() {
……
double thisAmount = each.getCharge();
……
}
4、除去临时变量thisAmount。
选中变量thisAmount,在右键菜单中选择"重构/内联",重构预览窗口如下,可见达到了重构的目的。按下"确认"按钮重构代码。
statement()代码:
public String statement() {
double totalAmount = 0; // 总消费金额
int frequentRenterPoints = 0; // 常客积点
Enumeration rentals = _rentals.elements();
String result = "Rental Record for " + getName() + "\n";
while(rentals.hasMoreElements()){
Rental each = (Rental)rentals.nextElement(); //取得一笔租借记录
// add frequent renter points(累加 常客积点)
frequentRenterPoints ++;
// add bouns for a two day new release rental
if((each.getMovie().getPriceCode())==Movie.NEW_RELEASE && each.getDaysRented()>1)
frequentRenterPoints ++;
// show figures for this rental(显示此笔租借数据)
result += "\t" + each.getMovie().getTitle() + "\t" +
String.valueOf(each.getCharge()) + "\n";
totalAmount += each.getCharge();
}
// add footer lines(结尾打印)
result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points";
return result;
}
四、重构第三步:提炼"常客积点计算"代码
目的:提取"常客积点计算"代码并放在Rental类中,"常客积点计算"代码如下。
public String statement() {
……
// add frequent renter points
frequentRenterPoints ++;
// add bouns for a two day new release rental
if((each.getMovie().getPriceCode())==Movie.NEW_RELEASE && each.getDaysRented()>1)
frequentRenterPoints ++;
……
}
重构后的代码如下:
frequentRenterPoints += each.getFrequentRenterPoints();
重构方法:
Extract Method
Move Method
Change Method signatrue
Inline Method
方法:
1、 首先,抽取代码到独立的函数中。
用"抽取方法"重构代码,函数名:getFrequentRenterPoints。很遗憾,eclipse的不能生成诸如:frequentRenterPoints += getFrequentRenterPoints(Rental aRental); 的代码。原因是执行自增操作的局部变量frequentRenterPoints要出现在等式右边,因此抽取函数getFrequentRenterPoints()一定要把frequentRenterPoints作为参数。手工修改函数和对函数的引用,重构后的代码如下:
public String statement() {
……
while(rentals.hasMoreElements()){
……
frequentRenterPoints += getFrequentRenterPoints(each);
……
}
……
}
/**
* @param each
* @return
*/
private int getFrequentRenterPoints(Rental each) {
if((each.getMovie().getPriceCode())==Movie.NEW_RELEASE && each.getDaysRented()>1)
return 2;
else
return 1;
}
2、 把getFrequentRenterPoints()移动到Rental类中。
3、 对getFrequentRenterPoints()"更改方法特征符"为public。
4、 对Customer的函数getFrequentRenterPoints()执行内联操作,重构目标完成。
五、重构第四步:去除临时变量(totalAmount和frequentRenterPoints)
目的:去除临时变量(totalAmount和frequentRenterPoints)
方法:
1、 分析totalAmount和frequentRenterPoints的定义和引用结构如下:
// 声明和定义
double totalAmount = 0;
int frequentRenterPoints = 0;
……
// 在循环中修改
while(rentals.hasMoreElements()){
……
frequentRenterPoints += each.getFrequentRenterPoints();
……
totalAmount += each.getCharge();
……
}
……
// 在循环外使用
result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points";
……
上述两个变量在循环体外面定义和使用,在循环中被修改,运用Replace Temp with Query方法去除这两个临时变量是一项稍微复杂的重构。很遗憾,eclipse目前不支持这样的重构。
2、手工修改代码。
六、重构第五步:运用多态取代与价格相关的条件逻辑
目的:
1、 把Rental类中的函数getCharge()移动到Movie类中。
2、 把Rental类中的函数getFrequentRenterPoints()移动到Movie类中。
重构方法:
Move Method
Inline Method
方法:
1、 选中Rental类中的函数getCharge(),右键菜单选中"重构/移动",eclipse提示找不到接收者,不能移动。原因在于这行语句:
switch(getMovie().getPriceCode()){//取得影片出租价格
选中getMovie(),右键菜单选中"重构/内联",确定后代码成为:
switch(_movie.getPriceCode()){ //取得影片出租价格
选中getCharge(),执行"重构/移动"后,函数被移动到Movie类中。然而这只是部分达成了重构目的,我们发现,移动后的代码把Rental作为参数传给了getCharge(),手工修改一下,代码变成:
class Movie ……
/**
* @param this
* @return
*/
public double getCharge(int _daysRented) {
double result = 0;
switch(getPriceCode()){ //取得影片出租价格
case Movie.REGULAR: // 普通片
result += 2;
if(_daysRented>2)
result += (_daysRented-2)*1.5;
break;
case Movie.NEW_RELEASE: // 新片
result += _daysRented*3;
break;
case Movie.CHILDRENS: // 儿童片
result += 1.5;
if(_daysRented>3)
result += (_daysRented-3)*1.5;
break;
}
return result;
}
class Rental……
/**
* @param this
* @return
*/
public double getCharge() {
return _movie.getCharge(_daysRented);
}
2、用同样的步骤处理getFrequentRenterPoints(),重构后的代码:
class Movie ……
/**
* @param frequentRenterPoints
* @param this
* @return
*/
public int getFrequentRenterPoints(int daysRented) {
if((getPriceCode())==Movie.NEW_RELEASE && daysRented>1)
return 2;
else
return 1;
}
class Rental……
/**
* @param frequentRenterPoints
* @param this
* @return
*/
public int getFrequentRenterPoints(int daysRented) {
if((getPriceCode())==Movie.NEW_RELEASE && daysRented>1)
return 2;
else
return 1;
}
七、重构第六步:终于……我们来到继承
目的:对switch语句引入state模式。
方法:
很遗憾,不得不在这里提前结束eclipse的自动重构之旅。Eclipse几乎不能做结构上的重构。也许Martin Fowler在书中呼唤的自动重构工具止于"工具辅助下的重构工作"这一理念。艺术是人类的专利,编程艺术的梦想将持续下去。
感兴趣的读者可以查看手工重构的最后一步代码。将重构进行到底!
附录:eclipse支持的重构方法(摘自eclipse中文帮助)
posted on 2005-11-02 15:42
Sung
阅读(747)
评论(0)
编辑
收藏
所属分类:
Eclipse
新用户注册
刷新评论列表
只有注册用户
登录
后才能发表评论。
网站导航:
博客园
IT新闻
Chat2DB
C++博客
博问
管理
相关文章:
Eclipse中自动重构实现探索
利用Eclipse编辑中文资源文件
Eclipse插件开发之添加简单的GUI元素
Eclipse快速上手指南之使用ANT
在Eclipse中如何利用Maven
测试实践:Eclipse 之 JUnit
利用Eclipse开发Hibernate应用程序
利用Myeclipse快速开发struts应用程序
Eclipse 3.0 简介和插件开发示例
Eclipse的使用简介及插件开发