zoukankan      html  css  js  c++  java
  • 重构改善既有代码的设计--重新组织函数

    重新组织函数

    1、Extract Method (提炼函数)

    动机:你有一段代码可以被组织在一起并独立出来。

    做法:将这段代码放进一个独立函数中,并让函数名称解释该函数的用途。

    如下面的实例,提炼后的代码变得更清晰易读。

    代码1:

    void printOwing(double previousAmount)){

            Enumeration e = _orders.elemnets();

            double outstanding = previousAmount * 1.2);

            

            //pirnt banner

            System.out.println("***************************");

            System.out.println("****** Customer Owes ******");

            System.out.println("***************************");

            

            //calculate outstanding

            while (e.hasMoreElements()) {

                Order each = (Order)e.nextElement();

                outstanding += each.getAmout();

            }

            

            //print details

            System.out.print("name:" + _name);

            System.out.print("amout:" + outstanding);

    }

    代码2:

        void printOwing(double previousAmount){

            printBanner();

            double outstanding = getOutstanding(previousAmount * 1.2);

            printDetails(outstanding);

    }

    void printBanner(){

            System.out.println("***************************");

            System.out.println("****** Customer Owes ******");

            System.out.println("***************************");

        }

        double getOutstanding(double initialValue){

            double result = initalValue;

            Enumeration e = _orders.elemnets();

            while (e.hasMoreElements()) {

                Order each = (Order)e.nextElement();

                result += each.getAmout();

            }

            return result;

        }

        void printDetails(double outstanding){

            System.out.print("name:" + _name);

            System.out.print("amout:" + outstanding);

    }

    2、Inline Method(内涵函数)

    动机:一个函数的本体与名称同样清楚易懂。

    做法:在函数调用点插入函数本体,然后移除该函数。

    代码1

    int getRating(){

            return (moreThanFiveLateDeliveries()) ? 2 : 1;

        }

        boolean moreThanFiveLateDeliveries(){

            return _numberOfLateDeliveries > 5;

    }

    代码2

    int getRating(){

            return (_numberOfLateDeliveries > 5) ? 2 : 1;

        } 

    3、Inline Temp (内联临时变量)

    动机:你有一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其他重构手法。

    做法:将所有对该变量的引用动作,替换为对它赋值的那个表达式自身。

    备注:检查该变量是否真的被赋值一次,可以将该变量声明为final,然后编译。

    代码1

    double basePrice = anOrder.basePrice();

    return (basePrice > 1000);

    代码2

    return (anOrder.basePrice()> 1000);

    4、Replace Temp with Query(以查询取代临时变量)

    动机:临时变量的问题在于:他们是暂时的,而且只能在所属函数内使用。由于临时变量只在所属函数内可见,所以他们会驱使你写更长的函数,因为只有这样你才能访问到需要的临时变量。如果把临时变量替换为一个查询,那么同一个类中的所有函数都将可以获得这份信心。这将带给你极大帮助,使你能够为这个类编写更清晰的代码。

    做法:将这个表达式提炼到一个独立函数中。将这个临时变量的所有引用点替换为对新函数的调用。此后,新函数就可被其他函数使用。

    代码1

    double basePrice = _quantity * _itemPrice;

        if(basePrice > 1000)

            reutrn basePrice * 0.95;

        else 

            return basePrice * 0.98;

            

    代码2

    if(basePrice() > 1000)

            return basePrice() * 0.95;

        else 

            return basePrice() * 0.98;

        ....

        double basePrice(){

            return _quantity * _itemPrice;

    }

    这个重构手法较为简单,如果临时变量比较多,还需要运用Split Temporary Variable(拆分临时变量)或Separate Query from Modifier(将查询函数和修改函数分离)使情况变得简单一些,然后再替换临时变量。如果你想替换的临时变量是用来收集结果的(例如循环中的累加值),就需要将某些程序逻辑(例如循环)复制到查询函数去。

    代码1

         double getPrice(){

            int basePrice = _quantity * _itemPrice;

            double discountFactor;

            if(basePrice > 1000) discountFactor = 0.95;

            else discountFactor = 0.98;

            return basePrice * discountFactor;

        }

        

    代码2

    double getPrice(){

            return basePrice() * discountFactor();

        }

        

        private int basePrice(){

            return _quantity * _itemPrice;

        }

        

        private double discountFactor(){

            if(basePrice() > 1000) return 0.95;

            else return 0.98;

        }

    5、Introduce Explaining Variable(引入解释性变量)

    动机:你有一个复杂的表达式。在条件逻辑中,Introduce Explaining Variable 特别有价值:你可以用这项重构将每个条件子句提炼出来,以一个良好命名的临时变量来解释对应条件子句的意义。使用这项重构的另一种情况是,在较长算法中,可以运用临时变量来解释,每一步运算的意义。

    代码1

    if((platform.toupperCase().indexOf("MAC") > -1) &&

       (browser.toUpperCase().indexOf("IE") > -1) &&

       wasInitialized() && resize > 0){

           //do something

       }

      代码2:  

      final boolean isMacOs = platform.toupperCase().indexOf("MAC") > -1;

      final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;

      final boolean wasResized = resize > 0;

      

      if(isMacOs && isIEBrowser && wasResized){

        //do something

    }

    类似的,我们也经常用Extract Method(提炼函数)来解释表达式用途,如下示例:

    代码1

    double price(){

            //price is base price - quantity discount + shipping

            return _quantity * _itemPrice -

                    Math.max(0, _quantity - 500) * _itemPrice * 0.05 +

                    Math.min(_quantity * _itemPrice * 0.1, 100.0);

    }

    代码2

    double price(){

            return basePrice() - quantityDiscount() + shipping();

        }

        

        private double quantityDiscount(){

            return Math.max(0, _quantity - 500) * _itemPrice * 0.05;

        }

        

        private double shipping(){

            return Math.min(basePrice() * 0.1, 100.0);

        }

        

        private double basePrice(){

            return _quantity * _itemPrice;

        }

    Introduce Explaining Variable(引入解释性变量)和Extract Method(提炼函数)比较:两者都可以达到解释表达式的效果,我觉得主要看临时变量是否被公用到,如果不是,则用引入解释性变量即可。

    6、Split Temporary Variable(分解临时变量)

    场景:你的程序有某个临时变量被赋值超过一次,它既不是循环变量,也不被用于手机计算结果。

    分析:这种临时变量应该制备赋值一次。如果它们被赋值超过一次,就意味它们在函数中承担了一个以上的责任。如果临时变量承担多个责任,它就应该被替换(分解)为多个临时变量,每个变量只承担一个责任。同一个临时变量承担两件不同的事情,会令代码阅读者糊涂。

    代码1

    double temp = 2 * (_height + _width);

    System.out.println(temp);

    temp = _height * _width;

    System.out.println"temp);

    代码2

      final double perimeter = 2 * (_height + _width);

            System.out.println(perimeter);

            final double area = _height * _width;

            System.out.println(area);

    7、Remove Assignments to Parameters (移除对参数的赋值)

    场景:代码对一个参数进行赋值

    做法:以一个临时变量取代该参数的位置。

    分析:从本质上说,参数是对象的引用,而对象的引用时按值传递的。因此我可以修改参数对象的内部状态(属性),但对参数对象重新赋值是没有意义。

    代码1

    int discount(int inputVal, int quantity, int yearToDate){

            if(inputVal > 50) inputVal -=2;

        }

    代码2

    int discount(int inputVal, int quantity, int yearToDate){

             int result = inputVal;

             if(inputVal > 50) result -=2;

           } 

    我还可以为参数加上关键词final,从而强制它遵循“不对参数赋值”这一惯例:

    代码1

    int discount(int inputVal, int quantity, int yearToDate){

            if(inputVal > 50) inputVal -=2;

            if(quantity > 100) inputVal -=1;

            if(yearToDate > 10000) inputVal -=4;

    }

    代码2

    int discount(final int inputVal, final int quantity, final int yearToDate){

            int result = inputVal;

            if(inputVal > 50) result -=2;

            if(quantity > 100) result -=1;

            if(yearToDate > 10000) result -=4;

        }

    不过我得承认,我不经常使用final来修饰参数,因为我发现,对于提高短函数的清晰度,这个方法并无太大帮助。我通常会在较长的函数中使用它,让它帮助我检查参数是否被做修改。

    8、Replace Method with Method Object(以函数对象取代函数)

    动机:你有一个大型函数,其中对局部变量的使用使你无法采用Extract Method(提炼函数)。

    做法:将这个函数放进一个单独对象中,如此一来局部变量就成了对象内的字段。然后你可以在同一个对象中将这些大型函数分解为多个小型函数。

    9、Subsitute Algorithm(替换算法)

    动机:你需要把某个算法替换为另一个更清晰的算法。

    做法:将函数本体替换为另一个算法。

    代码1

    String foundPerson(String[] people){

            for(int i=0; i<people.length; i++){

                if(people[i].equals("Don")){

                    return "Don";

                }

                if(people[i].equals("John")){

                    return "John";

                }

                if(people[i].equals("Kent")){

                    return "Kent";

                }

           }

            return "";

        }

        

    代码2

    String foundPerson(String[] people){

            List candidates = Arrays.asList(new String[]{"Don", "John", "Kent");

             for(int i=0; i<people.length; i++){

                if(candidates.contains(people[i])){

                    return people[i];

                }

           }

            return "";

        }

  • 相关阅读:
    聊聊 API Gateway 和 Netflix Zuul
    现行统编中学数学教科书有多烂
    线程池的成长之路
    Quick Guide to Microservices with Spring Boot 2.0, Eureka and Spring Cloud
    以太坊、Hyperledger Fabric和Corda,哪个更好?
    【SFA官方翻译】Spring WebFlux和Spring Cloud进行响应式微服务开发
    goroutine背后的系统知识
    goroutine与调度器
    MySQL命令,一篇文章替你全部搞定
    微服务架构技术栈选型手册(万字长文)
  • 原文地址:https://www.cnblogs.com/kuyuyingzi/p/4266277.html
Copyright © 2011-2022 走看看