Day931.六种遗留系统常用的安全重构手法 -系统重构实战

时间:2022-05-09 01:18:25

Hi,我是阿昌,今天学习记录的是关于六种遗留系统常用的安全重构手法的内容。

针对代码的坏味道,也有一些基本的安全重构手法。遗留系统常用的安全重构手法,分别是:

  • 提取变量
  • 提取参数
  • 提取方法
  • 提取接口
  • 移动方法或类
  • Modularize 跨模块移动

安全重构手法就是借助 IDE 自动辅助完成代码的重构,让重构更加高效,同时也可以避免人工挪动代码带来的风险。


一、提取变量

提取变量 是将代码的表达式提取成方法内部变量或者类成员变量。

下面来看一段代码示例,代码中有一个 if 语句,其中有三个条件判断。

void improveReadability() {
    if (platform.toUpperCase().indexOf("Android") > -1 &&
            browser.toUpperCase().indexOf("Chrome") > -1 &&
            pm.equals("com.tencent.mm")) {
    }
}

这段示例代码中的条件判断逻辑相对比较复杂,阅读理解整段含义需要一定的时间。

可以通过提取合适的解释性变量来说明表达式的含义,从而提高代码的可读性。

具体来讲,提取变量的安全重构手法是后面这样。

Day931.六种遗留系统常用的安全重构手法 -系统重构实战

后面是提取变量重构后的代码。

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);
    }
}

对此,通过提取参数的手法进行重构。

Day931.六种遗留系统常用的安全重构手法 -系统重构实战

重构后的代码是后面这样。

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() {
    }
}

这是一段重复代码,里面的判断名称和密码的逻辑是重复的。

这时候,可以使用提取方法来重构。

Day931.六种遗留系统常用的安全重构手法 -系统重构实战

使用提取方法重构后的代码是这样。

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);
}

由于网络的加载库有可能在迭代的过程中被更换,希望代码可以更灵活扩展,显示图片的逻辑不与具体的图片加载框架耦合,对于这种情况,就可以使用提取参数的安全重构手法来解耦。

Day931.六种遗留系统常用的安全重构手法 -系统重构实战

使用提取接口方法重构后的代码是这样。

private IImageLoader imageLoader;
public void show() {
    String url = "http://XXX";
    Bitmap bitmap = imageLoader.getBitmap(url);
    showImage(bitmap);
}

这里将原本依赖具体下载图片的实现,重构为依赖具体的抽象接口,当需要更换下载图片的实现时,只需要注入对应的实现就可以了。

在实践中需要注意,抽取接口也有成本,不仅要定义新的接口类,还要将接口实现注入到调用接口的类中。

通常来说,可以考虑将容易变化或者不稳定的依赖抽取成接口,这样一方面能让程序更容易扩展,另一方面也可以提高代码的可测试性。


五、移动方法或类

这个方法比较简单,在运用自动化工具诊断分析Sharing项目中以新的代码架构组织,代码中已经充分运用移动类的重构手法,只需要使用 IDE 的 MOVE 重构功能就可以安全移动类,自动修改引用该类的 import。对于移动方法来说,则相对比较复杂。

分成两种情况分别讨论:

  • 一种是比较简单的移动静态方法
  • 另外一种是相对比较复杂的移动非静态方法。

移动静态方法相对比较简单,只需要选择方法后使用 MOVE 重构功能,然后选择要移动到的类即可。

Day931.六种遗留系统常用的安全重构手法 -系统重构实战


对于移动非静态方法,IDE 只支持将该方法移动到成员变量的类中,不能随意移动。

同样可以使用 MOVE 的重构功能。

Day931.六种遗留系统常用的安全重构手法 -系统重构实战

这里需要注意,如果代码是使用 Kotlin 编写,目前最新的 Android Studio Chipmunk 还不支持移动方法的功能,系统会提示“无法执行重构”。


六、Modularize 跨模块移动

Modularize 跨模块移动 该功能是组件化重构里的重要功能。

在组件化的过程中,经常需要将一个页面移动到独立的模块中,但是这个页面可能会依赖到其他的类和资源文件,如果一个个靠人工去分析后移动,那么这个工程无疑会非常困难。而 Modularize 跨模块移动能够将一个类及其所依赖的类和资源,一并识别并移动到目标的模块中。


这个功能可以大大减少人工移动代码和资源的效率,下面演示一下如何使用该功能。

这里需要将一个类 Modularize 移动到另外的一个独立模块中,这个类依赖了一个关联的类 ModularizeReationClass 和一个字符串资源,需要一并进行移动,那么具体的操作就是后面这样。

Day931.六种遗留系统常用的安全重构手法 -系统重构实战

在实践的过程中你要注意,当被移动的类的依赖类有被其他文件使用时,用 Modularize 的预览功能预览该文件会有下划线提示,这个时候需要我们先对有下划线提示的文件进行解耦,直至预览功能没有任何的下划线提示时,再确认进行移动。


七、总结

6 种遗留系统常用的安全重构手法,分别是提取变量、提取参数、提取方法、提取接口、移动方法或类以及 Modularize 跨模块移动。

在日常的编码过程中运用这些重构手法,更加高效地优化代码结构,提高代码的质量。

将这些安全重构的手法的定义、作用以及使用步骤总结成思维导图,供参考。

Day931.六种遗留系统常用的安全重构手法 -系统重构实战