当前位置: 首页 > news >正文

Idea中重构从精通到陌生

Idea中重构从精通到陌生

为什么要重构,因为我们总是不可能在一开始就写出简洁的代码
为了类更小,函数更小,越小,意味着它所承担的少,求求你了,给代码减负吧!!(甘哥你在看吗?
如何借助 idea来实现快速重构
请添加图片描述

函数

提炼函数

一个函数几百行,需要依靠注释才能看懂,这种时候我们就要用 大名鼎鼎的提炼函数,在代码整洁之道这样说:阅读一段好的代码就像看看报一样!

例子

鼠标选中,右键

public class ExtractMethodExampleOriginal {
    private String _name;
    private Hashtable orders;

    void printOwing() {
        Enumeration e = orders.elements();
        double outstanding = 0.0;
        //打印横幅
        System.out.println("************************************");
        System.out.println("***********Customer Owes***********");
        System.out.println("************************************");
        //计算未完成
        while (e.hasMoreElements()) {
            Order each = (Order) e.nextElement();
            outstanding += each.getAmount();
        }
        //打印详细信息
        System.out.println("name:" + _name);
        System.out.println("amount" + outstanding);
    }
}

idea技巧

在这里插入图片描述

重构后

public class ExtractMethodExampleOriginal2 {
    private String _name;
    private Hashtable orders;

    void printOwing() {
        printBanner();
        double outstanding = calculateOutstanding();
        printDetails(outstanding);
    }

    private void printDetails(double outstanding) {
        System.out.println("name:" + _name);
        System.out.println("amount" + outstanding);
    }

    private double calculateOutstanding() {
        double outstanding = 0.0;
        while (true) {
            Enumeration elements = orders.elements();
            if (!elements.hasMoreElements()) break;
            Order each = (Order) elements.nextElement();
            outstanding += each.getAmount();
        }
        return outstanding;
    }

    private static void printBanner() {
        System.out.println("************************************");
        System.out.println("***********Customer Owes***********");
        System.out.println("************************************");
    }
}

内联函数&内联变量

当函数代码和名称一样好懂时,需要内联,一个临时变量被简单的赋值了一次,需要内联,,刚刚把代码提出去,现在又放回去了=。=

例子:

isCurrentOutstandingBiggerThanZero方法的内容是 getCurrentOutstanding() > 0 ;我一看就知道是大于0啊,反复套娃是吧!这时候就要内联函数

public class InlineMethodAndTempExampleOriginal {
    double calcPreviousOutstanding() {
        return isCurrentOutstandingBiggerThanZero() ? 2 : 1;
    }

    double calcAfterOutstanding() {
        return isCurrentOutstandingBiggerThanZero() ? 20 : 10;
    }

    boolean isCurrentOutstandingBiggerThanZero() {
        return getCurrentOutstanding() > 0;
    }

    double getCurrentOutstanding() {
        return 10;
    }
}

idea操作

选中方法,右键重构,选择
内联操作(Ctrl+Alt+N) 内联方法和内联变量的快捷键都是Ctrl+Alt+N

重构后

    double calcPreviousOutstanding() {
        return getCurrentOutstanding() > 0 ? 2 : 1;
    }

    double calcAfterOutstanding() {
        return getCurrentOutstanding() > 0 ? 20 : 10;
    }

    double getCurrentOutstanding() {
        return 10;
    }

已查询取代临时变量

就是将一个临时变量的计算过程,放到一个函数中。然后变量去引用函数。
这样提有什么好处?
1.是扩大了范围,本来临时变量是只在函数内可见。
2.更容易提方法

上链接(例子)

public class ReplaceTempWithQueryExampleOriginal {             
    private int quantity;                                      
    private int itemPrice;                                     
                                                               
    public double calcPrice() {                                
        int basePrice = quantity * itemPrice;                  
        double discountFactor;                                 
        if (basePrice > 1000) {                                
            discountFactor = 0.95;                             
        } else {                                               
            discountFactor = 0.98;                             
        }                                                      
        return basePrice * discountFactor;                     
    }                                                          
}                                                              

步骤

1.首先将 quantity * itemPrice 提取函数
2. 选中 提取函数

   double discountFactor;
        if (basePrice > 1000) {
            discountFactor = 0.95;
        } else {
            discountFactor = 0.98;
        }

3.这时候 是不是 发现discountFactor 只被用了一次,一看到这种情况,DNA就触动了,TM直接将 discountFactor 内联变量,

    public double calcPrice() {
        int basePrice = getBasePrice();
        double discountFactor = getDiscountFactor(basePrice);
        return basePrice * discountFactor;
    }
    private static double getDiscountFactor(int basePrice) {
        double discountFactor;
        if (basePrice > 1000) {
            discountFactor = 0.95;
        } else {
            discountFactor = 0.98;
        }
        return discountFactor;
    }

重构后

public class ReplaceTempWithQueryExample2 {
    private int quantity;
    private int itemPrice;

    public double calcPrice() {
        int basePrice = getBasePrice();
        return basePrice * getDiscountFactor(basePrice);
    }

    private static double getDiscountFactor(int basePrice) {
        double discountFactor;
        if (basePrice > 1000) {
            discountFactor = 0.95;
        } else {
            discountFactor = 0.98;
        }
        return discountFactor;
    }

    private int getBasePrice() {
        return quantity * itemPrice;
    }


}

引入解释性变量

当你有一个复杂的表达式时,将它的结果或者其中一部分的结果放进一个临时变量

例子

  private void introduceVariableConditionalMethod() {
        if (platform.toUpperCase().indexOf("MAC") > -1 &&
                browser.toUpperCase().indexOf("IE") > -1 && isInitialized() && resize > 0) {
            //do something
        }
    }

    private double calcPrice() {
        return quantity * itemPrice - Math.max(0, quantity - 500) * itemPrice * 0.05 + Math.min(quantity * itemPrice * 0.1, 100.0);
    }

    private boolean isInitialized() {
        return false;
    }

技巧

上面introduceVariableConditionalMethod函数中的if中有 大量的一眼看过去不知道意思的,这时候就要将一个个表达式的结果用 临时变量保存,专业术语叫 引入解释性变量(Ctrl+Alt+V),干就完事了,兄弟们!!
1.我们先对introduceVariableConditionalMethod函数下手

  private void introduceVariableConditionalMethod() {
        boolean isMacos = platform.toUpperCase().indexOf("MAC") > -1;
        boolean isIe = browser.toUpperCase().indexOf("IE") > -1;
        boolean wasResized = resize > 0;
        if (isMacos && isIe && isInitialized() && wasResized) {
            //do something
        }
    }

2.calcPrice函数,我k这么长,这谁写的代码!我写的,那没事了,哈哈哈哈!!

private double calcPrice() {
    return quantity * itemPrice - Math.max(0, quantity - 500) * itemPrice * 0.05 + Math.min(quantity * itemPrice * 0.1, 100.0);
}

引入解释性变量 这样有助于我们理解,下面代码是不是一目了然

    private double calcPrice() {
        int totalPrice = quantity * itemPrice;
        double discount = Math.max(0, quantity - 500) * itemPrice * 0.05;
        double shippingCost = Math.min(quantity * itemPrice * 0.1, 100.0);
        return totalPrice - discount + shippingCost;
    }

其实到这已经可以了,不过还记得上面我们说了啥吗?
难道是以查询来取代临时变量,没错,不过我就不写,就是懒。
使用引入解释性变量 还有助于提取函数(以查询来取代临时变量本质上就是提取函数,不要说你没看出来)

重构后

      private void introduceVariableConditionalMethod() {
        if (platform.toUpperCase().indexOf("MAC") > -1 &&
                browser.toUpperCase().indexOf("IE") > -1 && isInitialized() && resize > 0) {
            //do something
        }
    }

    private double calcPrice() {
        int basePrice = quantity * itemPrice;
        double discount = Math.max(0, quantity - 500) * itemPrice * 0.05;
        double shippingCost = Math.min(quantity * itemPrice * 0.1, 100.0);
        return basePrice - discount + shippingCost;
    }

    private boolean isInitialized() {
        return false;
    }

分解临时变量

当有一个临时变量,被多次赋值(一个变量被承担了多个责任),但它既不是循环变量,也不是计算结果。我从业这么多年还未见到,有人这样写!

例子

    double calcDistanceTravelled(int time) {
        double result;
        // 加速度  /力除以质量
        double acc = primaryForce / mass;
        int primaryTime = Math.min(time, initPrimaryTime);
        result = 0.5 * acc * primaryTime * primaryTime;
        int secondaryTime = time - initPrimaryTime;
        if (secondaryTime > 0) {
            double primaryVel = acc * initPrimaryTime;
            acc = (primaryForce + secondaryForce) / mass;
            result += primaryVel * secondaryTime + 0.5 * acc * secondaryTime * secondaryTime;
        }
        return result;
    }

技巧

例子中可以看出acc(加速度)被赋值多次,
1.先将acc第二次 赋值改为二次加速度

2.然后将第一次acc 使用 shift+F6 可以快速将acc 全部重命名。
当然这份代码还能优化~

重构后

    double calcDistanceTravelled(int time) {
        double result;
        // 加速度  /力除以质量
        double primaryAcc = primaryForce / mass;
        int primaryTime = Math.min(time, initPrimaryTime);
        result = 0.5 * primaryAcc * primaryTime * primaryTime;
        int secondaryTime = time - initPrimaryTime;
        if (secondaryTime > 0) {
            double primaryVel = primaryAcc * initPrimaryTime;
            double secondaryAcc = (primaryForce + secondaryForce) / mass;
            result += primaryVel * secondaryTime + 0.5 * secondaryAcc * secondaryTime * secondaryTime;
        }
        return result;
    }

移除对参数的赋值

代码中对入参赋值

例子

    public int changeSimpleTypeParameterValueAndReturn(int inputVal) {
        if (inputVal < 50) {
            inputVal = inputVal * 2;
        }
        if (inputVal > 60) {
            inputVal = inputVal + 1;
        }
        return inputVal;
    }

技巧

用一个临时变量取代该参数
注意java 中是按值传递,对参数赋值是无意义的
那没有意义,那我直接复用这个变量不行吗?
我看你之前说说的都忘了,分解临时变量,一个变量被多次赋值,一个人承担多个责任不累吗?

重构后

    public int changeSimpleTypeParameterValueAndReturn(int inputVal) {
        int result=inputVal;
        if (inputVal < 50) {
            result = inputVal * 2;
        }
        if (inputVal > 60) {
            result = inputVal + 1;
        }
        return result;
    }

以函数对象取代函数

当你有一个大型函数,局部变量很多,提取函数变得很困难, 就是将函数放到一个对象中,其中的局部变量变为对象的字段,这样就更容易的将大型函数拆成多个 小的

例子

还没看到合适的

替换算法

将原本不容易理解的算法替换成成一个更清晰的算法

例子

    public String findPerson(String[] people) {
        for (int i = 0; i < people.length; i++) {
            if (people[i].equals("Tom")) {
                return "Tom";
            }
            if (people[i].equals("Sam")) {
                return "Sam";
            }
            if (people[i].equals("Tim")) {
                return "Tim";
            }
        }
        return "";
    }

技巧

啊这,没啥说的

重构后

    public String findPerson(String[] people) {
        List<String> candidates = List.of("Tom", "Sam", "Tim");
        for (String person : people) {
            if (candidates.contains(person)) {
                return person;
            }
        }
        return "";
    }

对象间搬移特性

搬移函数(Move Method)

当一个类中的函数与另一个的的关系更强,这时候通常将该函数提取到此类中
例子

public class Account {

    private AccountType type;
    private int daysOverdrawn;

    public Account(AccountType type, int daysOverdrawn) {
        this.type = type;
        this.daysOverdrawn = daysOverdrawn;
    }

    public double calcOverdraftCharge() {
        if (type.isPremium()) {
            double result = 10;
            if (daysOverdrawn > 7) {
                result += (daysOverdrawn - 7) * 0.85;
            }
            return result;
        } else {
            return daysOverdrawn * 1.75;
        }
    }

    public double calcBankCharge() {
        double result = 4.5;
        if (daysOverdrawn > 0) {
            result += calcOverdraftCharge();
        }
        return result;
    }
}
public class AccountType {
    public static final int ACCOUNT_TYPE_PREMIUM = 1;
    public static final int ACCOUNT_TYPE_SENIOR = 2;
    private int accountType;

    public AccountType(int accountType) {
        this.accountType = accountType;
    }

    public boolean isPremium() {
        return ACCOUNT_TYPE_PREMIUM == accountType;
    }

}

重构技巧

我们可以看见 calcOverdraftCharge这个函数用来计算透支费用,它会根据不同的AccountType(账号类型) 来进行计算,可以感觉到这个函数和AccountType类的关系更强。
不管是重构代码还是使用设计模式,都需要对业务有一定的了解,不然就是纸上谈兵
开始对calcOverdraftCharge重构
1. 将 daysOverdrawn变为形参,选中daysOverdrawn变量 使用引入形参(CTRL+ALT+P) 然后代理方法(ALT+SHIFT+O)

有的时候因为快捷键冲突可能和导致失效,使用右键重构(CTRL+ALT+SHIFT+T)
因为后面我们要搬移函数,但是它依赖daysOverdrawn这个变量

    public double calcOverdraftCharge() {
        return calcOverdraftCharge(daysOverdrawn);
    }

    public double calcOverdraftCharge(int daysOverdrawn) {
        if (type.isPremium()) {
            double result = 10;
            if (daysOverdrawn > 7) {
                result += (daysOverdrawn - 7) * 0.85;
            }
            return result;
        } else {
            return daysOverdrawn * 1.75;
        }
    }
  1. 搬移函数(F6)

重构后

public class Account {

    private AccountType type;
    private int daysOverdrawn;

    public Account(AccountType type, int daysOverdrawn) {
        this.type = type;
        this.daysOverdrawn = daysOverdrawn;
    }

    public double calcOverdraftCharge() {
        return type.calcOverdraftCharge(daysOverdrawn);
    }

    public double calcBankCharge() {
        double result = 4.5;
        if (daysOverdrawn > 0) {
            result += calcOverdraftCharge();
        }
        return result;
    }
}
public class AccountType {
    public static final int ACCOUNT_TYPE_PREMIUM = 1;
    public static final int ACCOUNT_TYPE_SENIOR = 2;
    private int accountType;

    public AccountType(int accountType) {
        this.accountType = accountType;
    }

    public boolean isPremium() {
        return ACCOUNT_TYPE_PREMIUM == accountType;
    }

    public double calcOverdraftCharge(int daysOverdrawn) {
        if (isPremium()) {
            double result = 10;
            if (daysOverdrawn > 7) {
                result += (daysOverdrawn - 7) * 0.85;
            }
            return result;
        } else {
            return daysOverdrawn * 1.75;
        }
    }
}

搬移字段(Move Field)

类中字段被其它类用的更多,这就像自己的老婆老往别人家跑,你说这你能受的了吗?这不让她搬出去?

技巧

和上面搬移方法一样 F6(只适用于静态字段)

沉默是今夜的康桥!

提炼类(EXtract Class)

一个类承担的过多不属于它的职责

技巧

在这里插入图片描述
在这里插入图片描述

重构后

public class Person {
    private final TelephoneNumber telephoneNumber = new TelephoneNumber();
    private String name;

    public Person(String name, String officeAreaCode, String officeNumber) {
        this.name = name;
        this.telephoneNumber.officeAreaCode = officeAreaCode;
        this.telephoneNumber.officeNumber = officeNumber;
    }

    public String getTelephoneNumber() {
        return telephoneNumber.getTelephoneNumber();
    }


    public class TelephoneNumber {
        String officeAreaCode;
        String officeNumber;

        public TelephoneNumber() {
        }

        public String getTelephoneNumber() {
            return "(" + officeAreaCode + ")" + officeNumber;
        }
    }

}

内联类(Inline Class)

当类的职责过少时,就将改类内联到和它联系紧密的类

技巧

首先查看调用点(Alt+F7),然后重构

隐藏委托关系(Hide Delegate)

客户通过委托类来调用另一个对象,应该在服务类上直接返回客户想要的.这样的好处就是频闭了内部的实现,日后做变动时用户是无感知的

移除中间人(Remove Middle Man)

类中做了大量简单的委托,这时候应该让客户直接调用

重新组织数据

自封装字段

关于字段的访问要么直接访问,要么通过函数来间接访问.
间接访问的好处:

  1. 可以延迟初始化
  2. 子类可以动态的改变获取数据的途径

以对象取代数组

在我们公司中算法端的业务中存在大量的 return 数组,通过不同的下标来表明其含义
其实应该 用对象来取代数组

以字面常量取代魔法值

在阿里巴巴开发手册中明确要求不能出现魔法值,如果一个常量具有特殊意义那就应该创建一个常量并为其命名

以字段取代子类

你的各个子类中唯一差别只是在返回常量数据的函数上

例子

public interface Client {

    PtyOsType getTtyOsType();

}


public class CmdClient implements Client{
    @Override
    public PtyOsType getTtyOsType() {
        return PtyOsType.CMD;
    }
}

public class LinuxBashClient implements Client {

    @Override
    public PtyOsType getTtyOsType() {
        return PtyOsType.LINUX_BASH;
    }
}

重够后

public abstract class Client {

    public  final PtyOsType PtyOsType ;

    public Client(com.example.core.client.PtyOsType ptyOsType) {
        PtyOsType = ptyOsType;
    }

    public  PtyOsType getTtyOsType(){
        return PtyOsType;
    }

}


public class ClientFactory {
    

    public Client getClient(PtyOsType ptyOsType) {

        switch (ptyOsType) {
            case CMD -> {
                return SingletonClient.CMD_CLIENT.getInstance();
            }
            case LINUX_BASH -> {
                return SingletonClient.BASH_CLIENT.getInstance();
            }
            default ->  throw new IllegalStateException("没有该客户端");
        }
    }

    public Client getClient(Integer id) {
        return getClient(PtyOsType.getById(id));
    }


    public  enum SingletonClient {
        CMD_CLIENT(new CmdClient(PtyOsType.CMD)),BASH_CLIENT(new LinuxBashClient(PtyOsType.LINUX_BASH));
        private final Client instance;
        SingletonClient(Client client){
            instance = client;
        }
        public Client getInstance(){
            return instance;
        }
    }
}

参考

重构:改善即有代码的设计

相关文章:

  • springcloud
  • 【机器学习】树模型决策的可解释性与微调(Python)
  • SHRM在中国的认可度如何?这里说了实话
  • 单片机控制发光二极管的显示(1)
  • UNIX环境高级编程-第六章-系统数据文件和信息
  • 【初学者入门C语言】之习题篇(二)
  • [架构之路-14]:目标系统 - 硬件平台 - CPU、MPU、NPU、GPU、MCU、DSP、FPGA、SOC的区别
  • Linux下brk、sbrk实现一个简易版本的malloc
  • 一、CSS选择器与权重[基础选择器、结构选择器、属性选择器、伪类选择器]
  • flutter系列之:深入理解布局的基础constraints
  • 【C语言进阶】动态内存管理及柔性数组
  • 网课查题接口系统
  • C语言基础知识入门
  • 闲暇之际敲敲代码,记录Leetcode刷题Day-01
  • 2021年下半年信息安全工程师上午真题及答案解析
  • 《Javascript数据结构和算法》笔记-「字典和散列表」
  • 【跃迁之路】【669天】程序员高效学习方法论探索系列(实验阶段426-2018.12.13)...
  • Dubbo 整合 Pinpoint 做分布式服务请求跟踪
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • MobX
  • PHP 小技巧
  • SQLServer之索引简介
  • vue从创建到完整的饿了么(11)组件的使用(svg图标及watch的简单使用)
  • Work@Alibaba 阿里巴巴的企业应用构建之路
  • 纯 javascript 半自动式下滑一定高度,导航栏固定
  • 记录一下第一次使用npm
  • 微信公众号开发小记——5.python微信红包
  • 一个SAP顾问在美国的这些年
  • scrapy中间件源码分析及常用中间件大全
  • #AngularJS#$sce.trustAsResourceUrl
  • #HarmonyOS:Web组件的使用
  • (2)nginx 安装、启停
  • (27)4.8 习题课
  • (4)事件处理——(7)简单事件(Simple events)
  • (42)STM32——LCD显示屏实验笔记
  • (5)STL算法之复制
  • (solr系列:一)使用tomcat部署solr服务
  • (SpringBoot)第七章:SpringBoot日志文件
  • (三) diretfbrc详解
  • (一)80c52学习之旅-起始篇
  • (转)AS3正则:元子符,元序列,标志,数量表达符
  • (转)树状数组
  • (转载)深入super,看Python如何解决钻石继承难题
  • (自用)learnOpenGL学习总结-高级OpenGL-抗锯齿
  • ***微信公众号支付+微信H5支付+微信扫码支付+小程序支付+APP微信支付解决方案总结...
  • .NET CF命令行调试器MDbg入门(三) 进程控制
  • .NET LINQ 通常分 Syntax Query 和Syntax Method
  • .NET/C# 推荐一个我设计的缓存类型(适合缓存反射等耗性能的操作,附用法)
  • .Net下使用 Geb.Video.FFMPEG 操作视频文件
  • [ 渗透测试面试篇 ] 渗透测试面试题大集合(详解)(十)RCE (远程代码/命令执行漏洞)相关面试题
  • [<事务专题>]
  • [Angularjs]ng-select和ng-options
  • [Bada开发]初步入口函数介绍
  • [BZOJ4016][FJOI2014]最短路径树问题
  • [codevs 1515]跳 【解题报告】