2.3 代码重构 - 《Eclipse3高级编程》

来源:百度文库 编辑:神马文学网 时间:2024/05/01 12:37:39
2.3 ECLIPSE代码重构
修改已有代码常常需要花费大量时间,而且可能引入新的bug。即使是对某个类型做一下简单的重命名也可能会影响到其他成百上千个编译单元。在这种情况下,让计算机来做比人工来做要好得多,因此Eclipse提供了丰富的重构功能。重构的目的是在不修改应用程序行为的前提下改进代码的结构。尤其在极限编程(Extreme Programming,XP)中,重构扮演着重要的角色。
Eclipse中,可以通过上下文功能Refactor>…或者通过主菜单中的Refactor>…菜单项来进行重构。上下文功能是上下文有关的,即在给定上下文中只有那些可用的功能才可见。Eclipse新手可以从主菜单下的一组Refactor>…功能入手,来了解一下可用功能的概貌。
2.3.1 修改类型
类型级(类和接口)修改最好在包浏览窗口中完成。包浏览窗口的上下文菜单在子菜单Refactor栏下提供了一些重构功能,比如Refactor|Move和Refactor|Rename。另外,使用上下文功能Copy可以创建类型的副本。
●       Moving a compilation unit。如果不满意把类HelloWorld放在项目的默认包下,可以创建一个新的名为HelloPackage的包,然后把类HelloWorld移到该包中。
按通常方式(使用Create a Java Package按钮)新建一个包,然后在包浏览窗口中选择HelloWorld编译单元。从上下文菜单中选择Refactor|Move…功能,出现的对话框中包含了另一个小的包浏览窗口。在这里,单击“+”号展开HelloWorld项目,然后选择HelloPackage包作为移动的目标。单击OK后,HelloWorld编译单元就会移到目标包中去。此时,HelloWorld源代码中就会包含行
package HelloPackage;
如果其他编译单元中包含了对HelloWorld类型的引用,这些引用也会得到相应更新。如果不想这样,可以取消Update References to Moved Element(s)选项。甚至还可以选择更新非Java文件中的引用。
事实上,通过鼠标简单的拖放动作也可以移动编译单元。读者不妨试试把HelloWorld编译单元从默认包拖拽到HelloPackage包。然而,在大型项目中,包包之间的距离可能比较大,此时使用上下文功能Refactor|Move…通常会更方便点。
●       Moving a type。类似地,也可以在编译单元内部移动类型(类和接口)。例如,可以把类的符号(带C的绿色圆圈)拖拽到另一个类的符号上。这样,被拖拽的类将会变成目标类的一个内部类(inner class)。但是,在这种情况下,被拖拽类的原始版本仍然保留在原来的位置,所以这其实是拷贝而不是移动。
●       Renaming compilation units and types。类似地,调用上下文功能Refactor|Rename…可以为编译单元和类型重命名。
图2-7所示对话框说明了如何对一个编译单元进行重命名。除了可以更新代码中的引用外,还可以更新Javadoc注释、普通注释和字符串文字中的引用。

图  2-7
2.3.2 代码重构
除了类和接口,还有许多东西可以进行代码重构。可以从源代码编辑器的上下文菜单、大纲视图的上下文菜单(参见第4章的“大纲视图”节)或者工作台的主菜单中调用这些功能。
1.方法
●       Rename。几乎所有东西都可以通过Refactor|Rename…重命名:类和接口、方法、域、参数和本地变量。对被重命名元素的引用也会作相应更新。如果某域被重命名并且存在针对该域的访问方法(get…()和set…()),方法名称也会得到更新。
●       Move。调用Refactor|Move…功能可以把静态方法(在一定的约束条件下实例方法也可以)移动到其他类中。这些方法的引用也会得到相应更新。公共静态常量(public static final)和内部类也可以被移动。
●       Pull Up。应用Refactor|Pull Up功能可以把非静态方法和域移动到超类中。
●       Change Method Signature。应用Refactor|Change Method Signature功能可以修改方法的访问修饰符和返回类型,参数的阶、名字和类型,以及异常声明。该方法的引用也会得到相应更新。当方法引入新的参数时,有必要为每个新参数定义默认值。当更新相应方法调用时,默认值作为新参数的值插入。
●       Introduce Parameter。该功能用来向方法声明引入新的参数。在方法声明中选择表达式,然后应用该功能。在出现的对话框中,输入新参数的名称。Eclipse就会用该参数的名字替换所选表达式,并在方法头补入新参数,在该方法的所有调用处补入所选表达式。
●       Extract Method。使用Refactor|Extract Method…功能把所选代码封装成一个新的方法定义。Eclipse会为所选代码段作数据流控制分析,并根据分析结果来确定新方法的参数和返回类型。新方法在当前方法之后插入,原方法中所选代码由对应的新方法调用取代。但是,在某些情况下,该功能不能使用,比如所选代码段有多个结果值时。如果该功能不能使用,Eclipse会把拒绝的原因告诉用户。
下面举个例子,在如下方法中选中加粗行,并应用Extract Method功能:
public static void main(String[] args) {
System.out.println("Hello World");
System.out.println("Hello Eclipse");
}
在出现的对话框中,指定新方法的名字为helloEclipse,就会得到如下代码:
public static void main(String[] args) {
System.out.println("Hello World");
helloEclipse();
}
public static void helloEclipse() {
System.out.println("Hello Eclipse");
}
该功能将在当前编译单元中检测能够进行这样替换的、匹配所选代码段的所有出现。可以仅对当前的选择作替换,也可以对所有的匹配出现作替换。
反之,利用Refactor|Inline功能可对方法作相反处理。
2.工厂
●       Introduce Factory。使用Refactor|Introduce Factory…功能可以从给定的构造函数中生成一静态工厂方法。同时,针对该构造函数的所有调用都会被新的工厂方法的调用所取代。
3.类型和类
●       Extract Interface。使用Refactor|Extract Interface…功能可以为已有类生成相应接口。例如,选择类名HelloWorld并调用该功能,此时需要为新的接口指定名字。输入IHelloWorld然后单击OK,就会生成Java接口IHelloWorld,并且HelloWorld类的定义中会补入implements IHelloWorld语句。同时,Eclipse还会确定哪些对HelloWorld的引用需要替换成对接口IHelloWorld的引用。本例中生成的接口将会为空,因为类HelloWorld中只有静态方法。
●       Generalize Type。选择类型名字然后调用该功能,就会出现对话框显示超类型的层次结构。可以从树中选择一个类型来取代最初选中的类型名字。
●       Use Supertype。创建接口IHelloWorld后,针对类HelloWorld调用Refactor|Use Supertype Where Possible功能,就会出现包含类型IHelloWorld和Object的候选列表,两者都是HelloWorld的超类型。如果选择IHelloWorld,Eclipse会把对HelloWorld的所有引用都替换为对IHelloWorld的引用,前提是这样做不会产生编译错误。
●       Convert Nested Type to Top Level。针对内部类和接口,应用Refactor| Convert Nested Type to Top Level…方法可以将其分离出来,并放入到各自的编译单元(.java文件)中去。新的编译单元会自动补入必要的import语句。先前包含内部类型的类型定义中,会生成新的类的域,其类型声明为新生成的顶级类型。另外,容器类型的构造函数会添加一个新的参数,以向新的域提供一个新顶级类型的实例。
●       Convert Anonymous Type to Nested Type。匿名类常用作事件监听器(event listener)。使用Refactor|Convert Anonymous to Nested…功能可以把匿名类转换为命名的内部类。
4.变量
●       Extract Local Variable。使用Refactor|Extract Local Variable…功能可以把所选表达式替换为一个新变量的名字。在被修改的表达式之前会插入合适的变量赋值语句。例如,在
System.out.println("Hello World");
中选择HelloWorld应用该功能,在出现的对话框中,指定变量名为hi。结果得:
String hi = "Hello World";
System.out.println(hi);
可以选择把HelloWorld的所有出现替换为对变量hi的引用。
●       Inline method or local variable。Refactor|Inline…功能正好相反。例如,如果选择变量hi应用该功能,hi的所有出现就会被hi的值(字符串Hello World)所替换。在替换之前,会出现一对话框比较编译单元的新老版本(参见“本地历史记录”一节)间差异来展示替换效果。类似地,选择方法名然后调用该功能可以针对方法作相应处理。
●       Encapsulate。使用Refactor|Self Encapsulate…功能可以把公共变量转换为私有变量,并为该变量生成访问方法(也可参见“封装域”一节的Generate Getter and Setter功能),同时会对该变量的所有读写访问作相应更新。
之前:
public int status;
public void process() {
switch (status) {
System.out.println("Status 0");
break;
}
}
之后:
private int status;
public void process() {
switch (getStatus()) {
case 0 :
System.out.println("Status 0");
break;
}
}
public void setStatus(int status) {
this.status = status;
}
public int getStatus() {
return status;
}
●       Convert Local Variable to Field。使用Refactor| Convert Local Variable to Field功能可以把方法体中定义的本地变量转换成实例域。
5.常量
●       Extract/Inline Constant。针对变量讨论的抽取和内联功能,对于常量也同样适合。例如,选中字符串Hello World,调用Refactor|Extract Constant…功能,在出现的对话框中,指定新的常量名为HELLOWORLD。Eclipse就会插入如下行
private static final String HELLOWORLD = "Hello World";
并会把Hello World的所有出现替换为HELLOWORLD。反之,调用Refactor|Inline…功能可以把常量名替换为常量值。