重构-改善既有代码设计(一)

一、重构的风险

1.它必须修改运作中的程序,这可能引入一些不易察觉的错误。
2.如果重构时不做好准备,不遵守规则,风险就更大。你挖掘自己的代码,很快发现了一些值得修改的地方,于是你挖得更深。挖得愈深,找到重构的机会就越多,于是你的修改也愈多......最后你给自己挖了一个大坑,最后爬不出去了。

总结:所以为了避免这些风险,重构必须系统化进行。

二、什么是重构?

重构:在代码写好之后改进它的设计。

三、第一个重构的例子

3.1 新建Movie、Customer、Rental类

/**
 * @author eli.hou
 * Movie只是一个简单的纯数据类
 */
public class Movie {

    public static final int CHILDRENS = 2;
    public static final int REGULAR = 0;
    public static final int NEW_RELEASE = 1;

    private String title;
    private int priveCode;

    public Movie(String title, int priveCode) {
        this.title = title;
        this.priveCode = priveCode;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getPriveCode() {
        return priveCode;
    }

    public void setPriveCode(int priveCode) {
        this.priveCode = priveCode;
    }
}
import java.util.Enumeration;
import java.util.Vector;

/**
 * @author eli.hou
 * 顾客
 */
public class Customer {

    private String name;
    private Vector<Rental> rentals = new Vector<>();

    public Customer(String name) {
        this.name = name;
    }

    public void addRental(Rental ary){
        rentals.addElement(ary);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

/**
 * @author eli.hou
 * 租赁
 */
public class Rental {

    private Movie movie;
    private int daysRented;

    public Rental(Movie movie, int daysRented) {
        this.movie = movie;
        this.daysRented = daysRented;
    }

    public Movie getMovie() {
        return movie;
    }

    public void setMovie(Movie movie) {
        this.movie = movie;
    }

    public int getDaysRented() {
        return daysRented;
    }

    public void setDaysRented(int daysRented) {
        this.daysRented = daysRented;
    }
}

3.2 给Customer创建一个生成详单的函数

//region 原始方法
    /**
     * 生成详单
     */
    public String statement(){
        double totalAmount = 0;
        int frequentRenterPoints = 0;
        Enumeration _rentals = rentals.elements();
        String result = "Rental Record for " + getName() + "\n";
        while (_rentals.hasMoreElements()){
            double thisAmount = 0;
            Rental each = (Rental) _rentals.nextElement();

            //determine amounts for each line
            switch (each.getMovie().getPriveCode()){
                case  Movie.REGULAR:
                    thisAmount += 2;
                    if(each.getDaysRented() > 2)
                        thisAmount += (each.getDaysRented() - 2) * 1.5;
                    break;
                case Movie.NEW_RELEASE:
                    thisAmount += each.getDaysRented() * 3;
                    break;
                case Movie.CHILDRENS:
                    thisAmount += 1.5;
                    if (each.getDaysRented() > 3)
                        thisAmount += (each.getDaysRented() - 3) * 1.5;
                    break;
            }

            // add frequent renter points
            frequentRenterPoints ++;
            // add bonus for a two day new release rental
            if ((each.getMovie().getPriveCode() == Movie.NEW_RELEASE) &&
                each.getDaysRented() > 1 ) frequentRenterPoints ++;

            // show figures for this rental
            result += "\t" + each.getMovie().getTitle() + "\t" +
                    String.valueOf(thisAmount) + "\n";
            thisAmount += totalAmount;
        }
        //add footer lines
        result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
        result += "You earned " + String.valueOf(frequentRenterPoints) +
                " frequent renter points";
        return result;
    }
    //endregion

思考:

1.代码块太长了,导致代码的职责太多了,很多事情应该由其他类来完成
2.switch 逻辑泥团,把它提炼到独立函数中比较好

3.3 把switch语句提炼到Rental中去

Customer中代码

public String statement(){
        double totalAmount = 0;
        int frequentRenterPoints = 0;
        Enumeration _rentals = rentals.elements();
        String result = "Rental Record for " + getName() + "\n";
        while (_rentals.hasMoreElements()){
            double thisAmount = 0;
            Rental each = (Rental) _rentals.nextElement();

            // thisAmount = amountFor(each);
            // 此处代码就修改为each的getCharge()
            thisAmount = each.getCharge();

            // add frequent renter points
            frequentRenterPoints ++;
            // add bonus for a two day new release rental
            if ((each.getMovie().getPriveCode() == Movie.NEW_RELEASE) &&
                    each.getDaysRented() > 1 ) frequentRenterPoints ++;

            // show figures for this rental
            result += "\t" + each.getMovie().getTitle() + "\t" +
                    String.valueOf(thisAmount) + "\n";
            thisAmount += totalAmount;
        }
        //add footer lines
        result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
        result += "You earned " + String.valueOf(frequentRenterPoints) +
                " frequent renter points";
        return result;
    }

Rental代码

    /**
     * 该方法是由Customer类的amountFor()方法移植过来
     *
     * 代码为了适应新家,意味着要去掉参数,然后在迁移的同时改变函数名称
     *
     * switch语句,最好不要在另一个对象的属性基础上运用switch语句。如果不得不使用,也应该在对象自己的数据上使用,而不是在别人的数据上使用
     * 所以,switch语句应该迁移到Rental类中
     */
    double getCharge() {
        double result = 0;
        //determine amounts for each line
        switch (getMovie().getPriveCode()){
            case  Movie.REGULAR:
                result += 2;
                if(getDaysRented() > 2)
                    result += (getDaysRented() - 2) * 1.5;
                break;
            case Movie.NEW_RELEASE:
                result += getDaysRented() * 3;
                break;
            case Movie.CHILDRENS:
                result += 1.5;
                if (getDaysRented() > 3)
                    result += (getDaysRented() - 3) * 1.5;
                break;
        }
        return result;
    }
    

3.3.1 思考:改变变量名是值得的行为吗?

绝对值得,好的代码应该清楚表达出自己的功能,变量名是代码清晰的关键。
ps:任何一个傻瓜都能写出计算机可以理解的代码,唯有写出人类容易裂解的代码,才是优秀的程序员。

3.4 提炼“常客积分计算”代码

把积分计算责任放在Rental类

Rental

int getFrequentRenterPoints() {
    if ((getMovie().getPriveCode() == Movie.NEW_RELEASE) &&
       getDaysRented() > 1 ) return 2;
    else
       return 1;
}

Customer

public String statement(){
        double totalAmount = 0;
        int frequentRenterPoints = 0;
        Enumeration _rentals = rentals.elements();
        String result = "Rental Record for " + getName() + "\n";
        while (_rentals.hasMoreElements()){
            Rental each = (Rental) _rentals.nextElement();

            frequentRenterPoints += each.getFrequentRenterPoints();

            // show figures for this rental
            result += "\t" + each.getMovie().getTitle() + "\t" +
                    each.getCharge() + "\n";
            totalAmount += each.getCharge();
        }
        //add footer lines
        result += "Amount owed is " + totalAmount + "\n";
        result += "You earned " + frequentRenterPoints +
                " frequent renter points";
        return result;
    }

3.5 去除statement()方法中的局部变量

3.5.1 去除thisAmount变量

    //region 新思考:thisAmount 变得有点多余,它接受each.getCharge()的执行结果之后就不回再有任何改变了

    //尽量去除掉临时变量,然后each.getCharge()方法就会被执行两次,然后再对each.getCharge()方法进行优化

    //思考:"常客积分计算"做类似处理。积分的计算可能会发生变化,把积分运算的职责交给Rental类 改变结果statement03()方法

    public String statement(){
        double totalAmount = 0;
        int frequentRenterPoints = 0;
        Enumeration _rentals = rentals.elements();
        String result = "Rental Record for " + getName() + "\n";
        while (_rentals.hasMoreElements()){
            Rental each = (Rental) _rentals.nextElement();

            frequentRenterPoints ++;
            // add bonus for a two day new release rental
            if ((each.getMovie().getPriveCode() == Movie.NEW_RELEASE) &&
                    each.getDaysRented() > 1 ) frequentRenterPoints ++;

            // show figures for this rental
            result += "\t" + each.getMovie().getTitle() + "\t" +
                    each.getCharge() + "\n";
            totalAmount += each.getCharge();
        }
        //add footer lines
        result += "Amount owed is " + totalAmount + "\n";
        result += "You earned " + frequentRenterPoints +
                " frequent renter points";
        return result;
    }

3.5.2 去除totalAmount、frequentRenterPoints变量



    //region 去除临时变量

    //利用查询函数来取代totalAmount、frequentRenterPoints这两个临时变量


    // totalAmount : Customer 类的getTotalCharge()来取代
    // frequentRenterPoints : Customer 类的getTotalFrequentRenterPoints()来取代

    private double getTotalCharge(){
        double result = 0;
        Enumeration _rentals = rentals.elements();
        while (_rentals.hasMoreElements()){
            Rental each = (Rental) _rentals.nextElement();
            result += each.getCharge();
        }
        return result;
    }

    private int getTotalFrequentRenterPoints(){
        int result = 0;
        Enumeration _rentals = rentals.elements();
        while (_rentals.hasMoreElements()){
            Rental each = (Rental) _rentals.nextElement();
            result += each.getFrequentRenterPoints();
        }
        return result;
    }

    public String statement(){
        Enumeration _rentals = rentals.elements();
        String result = "Rental Record for " + getName() + "\n";
        while (_rentals.hasMoreElements()){
            Rental each = (Rental) _rentals.nextElement();

            // show figures for this rental
            result += "\t" + each.getMovie().getTitle() + "\t" +
                    each.getCharge() + "\n";
        }
        //add footer lines
        result += "Amount owed is " + getTotalCharge() + "\n";
        result += "You earned " + getTotalFrequentRenterPoints() +
                " frequent renter points";
        return result;
    }
    //endregion

本文源代码:https://gitee.com/KimHX/Java-Refactoring

posted @ 2020-12-16 20:20  Eli-Hou  阅读(210)  评论(0)    收藏  举报