Hi,我是阿昌
,今天学习记录的是关于六种遗留系统常用的安全重构手法
的内容。
针对代码的坏味道,也有一些基本的安全重构手法
。遗留系统常用的安全重构手法,分别是:
- 提取变量
- 提取参数
- 提取方法
- 提取接口
- 移动方法或类
- Modularize 跨模块移动
安全重构手法就是借助 IDE 自动辅助
完成代码的重构,让重构更加高效,同时也可以避免人工挪动代码带来的风险。
一、提取变量
提取变量 是将代码的表达式提取成方法内部变量或者类成员变量。
下面来看一段代码示例,代码中有一个 if 语句,其中有三个条件判断。
void improveReadability() {
if (platform.toUpperCase().indexOf("Android") > -1 &&
browser.toUpperCase().indexOf("Chrome") > -1 &&
pm.equals("com.tencent.mm")) {
}
}
这段示例代码中的条件判断逻辑相对比较复杂,阅读理解整段含义需要一定的时间。
可以通过提取合适的解释性变量来说明表达式的含义,从而提高代码的可读性。
具体来讲,提取变量的安全重构手法是后面这样。
后面是提取变量重构后的代码。
void improveReadability() {
boolean isAndroid = platform.toUpperCase().indexOf("Android") > -1;
boolean isChrome = browser.toUpperCase().indexOf("Chrome") > -1;
boolean isInstallWeiXin = pm.equals("com.tencent.mm");
if (isAndroid && isChrome && isInstallWeiXin) {
}
}
可以看出,重构代码提取出 3 个解释性变量,提高了代码的可读性,能马上看出这个条件判断语句是在判断:
字符串标识是否属于 Android 平台以及 Chrome 浏览器并且安装了微信?
在实践中需要注意,取一个“名副其实”的命名非常重要
,要避免使用缩写或者“词不达意”的命名方式。
二、提取参数
提取参数 是将方法内部的表达式结果提取成方法的参数。
可以通过提取参数对变量的依赖进行解耦,提高代码的可测试性。
结合一段代码示例来研究。该示例方法依赖了一个 userId 的变量,这个变量需要从数据库中获取,这样会降低代码的可测试性。
void improveTestability() {
String userId = UserDao.getId();
if (valid(userId)) {
Log.d("id", "improveTestability: " + userId);
}
}
对此,通过提取参数的手法进行重构。
重构后的代码是后面这样。
void improveTestability(String userId) {
if (valid(userId)) {
Log.d("id", "improveTestability: " + userId);
}
}
可以看到,重构以后该方法不依赖具体的数据库实现,可以通过传递参数来测试方法内部的逻辑。但在实践过程中需要注意,参数应该是越少越好,如果参数过多就要考虑封装成对象
。
另外,参数的顺序最好按照方法内部使用的顺序进行排列,这样阅读代码更方便。
三、提取方法
提取方法 是将代码的表达式提取成独立的方法。
可以通过提取方法来减少重复代码
,同时提高代码的可维护性
。
下面来看一段代码示例:
public class ExtractMethod {
String name;
String password;
public void login(){
if(name == null){
return;
}
if(password == null){
return;
}
accountLogin();
}
public void Register(){
if(name == null){
return;
}
if(password == null){
return;
}
accountRegister();
}
private void accountLogin() {
}
private void accountRegister() {
}
}
这是一段重复代码,里面的判断名称和密码的逻辑是重复的。
这时候,可以使用提取方法来重构。
使用提取方法重构后的代码是这样。
public void login(){
if (isInValid()) return;
accountLogin();
}
public void Register(){
if (isInValid()) return;
accountRegister();
}
private boolean isInValid() {
if (name == null) {
return true;
}
if (password == null) {
return true;
}
return false;
}
提取方法后,不仅减少了重复代码,还给判断用户名及密码合法性取了一个合适的名称 “isInValid” 来表达这个方法的含义,这样也可以提高代码的可读性
。
这里首先要注意命名的问题,其次需要控制方法的大小。如果一个方法的行数超过了 50 行
(超过一屏的大小),就建议做进一步的拆分。
当然,如果能将方法控制在 10 行左右,那就更好了。
四、提取接口
提取接口 是将类中的方法提取为接口方法,让原本依赖这个类的类变成依赖提取的接口。
通常在重构时,还会先将一些表达式提取为方法,然后再提取成接口。
提取接口可以让依赖行为更稳定
,从依赖具体的实现变成依赖具体的抽象接口,而且这样重构后的代码更容易扩展。
在显示图片时,需要通过一个网络加载的库下载网络图片,下载完成后显示到界面上。
public void show() {
String url = "http://XXX";
Bitmap bitmap = new Picasso().load(url);
showImage(bitmap);
}
由于网络的加载库有可能在迭代的过程中被更换,希望代码可以更灵活扩展,显示图片的逻辑不与具体的图片加载框架耦合,对于这种情况,就可以使用提取参数的安全重构手法来解耦。
使用提取接口方法重构后的代码是这样。
private IImageLoader imageLoader;
public void show() {
String url = "http://XXX";
Bitmap bitmap = imageLoader.getBitmap(url);
showImage(bitmap);
}
这里将原本依赖具体下载图片的实现,重构为依赖具体的抽象接口,当需要更换下载图片的实现时,只需要注入对应的实现就可以了。
在实践中需要注意,抽取接口也有成本,不仅要定义新的接口类,还要将接口实现注入到调用接口的类中。
通常来说,可以考虑将容易变化或者不稳定的依赖抽取成接口,这样一方面能让程序更容易扩展,另一方面也可以提高代码的可测试性。
五、移动方法或类
这个方法比较简单,在运用自动化工具诊断分析Sharing项目中以新的代码架构组织,代码中已经充分运用移动类的重构手法,只需要使用 IDE 的 MOVE 重构功能就可以安全移动类,自动修改引用该类的 import。对于移动方法来说,则相对比较复杂。
分成两种情况分别讨论:
- 一种是比较简单的移动静态方法
- 另外一种是相对比较复杂的移动非静态方法。
移动静态方法
相对比较简单,只需要选择方法后使用 MOVE 重构功能
,然后选择要移动到的类即可。
对于移动非静态方法
,IDE 只支持将该方法移动到成员变量的类中,不能随意移动。
同样可以使用 MOVE 的重构功能。
这里需要注意,如果代码是使用 Kotlin 编写
,目前最新的 Android Studio Chipmunk 还不支持移动方法的功能,系统会提示“无法执行重构”。
六、Modularize 跨模块移动
Modularize 跨模块移动 该功能是组件化重构里的重要功能。
在组件化的过程中,经常需要将一个页面移动到独立的模块中,但是这个页面可能会依赖到其他的类和资源文件,如果一个个靠人工去分析后移动,那么这个工程无疑会非常困难。而 Modularize 跨模块移动能够将一个类及其所依赖的类和资源,一并识别并移动到目标的模块中。
这个功能可以大大减少人工移动代码和资源的效率,下面演示一下如何使用该功能。
这里需要将一个类 Modularize 移动到另外的一个独立模块中,这个类依赖了一个关联的类 ModularizeReationClass 和一个字符串资源,需要一并进行移动,那么具体的操作就是后面这样。
在实践的过程中你要注意,当被移动的类的依赖类有被其他文件使用时,用 Modularize 的预览功能预览该文件会有下划线提示,这个时候需要我们先对有下划线提示的文件进行解耦,直至预览功能没有任何的下划线提示时,再确认进行移动。
七、总结
6 种遗留系统常用的安全重构手法,分别是提取变量、提取参数、提取方法、提取接口、移动方法或类以及 Modularize 跨模块移动。
在日常的编码过程中运用这些重构手法,更加高效地优化代码结构,提高代码的质量。
将这些安全重构的手法的定义、作用以及使用步骤总结成思维导图,供参考。