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

    参考

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

  • 相关阅读:
    企业跨境出海选择AWS怎么样?
    vue2 + element-ui,前端配置化表单封装(2024-06-14)
    测试左移:传统测试方式该如何过渡
    频域分析实践介绍
    Python-数据爬取(爬虫)
    K8s 暴露服务的新方法 Gateway API 详解,它有什么好处?
    从Hugging Face上手动下载并加载预训练模型
    SQL必需掌握的100个重要知识点:使用表别名
    Docker 实用操作文档
    【GD32F427开发板试用】IAR flash loader 下载GD32F427流程简要分析
  • 原文地址:https://blog.csdn.net/qq_40495860/article/details/126236239