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

第一个案例:

重构的第一步:为即将改变的代码建立一组可靠的测试环境。

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 _priceCode;

    public Movie(String title, int priceCode) {
        _title = title;
        _priceCode = priceCode;
    }

    public String getTitle() {
        return _title;
    }

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

    public int getPriceCode() {
        return _priceCode;
    }

    public void setPriceCode(int _priceCode) {
        this._priceCode = _priceCode;
    }
}
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 int getDaysRented() {
        return _daysRented;
    }
}

 

public class Cunstomer {
    private String _name;
    private Vector _rentals = new Vector();

    public Cunstomer(String _name) {
        this._name = _name;
    }

    public void addRental(Rental arg) {
        _rentals.addElement(arg);
    }

    public String getName() {
        return _name;
    }

    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();
            switch ((each.getMovie().getPriceCode())) {
                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;

            }
            frequentRenterPoints++;
            if((each.getMovie().getPriceCode()==Movie.NEW_RELEASE)&&each.getDaysRented()>1) frequentRenterPoints++;
            result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(thisAmount) + "\n";
            totalAmount += thisAmount;
        }
        result += "Amount owed is" + String.valueOf(totalAmount) + "\n";
        result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points";
        return result;
    }
}

现在需要重构的就是这个statement:

重构这个statement分为以下几步:

第一步:switch语句

    6b62aac5beacf1d0210cd4491d1c046fe27.jpg

我用idea重构功能,他并自动生成的方法,thisAmount也是作为入参传进来的,看起来好像没多大差,至少看到这我是没看出来差别或者特殊的用意的。

第二步:搬移“金额计算”的代码

        观察amountFor()时,发现这个函数使用了Rental类的信息,没有使用来自Customer类的信息。绝大多数情况下,函数应该放在它所使用的数据的所属对象内,所以amountFor()应该移动到Rental类去。然后找出旧函数的引用点并修改为引用新函数(全局搜索一下看都有哪些引用)。去掉旧函数。然后进行测试。

有时候会保留旧函数,然后让它调用新函数。比如说旧函数是public的函数,而又不想修改其他类的接口,这就是一种很有效的方法。

移动之后,函数会有微调,到Rental类的方法,不需要参数了。

第三步:改完之后,thisAmount就变得多余了,因为他接受的是each.getCharge()的结果,并且不会发生变化,所以可以去掉thisAmount这个局部变量。

第四步:提炼“常客积分计算”代码

7b9060a0ec969e0753d78272ef532c08543.jpg

首先,积分计算应该放在Rental类。然后,再看一下局部变量。each,可以被当作参数传入新函数;另一个临时变量是frequentRenterPoints,它在被使用前有初始化值,但提炼出来的函数并没有读取该值,所以我们不需要把它当作参数传进去,只需要把新函数的返回值累加上去就行了。

第五步:去除临时变量。

临时变量可能是个问题,他们使函数变得冗长而复杂。这个例子有2个临时变量totalAmount和frequentRentalPoints,他们都是用来从Rental对象中获得某个总量,可以利用查询函数来取代totalAmount和frequentRentalPoints这2个临时变量。这样类中任何函数都可以调用这个查询函数了。

第六步:

假如,需要修改影片分类规则,费用计算方式和常客积分计算方式有待确定。这个时候盲目重构,肯定是不合适的。

这个时候可以运用多态取代与价格相关的条件逻辑。

首先,是switch,最好不要再另一个对象的属性基础上运用switch语句,如果不得不使用,也应该在对象自己的数据上使用,而不是在别人的数据上使用。

也就是getCharge()应该从Rental类移动到Movie类中。这个时候想让方法可以使用,必须把租期长度作为参数传递进去。租期长度来自Rental对象。计算费用时需要2项数据:租期长度和影片类型。选择把租期长度传给Movie对象而不是将影片类型传给Rental对象,是因为影片类型可能会发生变化,所以选择在Movie对象内计算费用。

第7步:继承

d11e41c594fda4314073d0b91f31a9d1661.jpg

加入这一层间接性,可以在Price对象内进行子类化动作,可以在任何必要时刻修改价格。

为了引入State模式,首先运用Replace Type Code with State/Strategy,将与类型相关的行为搬移到State模式内。然后运用Move Method讲switch语句移动到Price类,最后运用Replace Conditional with Palymorphism去掉switch语句。

运用Replace Type Code with State/Strategy:

第一步骤,针对类型代码使用SelfEncapsulate Field,确保任何时候都通过取值函数和设置函数来访问类型代码。多数访问操作来自其他类,他们已经在使用取值函数,但构造函数仍然直接访问价格代码,可以在Movie构造函数中,用一个设置函数来代替直接访问价格,然后编译测试,确保没有破坏任何东西。新建一个Price类,并在其中提供类型相关的行为,即在Price类中加入一个抽象函数,并在所有子类中加上对应的具体函数。

第二步:修改Movie类中的“价格代号”访问函数(取值函数/设置函数),让他们使用新类。在Movie类内保存一个Price对象,而不再保存一个_priceCode变量,修改访问函数。然后重新编译测试。

Move Method:对getCharge()实施Move Method。

搬移之后,运用Replace Conditional with Palymorphism:一次取出一个case分支,在相应的类建立一个覆盖函数。这个函数覆盖了父类中的case分支,父类中的代码先不动,在取出下一个case分支,一次处理,并编译测试(注意确保执行的是子类的)。处理完所有的case分支之后,把P.getCharge()声明为abstract。再运用同样的手法处理getFrequentRenterPoints().但是这个方法不需要把超类函数声明为abstract,只需要为新片类型增加一个覆盖函数,并在超类留下一个已定义的函数,使它成为一种默认行为。

引入State模式,可以做到,如果要修改任何与价格有关的行为,或是添加新的定价标准,或是加入其它取决于价格的行为,程序的修改会容易很多。

经过以上重构之后,代码变成了如下的样子:

public abstract class Price {
    abstract int getPriceCode();

    abstract double getCharge(int daysRented);

    public int getFrequentRenterPoints(int daysRented) {
        return 1;
    }

}
public class RegularPrice extends Price {
    @Override
    int getPriceCode() {
        return Movie.REGULAR;
    }
   public double getCharge(int daysRented) {
        double thisAmount = 2;
        if (daysRented > 2)
            thisAmount += (daysRented - 2) * 1.5;
        return thisAmount;
    }
}
public class ChildrensPrice extends Price {
    @Override
    int getPriceCode() {
        return Movie.CHILDRENS;
    }

    public double getCharge(int daysRented) {
        double thisAmount = 1.5;
        if (daysRented > 3)
            thisAmount += (daysRented - 3) * 1.5;
        return thisAmount;
    }
}
public class NewReleasePrice extends Price {
    @Override
    int getPriceCode() {
        return Movie.NEW_RELEASE;
    }

    public double getCharge(int daysRented) {
        return daysRented * 3;

    }

    public int getFrequentRenterPoints(int daysRented) {
        return daysRented > 1 ? 2 : 1;
    }
}
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 int getDaysRented() {
        return _daysRented;
    }

    public double getCharge() {
        return _movie.getCharge(_daysRented);
    }

    public int getFrequentRenterPoints() {
        return _movie.getFrequentRenterPoints(_daysRented);
    }

}
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 Price _price;

    public Movie(String title, int priceCode) {
        _title = title;
        setPriceCode(priceCode);
    }

    public String getTitle() {
        return _title;
    }

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

    public int getPriceCode() {
        return _price.getPriceCode();
    }

    public void setPriceCode(int args) {
        switch (args) {
            case REGULAR:
                _price = new RegularPrice();
                break;
            case CHILDRENS:
                _price = new ChildrensPrice();
                break;
            case NEW_RELEASE:
                _price = new NewReleasePrice();
                break;
                default:
                    throw new IllegalArgumentException("Incorrent Price Code");
        }
    }

    public double getCharge(int daysRented) {
        return _price.getCharge(daysRented);

    }

    public int getFrequentRenterPoints(int daysRented) {
        return _price.getFrequentRenterPoints(daysRented);
    }
}
public class Cunstomer {
    private String _name;
    private Vector _rentals = new Vector();

    public Cunstomer(String _name) {
        this._name = _name;
    }

    public void addRental(Rental arg) {
        _rentals.addElement(arg);
    }

    public String getName() {
        return _name;
    }

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

        }
        result += "Amount owed is" + String.valueOf(getTotalCharge()) + "\n";
        result += "You earned " + String.valueOf(getTotalFrequentRenterPoints()) + " frequent renter points";
        return result;
    }

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

        }
        return frequentRenterPoints;
    }

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

    private double amountFor(Rental rental) {
        return rental.getCharge();
    }
}

可以试试把最开始的代码考到idea中,然后看看如果是你,你会怎么优化,然后按提示优化下,再跟答案比一下,最后对比一下自己的答案,还是挺有意思的,这个就是第一章的内容。

 

第二章:重构原则

f67d024a2a85053a5e22faf768baa593856.jpg

58adda2a64396c419e91f84f093eb6c13fc.jpg

重构的目的是使软件更容易被理解和修改。重构不会改变软件可观察的行为,重构之后软件功能一如以往。

与之形成对比的是性能优化。和重构一样,性能优化通常不会改变组建的行为(除了执行速度),之后改变内部结构。但是两者出发点不同:性能优化往往使代码较为难理解,但为了得到所需要的性能不得不那么做。

adbb4ed2372024e71a1c99bf24b28ae338b.jpg

为何重构:

1.重构改进软件设计,比如,消除重复代码,确定所有事物和行为在代码中只表述一次,方面未来代码的修改。

2.重构使软件更容易理解:在重构上花一点点时间,就可以让代码更好地表达自己的用途,即“准确说出我所要的”。而且,还可以利用重构来帮助自己协助理解不熟悉的代码。真正动手修改代码,让它更好地反映出我的理解,然后重新执行,看它是否正常运行,检验自己的理解是否正确。随着代码的简洁,就可以看到以前看不到的设计层面的东西。

3.重构帮助找bug:

4.重构提高编程速度

 

何时重构:

重构不是一件应该特别拨出事件做的事情,重构应该随时进行,不应该为了重构而重构。之所以重构,是因为想做别的事情,而重构可以帮助把那些事做好。

三次法则:第一次做某件事时只管去做;第二次做类似的事会产生反感,但无论如何还是可以去做;第三次在做类似的事,就应该重构。事不过三,三则重构。

1.添加功能时重构

2.修补错误时重构:调试过程中运用重构,让代码更具有可读性,因为代码还不够清晰,没有让自己一眼看出bug。

3.复审代码时重构:开始重构前可以先阅读代码,得到一定程度的理解,并提出一些建议。一旦想到一些点子,考虑是否可以通过重构立即轻松地实现它们。多做几次重构,代码会变得更清楚,提出更多恰当的建议,可以获得更高层次的认识。重构还可以帮助代码复审工作得到更具体的认识,不仅获得建议,而且其中许多建议能够理解实现,从实现中得到成就感。

81bbaef030000d983f314cda28f0aade078.jpg

 

间接层和重构:

间接层的价值:允许逻辑共享;分开解释意图和实现;隔离变化;封装条件逻辑

找出一个缺乏“间接层利益”之处,在不修改现有行为的前提下,为它加入一个间接层,获得一个更有价值的程序,提高程序质量,让自己再明天受益。

找出不值得的间接层,将它拿掉。这种间接层常以中介函数形式出现,它可能是个组件,你本来期望在不同地方共享它或者让它表现出多态性,最终却只在一处用到。

 

重构的难题:

1.数据库:重构经常出问题的一个领域就是数据库。第一,绝大多数商用程序都和背后的数据库结构紧密耦合在一起;第二,数据迁移。就算将系统分层,将数据库结构和对象模型间依赖降至最低,但数据库结构改变还是让你不得不迁移所有数据,这是漫长而烦琐的工作。

在非对象数据库中,解决这个问题的办法之一就是:在对象模型和数据库模型之间插入一个分隔层,这就可以隔离两个模型各自的变化。升级某一模型是,只需升级上述的分离层即可。这样的分割层会增加系统复杂度,但可以带来很大的灵活度。如果同时拥有多个数据库,或如果数据库模型较为复杂使你难以控制,那么就是不进行重构,这分隔层也是很重要的。

对开发者而言,对象数据库既有帮助也有妨碍。自行完成迁移时,必须留神类中的数据结构变化,可以放心把类的行为转移过去,但是转移字段时必须格外小心,数据尚未被转移前就得先运用访问函数造成“数据已经转移”的假象。一旦确定知道数据应该放在何处,就可以一次性将数据迁移过去。这是唯一需要修改的只有访问函数,可以降低错误风险。

2.修改接口:如何面对那些必须修改“已发布接口”的重构手法?尽量让旧接口调用新接口。但这样会使接口变得复杂,还有另一个选择,能不发布接口的时候,尽量不要发布接口。

0513ddaeef94288352bf3a2df3555662dac.jpg

 

何时不该重构:现行代码不能正常运行,满是错误,重构不如重写来的简单。重构之前,代码必须在大部分情况下正常运行。项目接近最后期限,避免重构。

重构与性能:除了对性能有严格要求的实时系统,其他任何情况下“编写快速软件”的秘密就是,首先写出可调的软件,然后调整它以求获得足够速度。

 

第三章:代码的坏味道

1.重复代码

cf4c2e85ba4dcabbb9779a844afb52ac4c2.jpg

2.过长函数

c603aad85e76850f92f58f773f991dcdb3a.jpg

fd67b6bfbd99d0901e6aceae8487c928b16.jpg

3.过大的类

67c37ad09dc08a5ad60e26ae257570f4b70.jpg

4.过长参数列

5b1b2cc5ff875c67dd641024a64844d887c.jpg

5.发散式变化

1791c3ea207d02a2b6eeeb1ba40bf7090e2.jpg

6.霰(xian,第四声)弹式修改

372f1a02632c34effe08f925252b0beb3a9.jpg

7.依恋情节

函数对某个类的兴趣高过对自己所处类的兴趣,这种孺慕之情通常就是数据。把这个函数移到另一个地方,使用Move Method,有时候函数中只有一部分受这种依恋之苦,就应该使用Extract Method提炼到独立的函数,在使用move method带它去他该去的地方。如果一个函数用到几个类的功能,判断哪个类拥有最多被此函数使用的数据,把该函数移到那个类,如果先以Extract Method将这个函数分解为数个较小函数并分别置放于不同地点,上述步骤就更容易完成了。将总是一起变化的东西放在一块儿。如果例外出现,就搬移那些行为,保持变化只在一地发生。Stragegy和Visitor使得可以轻松修改函数行为,但多一层间接性的代价。

8.数据泥团

1dd4041804a43bd9a77a4855ee82381b0bb.jpg

9.基本类型偏执

1f50ef231d4a2cc445588d7306e8d3bef2e.jpg

10.switch语句

689bf1519ced2e1e141a55d699df6df50f5.jpg

11.平行继承体系

a8c64cf866adbc36c39f0ce732e1a90bfba.jpg

12.冗余类

3be913e4c96a9182fcc07b745e7a921709f.jpg

13.夸夸其谈未来性

176d60b3852d61d19468f987fda5fe37191.jpg

14.令人迷惑的暂时字段

8d8c8f68cacde041b51a3580f869ccdc265.jpg

15.过度耦合的消息链。

c72a55898eff688cc0b0da0645cd8f97edc.jpg

16.中间人

324eafc1f7341d86965be0e3847998a1378.jpg

17.狎昵关系

bb1167dad32c478f146b58d201ad1da11b3.jpg

18.异曲同工的类

5f97695ea1ca5acbd199006d2b070d839e8.jpg

19.不完美的库类

af22b93d1dbe5f461aa75df402facf8a0cb.jpg

20.纯粹的数据类

cfbba1dbcbd6ac2a063e98adb8b22c43199.jpg

21.被拒绝的遗赠

57ea23fc97e7fa37d32e08c908ebf9a8fca.jpg

22.过多的注释

2e1abc37df20775a3dbc3bc64475b117ebc.jpg

第4章:构建测试体系

自动化的单元测试:test suit

每当收到bug报告,请先写一个单元测试来暴露bug

继续添加更多测试:观察类该做的所有事情,然后针对任何一项功能的任何一种可能失败情况,进行测试。

测试的一项重要技巧就是“寻找边界条件”。“寻找边界条件”也包括寻找特殊的,可能导致测试失败的情况。

当事情被认为应该会出错,要记得检查是否抛出了预期的异常。

把测试集中在可能出错的地方。“花合理时间抓出大多数bug”要好过“穷尽一生抓出所有bug”。

构建一个良好的bug检测器并经常运行它,对任何开发工作都将大有裨益,并且是重构的前提。

第5章:重构列表

第6章:重新组织函数
578b0159d0ee5755ccbe8b9064da631f801.jpg

1.提炼函数

9a4eec97511810850de220a6a10061c6be6.jpg

有局部变量时:

局部变量最简单的情况是:被提炼代码段只是读取这些变量的值,并不修改它们。这种情况下我可以简单地将它们当作参数传给目标函数。

如果局部变量是个对象,而被提取代码调用了对该对象造成修改的函数,也可以如法炮制,只需要将这个对象作为参数传递给目标函数即可。只有在被提炼代码真的对一个局部变量赋值的情况下,才必须采取其他措施。

72e8891494308be00d5472111012b49de10.jpg

57b98896150bde78b9ea18ce0863a049513.jpg

2.内联函数

958846363c933dcf05e19215ed033344958.jpg

810c910b79cd39654c656988b578b0b97a0.jpg

d927ef5100aaef1c81959e4a795663856da.jpg

3.内联临时变量

有一个临时变量,只被简单表达式赋值一次,而它妨碍了其他重构手法。就需要内联化。

fe09b1caf24eb3fc827481b1d7a76269957.jpg

4.以查询取代临时变量

f6ffc6e47cc645e42ff39dda8a4f6de4526.jpg

88b59df4a34afb3254b14c2c428ebf9fea7.jpg

94060a756902800b7f7fea39667e0e2a68a.jpg

5.引入解释性变量

将复杂表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途。

20a5e02016d3e3a5faea2749f5916d0ffa9.jpg

4ece11642577df0e78a1e1aa9b230c611dc.jpg

ceb3c56311be4f965f9bae017d639d5208f.jpg

6.分解临时变量

程序中某个临时变量被赋值超过一次,它既不是循环变量,也不被用于收集计算结果,针对每次赋值,创造一个独立、对应的临时变量。e853e3c3c514972557f7905db5c2553b1ab.jpg

92f7da063a5597015bdd393d80023f61926.jpg

7.移除对参数的赋值

2ab2c55c08250e0ea2ee2a3100bcf0b5154.jpg

ca730700a2a64f74c4e9495a87835e0bdac.jpg

e958ab3ee747cbaab88db1a83ce9de9d08e.jpg

(这个我不是很能理解和吸收)

8.以函数对象取代函数

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

863d9de599ca75dc187093e2ab7bf5e2af2.jpg

它带来的好处是:可以轻松地对compute()函数采取Extract Method,不必担心参数传递问题。

9.替换算法

70724c71a01389e92692bb12f110555c407.jpg

f76ebd1e469d839c32f4b68cccfdede90e4.jpg

第七章:在对象之间搬移特性

414714ee50fa242ce7d89af1c865bf2f993.jpg

1、搬移函数:

c2b0aaf13534694b60f1d49ad55b9c63156.jpg

b2eb195d66fb68a12a9aeb69012d26dd6a5.jpg

1dadf09c79e724ad0b9f2ff01bc900df0e3.jpg

2.搬椅字段

6fcb87e40198d479f7b20c7d73a50775630.jpg

193f428eb942fd09ff85ceefdc67bb7a3f4.jpg

3.提炼类

9772df662136f488b7a86b661d0bb20d537.jpg

4.将类内联化

b9b1b21b317992679da1e111d2c7c0c4639.jpg

5.隐藏委托关系

2cb4940553103e2b437d974c5227c6053e5.jpg

6.移除中间人

6111385276a9a62ddc89d877618f8d4bef9.jpg

7.引入外加函数

b476e2f077c682e4f6a6773fa33bf8f9c5a.jpg

8.引入本地扩展

3bd250a133b2798d0a10588b2d60c107be9.jpg

这个根本没看懂。

第八章:

a3d83a90e66c09c3a7a26d0224aada55278.jpg

1.自封装字段

b3193ffa1a4f18c5f69e7124632f94c164b.jpg

2.以对象取代数据值

a126e084708dc5055b59f8d19c656f88d81.jpg

3.将值对象改为引用对象

46c9e3007177c91db867da7da04c7cfc0d7.jpg

4.将引用对象改为值引用

0bdbe1d336ca6abb8563c884f750b69ea29.jpg

5.以对象代替数组

9e565ca88f32c458e2272c02c16e1a31a90.jpg

6.复制被监视的数据

959ee6094f5f3ab3e01f09c1586b499535f.jpg

7.将单向关联改为双向关联

26b9d4cf77c431a2596cc08653490df57c5.jpg

8.将双向关联改为单向关联

c246dde2fe8f5f27710143cba0092224f32.jpg

7和8都没怎么懂

9.以字面常量取代魔法数

f91664dea05283bdb55ea030b6e2507f060.jpg

10.封装字段

4c4347b55daf168fcd6f40272589eab0798.jpg

11.封装集合

36912ec38fc6fe1e3fa95ce18dc61491ef6.jpg

85032668975633d18170c551b9a2e354f3b.jpg

12.以数据类取代记录

e5c8d4d5e2cc085a2b7294eea3299a9283c.jpg

13.以类取代类型码

02c22e6084ef42c639bddc2a82408773d9f.jpg

 

(未完待续)

转载于:https://my.oschina.net/u/3944601/blog/3067885

一直很喜欢重构这本书,但是由于自己记性不太好,书看过之后其中的方法总是记不住,于是想如果有电子版的重构书就好了,工作中遇到重构的问题可以随时打开查阅。在网上搜索了许久,发现重构这本书有英文chm版本的,而中文版的电子书只有扫描的PDF版本,用起来非常不方便。于是萌生想做一本重构工具书的想法,本来打算自己重新将重构书的内容再整理归类一下,后来发现原书的目录编排就很适合做工具书,包括坏味道分类,重构手法归类等,都有了一个比较系统的整理。因此,我利用空余时间制作了这样的一本中文的chm版重构,希望对大家有所帮助,也算对中国软件业做出一点小小的贡献。 本书基本上是取自”重构”中文版一书的内容,但格式上参照的是chm英文版的格式,还有一些格式小修改,比如第一章的重构前后代码对比。因为时间匆促,个人能力有限,本书难免存在一些缺漏,如果大家发现有问题,随时可以给我发邮件,我会尽快更新错误的内容。 最后再次感谢几位大师 Martin Fowler、Kent Beck等,还有翻译的侯捷和熊节先生,为我们带来这么精彩的一本书。谢谢。 免责声明:本书仅供个人学习研究之用,不得用于任何商业目的,不得以任何方式修改本作品,基于此产生的法律责任本人不承担任何连带责任。
第1章 重构,第一个案例 1 1.1 起点 1 1.2 重构的第一步 7 1.3 分解并重组statement() 8 1.4 运用多态取代与价格相关的条件逻辑 34 1.5 结语 52 第2章 重构原则 53 2.1 何谓重构 53 2.2 为何重构 55 2.3 何时重构 57 2.4 怎么对经理说 60 2.5 重构的难题 62 2.6 重构设计 66 2.7 重构与性能 69 2.8 重构起源何处 71 第3章 代码的坏味道 75 3.1 Duplicated Code(重复代码) 76 3.2 Long Method(过长函数) 76 3.3 Large Class(过大的类) 78 3.4 Long Parameter List(过长参数列) 78 3.5 Divergent Change(发散式变化) 79 3.6 Shotgun Surgery(霰弹式修改) 80 3.7 Feature Envy(依恋情结) 80 3.8 Data Clumps(数据泥团) 81 3.9 Primitive Obsession(基本类型偏执) 81 3.10 Switch Statements(switch惊悚现身) 82 3.11 Parallel InheritanceHierarchies(平行继承体系) 83 3.12 Lazy Class(冗赘类) 83 3.13 Speculative Generality(夸夸其谈未来性) 83 3.14 Temporary Field(令人迷惑的暂时字段) 84 3.15 Message Chains(过度耦合的消息链) 84 3.16 Middle Man(中间人) 85 3.17 Inappropriate Intimacy(狎昵关系) 85 3.18 Alternative Classes with Different Interfaces(异曲同工的类) 85 3.19 Incomplete Library Class(不完美的库类) 86 3.20 Data Class(纯稚的数据类) 86 3.21 Refused Bequest(被拒绝的遗赠) 87 3.22 Comments(过多的注释) 87 第4章 构筑测试体系 89 4.1 自测试代码的价值 89 4.2 JUnit测试框架 91 4.3 添加更多测试 97 第5章 重构列表 103 5.1 重构的记录格式 103 5.2 寻找引用点 105 5.3 这些重构手法有多成熟 106 第6章 重新组织函数 109 6.1 Extract Method(提炼函数) 110 6.2 Inline Method(内联函数) 117 6.3 Inline Temp(内联临时变量) 119 6.4 Replace Temp with Query(以查询取代临时变量) 120 6.5 Introduce Explaining Variable(引入解释性变量) 124 6.6 Split Temporary Variable(分解临时变量) 128 6.7 Remove Assignments to Parameters(移除对参数的赋值) 131 6.8 Replace Method with Method Object(以函数对象取代函数) 135 6.9 Substitute Algorithm(替换算法) 139 第7章 在对象之间搬移特性 141 7.1 Move Method(搬移函数) 142 7.2 Move Field(搬移字段) 146 7.3 Extract Class(提炼类) 149 7.4 Inline Class(将类内联化) 154 7.5 Hide Delegate(隐藏“委托关系”) 157 7.6 Remove Middle Man(移除中间人) 160 7.7 Introduce Foreign Method(引入外加函数) 162 7.8 Introduce Local Extension(引入本地扩展) 164 第8章 重新组织数据 169 8.1 Self Encapsulate Field(自封装字段) 171 8.2 Replace Data Value with Object(以对象取代数据值) 175 8.3 Change Value to Reference(将值对象改为引用对象) 179 8.4 Change Reference to Value(将引用对象改为值对象) 183 8.5 Replace Array with Object(以对象取代数组) 186 8.6 Duplicate Observed Data(复制“被监视数据”) 189 8.7 Change Unidirectional Association to Bidirectional(将单向关联改为双向关联) 197 8.8 Change Bidirectional Association to Unidirectional(将双向关联改为单向关联) 200 8.9 Replace Magic Number with Symbolic Constant(以字面常量取代魔法数) 204 8.10 Encapsulate Field(封装字段) 206 8.11 Encapsulate Collection(封装集合) 208 8.12 Replace Record with Data Class(以数据类取代记录) 217 8.13 Replace Type Code with Class(以类取代类型码) 218 8.14 Replace Type Code with Subclasses(以子类取代类型码) 223 8.15 Replace Type Code with State/Strategy(以State/Strategy取代类型码) 227 8.16 Replace Subclass with Fields(以字段取代子类) 232 第9章 简化条件表达式 237 9.1 Decompose Conditional(分解条件表达式) 238 9.2 Consolidate Conditional Expression(合并条件表达式) 240 9.3 Consolidate Duplicate Conditional Fragments(合并重复的条件片段) 243 9.4 Remove Control Flag(移除控制标记) 245 9.5 Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式) 250 9.6 Replace Conditional with Polymorphism(以多态取代条件表达式) 255 9.7 Introduce Null Object(引入Null对象) 260 9.8 Introduce Assertion(引入断言) 267 第10章 简化函数调用 271 10.1 Rename Method(函数改名) 273 10.2 Add Parameter(添加参数) 275 10.3 Remove Parameter(移除参数) 277 10.4 Separate Query from Modifier(将查询函数和修改函数分离) 279 10.5 Parameterize Method(令函数携带参数) 283 10.6 Replace Parameter with Explicit Methods(以明确函数取代参数) 285 10.7 Preserve Whole Object(保持对象完整) 288 10.8 Replace Parameter with Methods(以函数取代参数) 292 10.9 Introduce Parameter Object(引入参数对象) 295 10.10 Remove Setting Method(移除设值函数) 300 10.11 Hide Method(隐藏函数) 303 10.12 Replace Constructor with Factory Method(以工厂函数取代构造函数) 304 10.13 Encapsulate Downcast(封装向下转型) 308 10.14 Replace Error Code with Exception(以异常取代错误码) 310 10.15 Replace Exception with Test(以测试取代异常) 315 第11章 处理概括关系 319 11.1 Pull Up Field(字段上移) 320 11.2 Pull Up Method(函数上移) 322 11.3 Pull Up Constructor Body(构造函数本体上移) 325 11.4 Push Down Method(函数下移) 328 11.5 Push Down Field(字段下移) 329 11.6 Extract Subclass(提炼子类) 330 11.7 Extract Superclass(提炼超类) 336 11.8 Extract Interface(提炼接口) 341 11.9 Collapse Hierarchy(折叠继承体系) 344 11.10 Form Tem Plate Method(塑造模板函数) 345 11.11 Replace Inheritance with Delegation(以委托取代继承) 352 11.12 Replace Delegation with Inheritance(以继承取代委托) 355 第12章 大型重构 359 12.1 Tease Apart Inheritance(梳理并分解继承体系) 362 12.2 Convert Procedural Design to Objects(将过程化设计转化为对象设计) 368 12.3 Separate Domain from Presentation(将领域和表述/显示分离) 370 12.4 Extract Hierarchy(提炼继承体系) 375 第13章 重构,复用与现实 379 13.1 现实的检验 380 13.2 为什么开发者不愿意重构他们的程序 381 13.3 再论现实的检验 394 13.4 重构的资源和参考资料 394 13.5 从重构联想到软件复用和技术传播 395 13.6 小结 397 13.7 参考文献 397 第14章 重构工具 401 14.1 使用工具进行重构 401 14.2 重构工具的技术标准 403 14.3 重构工具的实用标准 405 14.4 小结 407 第15章 总结 409
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值