为什么要重构?
好的软件除了满足功能需求之外,还应从长期维护的角度来考虑可读性、可扩展性等。没有明显问题,不代表明显没有问题。
然而,随着时间的推移,任何原本设计良好的代码都必然会逐渐腐坏,产生各种代码坏味道,变得难以理解,难以维护,缺陷频出。
随着代码质量下降,问题越来越多,程序员压力也越来越大,而士气也会受到影响,甚至原本的设计队伍都不在了。因而当需要修改bug或添加新需求时,人们更倾向于采用快速的打补丁方式来维护本已不堪的代码,致使代码质量进一步下降,从而造成更多的代码坏味道(Code Smell),形成恶性循环。
采用重构以后,可以有助于改善软件设计,使软件更加容易理解,发现潜在缺陷,从而使未来的编码更加快速。
什么是重构?
重构是指在保持程序的全部功能的基础上重新组织代码结构,以更好地适应将来的变化。重构的类型有很多,如更改类名,改变方法名,或者提取代码到方法中。每一次重构,都要执行一系列的步骤,这些步骤要保证代码和原代码相一致。
如何重构?
在重构时,测试是十分重要的。因为重构改变了代码的结构,你要保证重构后代码的功能没有被改变。手工重构时,很容易在代码中引入错误,例如拼写错误或者漏掉了重构的某一步。为了防止引入错误,在每次重构前后,都要执行充分的测试。
如果使用自动重构工具,测试也是有必要的,但不需要很频繁,因为自动重构工具不会产生手工重构时的那些错误,如拼写错误。
Eclipse、VisualStudio、IntelliJ、XCode等IDE都提供了自动重构工具,能够对相应编程语言的项目、类或成员进行多种类型的自动重构。
为某些元素进行重构的前提是你必须选中他们。你可以在多个视图中选择这些元素,像大纲视图或包浏览视图。可以按住Ctrl或Shift键,在视图中选择多个 元素。另外一种选择的方法是使该元素的编辑区高亮显示,或者把鼠标定位到源程序文件。在选中希望重构的元素后,可以从重构菜单的下拉项选择重构,也可以从右键单击后弹出菜单中选择重构子菜单。同时,各个IDE也都提供了重构的快捷键操作。
某些重构可以应用在任意元素上,有些则只能用在特定类型的元素上,如类或方法。
重构类型
下面以Eclipse为例,其他IDE大同小异。
看一下Eclipse的重构菜单,一般可以看到提供了三种类型的重构。
类型 | 手段 |
---|---|
物理结构 | Rename 重命名 |
Move 搬移代码 | |
Change Method signature 修改方法签名 | |
Convert Member Type to New File 将嵌套类转换为单独类 | |
类层次结构 | Push Down 下推 |
Push Up 上移 | |
Extract Interface 抽取接口 | |
Generalize Type 类型泛化 | |
类内部结构 | Inline 内联 |
Extract Method 抽取方法 | |
Extract Local Variable 抽取本地变量 | |
Extract Constant 抽取常量 | |
Introduce Parameter 引入参数 | |
Introduce Factory 引入工厂 | |
Convert Local Variable to Field 将本地变量转为类成员 | |
Encapsulate Field 封装成员变量 |
每种重构的目的和用法
Rename
Rename用来改变一个元素的名字。虽然你可以手工改变元素的名字,但是这样不能自动更新所有引用它们的文件或元素。你必须在项目中搜索文件然后手工替换这些引用。很可能你就会漏掉一个或者改错一个。Rename重构会智能的更新所有有此引用的地方。
有时候,元素的名字不是很明了,或者它的功能已经改变了。为了保持代码的可读性,该元素的名字也要更新。使用Rename重构,能够十分快捷的更新元素的名字和所有引用它的地方。
要为一个元素改名,在包浏览视图或大纲视图选中该元素,从重构菜单中选择Rename项,或者使用快捷键。Rename对话框会出现。在这里添入新的名字,选择是否更新该元素的引用。点击回车,重构生效。
Move
Move和Rename很相似。它用来把元素从一个位置移动到另一个位置。它主要用来将类从一个包移动到另一个包。选中要移动的元素,从重构菜单中选择 Move,或者使用快捷键,在弹出窗口中选择要移动的目的地。你仍然可以用预览功能检查一下有什么改变,也可以按OK按钮直接让其生效。
Change Method Signature
更改方法签名能够改变参数名,参数类型,参数顺序,返回类型,以及方法的可见性。也可以添加、删除参数。
要执行此重构,选择要重构的方法,选中重构菜单的更改方法签名项,会出现更改方法签名对话框。
在此对话框中选择方法的修饰词,返回类型,参数。参数的添加、修改、移动,删除可以通过右边的按钮控制。当添加新的参数时,会自动赋予默认值。凡是调用此方法的地方都会用此默认值作为参数输入。
改变方法签名可能在方法中导致问题,如果有问题,当你点击预览或OK时,会被标记出来。
Move Members Type to New File
此重构将嵌套类转为一个单独类。将会创建一个新的文件包含此嵌套类。选中要重构的类,在重构菜单上选择Move Member Type to New File项,在弹出的对话框中添入要创建的实例的名字。
Push Down
此重构将算中的方法和成员从父类中移动到它的直接子类中,所有下推的方法都可选作为一个抽象方法留在父类中。下推重构对于重新构建项目设计十分有用。
选择若干方法或成员,从重构菜单中选择下推项,弹出下推对话框。
在此对话框中,可以分别选择方法或成员,所有选中元素都会移动到当前类的子类中。当点击Add Required按钮时,所有已选择元素所必需的元素也会自动选上,此行为并不能保证所有必须的元素都能自动选中,还是需要人工确认。当有方法被选中时, 编辑按钮就会可用,点击编辑按钮,弹出编辑对话框。在其中可以选择为选中方法在当前类中遗留抽象方法,还是在当前类中删除这些方法。双击一天选中的方法, 也可以打开编辑对话框。在方法的Action列点击,会出现一个下拉列表,可以在其中选择遗留抽象方法还是在当前类中删除方法。按回车键确认编辑结果。
Pull Up
上移与下推类似,也是在类之间移动方法和成员。上移将方法或成员从一个类移动到它的一个父类中。选中若干个方法或成员,在重构菜单中选择上移项,上移向导马上会出现。
在选择目标类多选框中,列出了当前类继承的所有父类。你只能将方法或成员移动到它们其中的一个里面。
如果在选中方法的Action列,被设置成在目标类中声明抽象方法,那么在目标类的非抽象子类中创建必须的方法选项变为可选。当它选中时,目标类的所有子类,如果它们中没有选中的方法,则会为它们创建选中的方法。
和在下推中一样,选择多个方法,点击编辑按钮,或者双击一个方法,都会打开编辑成员对话框。其中有两个选项,上移和在目标类中声明抽象方法。上移只是简单 的复制方法到到父类中,并提供选择是否在当前类中删除该方法。在目标类中声明抽象方法会在父类中创建一个选中方法的抽象方法,如果父类不是抽象类则置为抽 象类,最后选中方法留在当前类中。和在下推中一样,也可以点击Action列,可以在出现的下拉列表中选择。
如果方法的Action列选为上移,在下一步的向导中,将会要求你选择是否在当前类中删除这些方法,选中的方法会在当前类中被删除。
在向导的任意一步都可以按完成按钮,结束重构操作,此时按照默认规则进行重构。
Extract Interface
抽取接口可以从一个存在的类中创建一个接口。你可以选择在接口中包含着个类的那些方法。选中一个类,从重构菜单选择抽取接口项,就可以打开抽取接口对话框。
这此对话框中添入接口的名字,选择希望包含的方法,在这个列表里面只列出了公共方法。选中改变对类[当前类名]的应用为对接口的引用选择框,将把所有对当前类的引用更新为对此接口的引用。
Generalize Type
泛化类型重构可以将一个声明对象的类型改变为它的超类,选择变量,参数,对象成员,方法返回类型,然后选择重构菜单的泛化类型项。在打开的泛化类型对话框,选择希望的新类型,然后点击完成按钮,结束重构。
Use Supertype Where Possible
使用超类会将对一个特定类型的引用改变为对它的超类的引用。选择一个类,选中重构菜单的使用超类项,会打开使用超类对话框。选中希望的超类类型,点击完成按钮完成重构。重构后,instanceof 表达式也会做相应的替换。
Inline
内联是用代码或值来取代调用方法的地方,静态final对象成员,或局部变量。比如说,如果你内联一个方法调用,这个调用的地方就会被替换为该方法体。要 内联一个方法,静态final对象成员,局部变量,选中这些元素,在重构菜单中选择内联项,或者使用快捷键。在随后打开的内联对话框,你可以选择是否要内联所有的调用,或者是选择的调用。如果选择所有调用,你还可以选择是否删除声明本身。
Extract Method
如果方法中含有过多特定的操作,方法太长,或者其中的某段代码被多次使用,这时,可以用抽取方法重构将这部分代码提取到单独的方法中。在Eclipse中应用此重构方便快捷。
选中要抽取的代码段,从重构菜单中选择抽取方法项,或者使用快捷键。
在抽取方法对话框中,输入新方法的名字,选择修饰词,选择是否让新方法抛出运行时异常。在底部提供了新方法的预览。
Extract Local Variable
使用一个变量来代替一个表达式有很多好处。如果表达式在多处被使用,这样能够提高性能,而且也提高了代码的可读性。要把一个表达式抽取为局部变量,选择要抽取的表达式,从重构菜单中选择抽取局部变量项,或者使用快捷键。
在抽取局部变量对话框中输入新变量的名字,选择是否要替换所有的表达式,是否使此变量为final。在对话框的底部提供变量的预览。
Extract Constant
抽取常量与抽取局部变量很相似,唯一的区别是抽取常量重构可以选择抽取出的常量的修饰词,而且此常量将作为类的成员变量。
Introduce Parameter
引入参数重构在方法中创建新的参数,然后用此新参数取代局部变量或者成员变量的实例。要是用此重构,选中方法中一个成员变量或局部变量的引用,然后从重构菜单中选择介绍参数项。
Introduce Factory
工厂是用来创建新对象,返回新创建对象的方法。你可以选择一个类的构造方法,从重构菜单中选择介绍工厂项,应用此重构,为此类创建工厂方法。
在介绍工厂对话框,输入工厂方法的名字和需要工厂方法创建的对象的名字。选择构造方法的修饰词是否为私有。
点击OK按钮后,在指定的类中会出现此指定工厂方法。此方法创建一个当前类的实例,然后返回此实例。
Convert Local Variable to Field
转换局部变量为成员变量重构,将方法内的变量声明移动到方法所在类中,使该变量对整个类可见。选择一个局部变量,从重构菜单中选择转换局部变量为成员变量项,随后打开配置的对话框。
在此对话框中,添入成员变量的名字,选择修饰词,选择在哪里实例化此成员变量。随后的声明为静态,声明为final 选择项是否可以使用,取决于实例化位置的选择情况。
Encapsulate Field
要正确的实践面向对象编程,应该将成员变量的修饰词置为私有,提供相应的访问器来访问这些成员变量。但是这些操作很烦琐。如果使用了封装成员变量重构,则十分方便。选择一个成员变量,从重构菜单中选择封装成员变量项。
在封装局部变量对话框中,添入Getter, Setter方法的名字,选择新方法在哪个方法后出现。选择合适的修饰词。应用了此重构会创建两个新方法,将此成员变量的修饰词置为私有,将对此成员变量的引用改变为对新方法的引用。
参考阅读: