第四章:重构代码

优质
小牛编辑
135浏览
2023-12-01

在Android Studio中开发,解决方案不会总是一蹴而成的。作为一个有效率的编程者,在你的开发,调试和测试中需要一些弹性以及代码重构。随着在这章中的行进,你将明白Android Studio如何产生代码;在这章里你将看到Android Studio如何重构你的代码。重构代码最大的风险是可能引入不期望的错误。通过分析某些风险重构操作的结果,Android Studio减低了这些风险,然后激活Find tool窗口,在发行前,你可以预览你的更改-那里标志着任何错误或冲突。

在这章里描述的许多重构操作也可不用Android Studio的重构工具来执行。无论怎样,你必须避免强行重构(例如:通过一个全文的查找/替换操作来重组代码),因为在这些情形下Android Studio不能从引入的错误中一直保护你。相反,如果Android Studio发现你企图一个重构操作,它将尽力避免你犯任何愚蠢错误。例如,在Project tool窗口中拖动一个JAVA源文件从一个包到另一个包,这将强制产Refactor ➤ Move操作,由此会分析你的移动操作结果,允许你预览那些改变,然后适当地改变类中所有的import语句以适应合格的新全包名。

绝大多数的重构操作被限制于一个方法或一个类里面,因而将不太会引入错误到你的项目中。危险的重构操作是那些涉及到两个或以上的资源文件。如果重构操作引入编译错误,检查管理器将在编辑器里用红标签标志那些被影响的资源文件。基于这点,你或者可以企图去修正它们,或者用Ctrl+Z|Cmd+Z简单地取消这个重构操作了。如果重构操作以没有编译错误完成,然而涉及到太多资源文件,那你还是必须要测试及校验你是否引入了任何实时错误。第11章涵盖了测试。

提示:你必须提交任何重要的重构更改,作为单一的Git提交,因此之后你可以轻松地回复那个提交。第7章涵盖了Git。

这章聚焦于用最好的工具来执行重构操作。在我们探访独立的重构操作之前,我们乐于指出Android Studio有一个极其便利的重构工具叫Refactor ➤Refactor This.总在上下文菜单中选择这个,图4-1所示,集成了绝大多数有用的重构操作。快捷键是Ctrl+Alt+Shift+T | Ctrl+T,在PC上你可以方便地记首字母CAST。

图4-1:重构器Refactor This菜单,包括了大部分的有用重构操作。

在你开始本章的例子之前,修改第3章的Sandbox.java文件,它没扩充和包含任何方法或成员变量,象下面的小片段:

public class Sandbox {

}

重命名

在Project tool窗口中选择Sandbox并导航到Refactor➤ Rename或按Shift+F6.引出一个对话框以允许你重命名你的类,另外也可以重命名出现在注释中,测试实例,以及继承类中的那些名字。重命名SandboxPlaypen并点击Refactor按钮,如图4-2所示。你将看到在你项目中重命名的操作。现在请用Ctrl+Z | Cmd+Z取消这个操作。

图4-2:重命名Sandox为Playpen

修改签名

更改签名的操作允许你改变方法的下列属性:可视性,名称,返回类型,参数,以及例外抛出。在Sandbox.java里创建一个方法,如下面的代码片断:

public String greetings(String message){
    return "Hello " + message;
}

将光标移到单词greetings上面(高亮的粗体字)并按Ctrl+F6 |Cmd+F6,或导航到Refactor ➤ Change Signature。结果对话框会允许你修改方法的签名,如图4-3所示。

图4-3:修改签名对话框

在参数表里,点击字符串类型参数message这项,将它的名称从message改为greet,如图4-3所示。图标绿色的+和红色的-允许你分别增加或减少方法的参数。并且,你也可以编辑参数表里面它们的类型和名称。另外,修改当前方法时,你可能决定选择委托重载方法的单选按钮。选择这个按钮将保留你的原始方法不受影响,但会生成另一个你新定义签名的方法。在JAVA里如果你可能考虑一系列有相同的名称重载方法,但参数的顺序和/或参数的类型不同。无论如何,你做的更改并没限制方法的重载。如果你希望的话通过点击预览按钮,可以在提交它们之前预览。按Refactor按钮完成并离开。

类型移植

如标题所述,类型移植允许你从一个JAVA类型移植到另一个。让我们假设你创建了一个Person类。随着你的开发进行,你发现Person太一般了,因而你又创建了一个Manager类扩展于Person。如果你想移植所有的Person实例到Manager,你可以用类型移植轻松办到。

将光标放到greetings方法的String声明上(在下面代码片段中的高亮粗体字)并且按Ctrl+Shift+F6 | Cmd+Shift+F6或选择Refactor ➤ Type Migration。结果对话框如图4-4所示。

public String greetings(String greet){
    return "Hello " + greet;
}

图4-4:类型移植,从Stringdate

更改java.lang.Stringjava.util.Date,如图4-4所示。从选择范围框的下拉菜单里选择打开文件。如同大多数重构操作,通过点击预览按钮,你可预览你的更改。这里我们按Refactor按钮。

移动

你可以通以下三种方法中的一个方法来移动源代码文件。

  • 在Project tool窗口拖动源文件从一个包到另一个包中;
  • 选择源文件从主菜单导航到Refactor ➤ Move
  • 在Project tool窗口选择源文件并按下F6

右键点击(在Mac电脑上Ctrl-click)com.apress.gerber.helloworld包并选择New ➤ Package。给这个重构目标包命名。从Project tool窗口,把sandbox.java拖放到重构目标包里,当出现图4-5对话框时按OK。在Project tool窗口中执行的任何拖放操作将自动产生一个重构移动操作,这个会让你安全地把一个类从一个包移动到别一个包里。

图4-5:由于拖放操作导致的重构移动对话框

除了移动类之外,你也可以移动成员变量。在你的Sandbox.class中,定义一个新成员变量如下:

public static final String HELLO = "Hello Android Studio";

移动光标到这行代码上并按下F6。导致一个对话框允许你移动成员变量从一个类到另一个。如图4-6所示。请点击取消按钮来取消这次操作。

图4-6:移动成员变量的对话框

复制

复制,有点象移动,按快捷键F5或选择主菜单里的Refactor ➤ Copy来访问。在Project tool窗口,选择之前重构的包中的Sandbox.java并按下F5键。在目标包的拖放菜单中选择com.apress.gerber.helloworld包并点击OK,如图4-7所示。象我们这里无差别地复制JAVA源文件不是一个好主意,因为结果不明确且普通存在着错误。

图4-7:复制类的对话框

安全删除

让我们来删除刚才复制的类。在Android Studio的Project tool窗口里用Delete按键,你总可以删除文件和资源。在刚才重构包中点击Sandbox.java文件并按下Delete键。结果对话框允许你使用安全删检查选项来删除。安全删除的先进之处在于执行删除前我们可查询任何因依赖于这个资产而可能导致的破坏,如图4-8所示。如果在这个项目中发任何东西依赖于这个资产,将给出一个可选项浏览它们,或点击无论如何都删除可选项来强制删除。

图4-8:安全删除对话框

析出

析出不是一个操作而是几个。这节涵括了一些重要的析出操作:析出变量,析出常量,析出域,析出参数,和析出方法。在·Sandbox.class·里,让我们先移走所有的成员变量和方法从白纸开始:

public class Sandbox {

}

析出变量

在你的Sandbox.java类里,定义一个方法如下示:

private String saySomething(){
    return "Something";
}

把光标放到硬编码Something的值(粗体字)上并选择Refactor ➤ Extract ➤ Variable,或者按Ctrl+Alt+V | Cmd+Alt+V接着按回车,不要选择声明final的复选框。Android Studio依据硬编码字符串析出一个本地变量并命名它。你将以如下代码告终:

private String saySomething(){
    String something = "Something";
    return something;
}

析出常量

当你开发Android的APP时,会发现你会用很多的字符串作为键-例如,在MapBundle里。因此,析出常量将会节省你大量的时间。

定义一个方法象如下的代码片段。把光标放到name_key字符串上并按Ctrl+Alt+C | Cmd+Alt+C。结果对话框将如图4-9所示。这里,Android Studio提供一些建议的名称。按惯例,常量在JAVA里都应该是大写的。选择NAME_KEY并按回车。 注意:为了创建和处理这个方法而不发生编译错误,你必须导入android.os.Bundle

private void addName(String name, Bundle bundle ){
    bundle.putString("name_key", name);
}

结束时你将看一个常量名称是NAME_KEY,象以下定义的:

public static final String NAME_KEY = "name_key";

图4-9:析出常量NAME_KEY

析出域

析出域将转换一个本地变量到你的类域(a.k.a.成员变量)里。 注意:为了创建和处理这个方法而不发生编译错误,你必须导入android.os.Bundle 在你的Sandbox类里定义一个方法:

private Date getDate(){
    return new Date();
}

把光标放到Date(粗体高亮)上并按键Ctrl+Alt+F | Cmd+Alt+F。你将看到一个对话框如图4-10所示。在Android里,按惯例域(a.k.a. 成员变量)名的头字母用m。你将会注意到一个下拉菜单允许你初始化当前方法中的域,域声明,或构造器。选择域声明并按回车。

图4-10:析出域对话框

结果如下:

private final Date mDate = new Date();
...
private Date getDate(){
    return mDate;
}

移除final关键字,结果这个域声明如下:

private Date mDate = new Date();

析出参数

析出参数允许你析出一个变量并放到封套的方法里。在你的Sandbox类里定义一个方法:

private void setDate(){
    mDate = new Date();
}

将光标放到Date()(粗体高亮)上,按Ctrl+Alt+P | Cmd+Alt+P接着按回车。结果这个方法如下代码片段所示:

private void setDate(Date date){
    mDate = date;
}

析出方法

析出方法让你选择一行或多行行连续的代码放到一个分离的方法中。有两个原因你会想做这个。首先是方法太复杂了。相对于100行的代码,打断它到约10到20行的算法块会比较容易阅读且会较少错误。

再者,重复的代码从来就不是好主意,因而如果你发现了重复的代码,最好析出它们成一个方法并在重复的地方调用它。通过析出之前重复的代码到一个方法里并调用它,你可以保留你的方法在一个地方,如果你想修改它,你只要修改一次。在你的Sandbox类里重建下面的两个方法如清单4-1。你可以自由地复制和粘贴。

清单4-1

private String methodHello (){
    String greet = "Hello";
    StringBuilder stringBuilder = new StringBuilder();
    for(int nC = 0; nC < 10; nC++){
        stringBuilder.append(greet + nC);
    }
    return stringBuilder.toString();
}
private String methodGoodbye (){
    String greet = "Goodbye";
    StringBuilder stringBuilder = new StringBuilder();
    for(int nC = 0; nC < 10; nC++){
        stringBuilder.append(greet + nC);
    }
    return stringBuilder.toString();
}

象我们已经提到过的,任何时候你发现重复一个代码块或复制和粘贴代码块,你就必须考虑析出方法了。选择清单4-1中粗体高亮的块。现在按Ctrl+Alt+M | Cmd+Alt+M析出方法。将呈现一个对话框列出方法的签名。重命名这个方法为getGreet,如图4-11所示,接着点击OK。

图4-11:析出方法对话框

Android Studio扫描你的文件并看到你有另外一个确凿的代码块案例。在处理复制对话框里点击Yes接受建议,如图4-12所示。

图4-12:复制处理对话框

你将看到清单4-2的代码。现在结果方法很容易保留在某个地方。

清单 4-2.从析出方法操作里得出的结果代码

private String methodHello (){
    String greet = "Hello";
    return getGreet(greet);
}
private String methodGoodbye (){
    String greet = "Goodbye";
    return getGreet(greet);
}
private String getGreet (String greet){
    StringBuilder stringBuilder = new StringBuilder();
    for(int nC = 0; nC < 10; nC++){
        stringBuilder.append(greet + nC);
    }
    return stringBuilder.toString();
}

高级重构

整个这章所描述的剩下的其他重构操作是高级别的。如果你有兴趣简单迅速地提高Android Studio,已经有了足够的知识来有效地使用重构操作,也许你可以跳过这节。但无论如何,如果你对JAVA有比较好的理解并且想钻研更多的重构操作,继续看下去吧。

移除Sandbox.java里的所有成员变量及方法,从一张白纸开始。

public class Sandbox {

}

右键点击(Mac电脑用Ctrl+点击)Project tool窗口里的com.apress.gerber.helloworld包,选择New ➤ Java Class。命名这个类为Minibox。修改Minibox类定义继承自Sandbox并且有个成员变量叫做mShovel,如这里所示:

public class Minibox extends Sandbox {
    private String mShovel;
}

推高成员变量以及拉低成员变量

推高成员变量以及拉低成员变量用于继承。注意到我们定义了一个mShovel成员变量在Minibox类里。让我们假设mShovel可能对于其他继承自Sandbox类的子类也有用。这样做,打开Minibox类并选择Refactor ➤ Pull Members Up。结果对话框如图4-13所示。

图 4-13: 推高成员变量对话框

mShovel会被缺省地选中,并且因为SandboxMinibox的父类所以推高成员变量组合窗口会缺省地设定在com.apress.gerber.helloworld.Sandbox类上。点击Refactor,检视SandboxMinibox,你将发现mShovel成员变量不再出现在Minibox里了,而属于Sandbox。作为一般规则,如果你觉得一个成员变量对其他扩展类也是有用的,那么就把这些成员变量在继承层次里推高。用类似的手法可以拉低成员变量。

解除继承并委托成员变量

右键点击(Mac电脑用Ctrl+点击)com.apress.gerber.helloword包选择New ➤ Java Class。命名这个新类为Patio并继承自Sandbox

public class Patio extends Sandbox {

}

进一步分析后,我们决定Patio不是一个Sandbox,但更象是它拥有一个Sandbox。为了改变它们的关系,导航到Refactor ➤ Replace Inheritance with Delegation。在结果对话框里,点击为委托成产生getter方法,如图4-14所示。

图 4-14:解除继承并委托成员变量对话框

现在你的Patio类将拥有一个Sandbox成员变量,如下代码片段所示:

public class Patio {
    private final Sandbox mSandbox = new Sandbox();
    public Sandbox getSandbox() {
        return mSandbox;
    }
}

封装域

封装域是一种面向对象策略,通过使访问级别为私有来隐藏类成员变量并提供以公开的getter/setter方法作为公开的接口。尽管当你选择Refactor ➤ Encapsulate Fields时有很多的选择,但Refactor ➤ Encapsulate Fields类似于Code ➤ Generate ➤ Getter and Setter。打开你的Sandbox类并定义一个成员变量叫mChildren,如下面代码片段中的粗体高亮部分。从主菜单,选择Refactor ➤ Encapsulate Fields

public class Sandbox {
    private String mShovel;
    private int mChildren;
}

结果对话框允许你精确地选择怎样封装这些域以及它们的访问级别。真正地封装域将有一个私有的可视度及公有的访问(getter)及改变(setter)方法。点击Refactor按钮,如图4-15所示,注意到Android Studio在我们的Sandbox.java类里为我们生成所有的gettersetter

图 4-15:封装域对话框

包装方法的返回值类型

当你需要返回一个对象而不是基本数据类型时,包装返回值可能是有用的(当然还有其他的情形你可能想包装一个返回值)。把光标放到getChildren()方法上并导航到Refactor ➤ Wrap Method Return Value。选择Use Existing Class选项,名称框输入java.lang.Integer,包装域框则选择value,如图4-16所示。现在点击Refactor按钮且注意到你的getChildren()方法返回一个Integer类,而不是基本数据类型int

图 4-16: 包装返回值对话框

用工厂方法代替构造器

把光标放在Sandbox类定义附栏内。按Alt+Insert | Cmd+N接着选择生成一个新构造器。选择所有成员变量,如图4-17所示,然后点击OK。

图 4-17:通过构造器来选择域的对话框

将光标放到如下所示代码片段构造器的任何位置,并导航到Refactor ➤ Replace Constructor with Factory Method。结果对话框象图4-18所示。点击Refactor来生成一个工厂方法。

public Sandbox(String shovel, int children) {
    mShovel = shovel;
    mChildren = children;
}

图 4-18: 用工厂模式代替构造器的对话框

注意到之前的构造器现在变成private了并且一个新的静态方法返回Sandbox类的实例,如下面代码片段所示。如果你正创建一个单例类,这将会是非常有用的。

public static Sandbox createSandbox(String shovel, int children) {
    return new Sandbox(shovel, children);
}

转换匿名内部类

在你的Sandbox类的构造器里加上下面行:

new Thread(new Runnable()).start();

把光标放在Runnable()上并按下Alt+Enter来调用完成代码操作。然后选择实现方法。选择run()方法并点击OK。你的代码将象下面片段所示:

new Thread(new Runnable() {
    @Override
    public void run() {
        //do something
    }
}).start();

把光标放在Runnable()上并导航到Refactor ➤ Convert Anonymous to Inner。Android Studio建议MyRunnable作为这个类名,如图4-19所示。去选Make class static选项后点击OK。注意到你将在Sandbox类里得到一个私有的内部类叫MyRunnable,它实现Runnable接口。这个例子没有做更多的事;不管怎样,当委派View的行为时你也许有机会用到这个。

图 4-19: 转换匿名内部类的对话框

小结

这章讨论了Android Studio目前有效的许多重构操作。重构代码是编程工程的一个必需的部分,Android Studio里的重构工具是最好的重构工具之一。Android Studio通过分析推理以及允许你于提交操作前在Find tool窗口预览结果,减低了某些操作执行的风险。最重要的是重构操作Refactor ➤ Refactor This对话框是如此有效的,这个可以通过用快捷键Ctrl+Alt+Shift+T | Ctrl+T来调用。