日期:2014-05-18  浏览次数:20903 次

《重构》C#版实现(二)抽取AmountFor方法

上一篇讲了如何使用原始(待重构的)代码构建自动单元测试,以保障后面重构的进行。如果你跟着一步一步地做,会发现,自动测试绝对是重构过程必不可少的工具。那么现在开始真刀真枪地进行重构。

等等,在开始前的最后一刻,还需要提醒的是,也许你带着满腔热情,想把代码重构得优雅极致,但就像平时的工作一样,重构的过程更需要的是理性思考,而不是冲动。每一次重构实践,都应该包含了对设计、实现、可维护、可扩展性,以及成本的估算和权衡。

所以,首先看看对于Statement方法来说,从哪里入手比较合适?

public string Statement()
{
	double totalAmount = 0;
	int frequentRenterPoints = 0;
	string result = "Rental Record for " + Name + "\n";
	foreach (Rental rental in Rentals)
	{
		double thisAmount = 0;
		
		// determine amounts for each line
		switch (rental.Movie.PriceCode)
		{
			case Movie.REGULAR:
				thisAmount += 2;
				if (rental.DaysRented > 2)
					thisAmount += (rental.DaysRented - 2) * 1.5;
				break;
			case Movie.NEW_RELEASE:
				thisAmount += rental.DaysRented * 3;
				break;
			case Movie.CHILDRENS:
				thisAmount += 1.5;
				if (rental.DaysRented > 3)
					thisAmount += (rental.DaysRented - 3) * 1.5;
				break;
		}

		// add frequent renter points
		frequentRenterPoints++;
		// add bonus for a two day new release rental
		if (rental.Movie.PriceCode == Movie.NEW_RELEASE &&
			rental.DaysRented > 1) frequentRenterPoints++;
		
		// show figures for this rental
		result += "\t" + rental.Movie.Title + "\t" + thisAmount.ToString() + "\n";
		totalAmount += thisAmount;
	}
	// add footer lines
	result += "Amount owed is " + totalAmount.ToString() + "\n";
	result += "You earned " + frequentRenterPoints.ToString() + " frequent renter points";
	return result;
}
实际上,应该换一个问题:对现在的程序来说,哪里最容易产生变化?毕竟,如果代码不会变化,重构就是多余的行为。优雅的代码只能满足程序员的审美需求,而不是客户对于功能的需求。
《重构》中指出:该程序最可能产生的变化有三点:
1.报告输出的类型可能变化,例如由普通字符串变成HTML格式的文本
2.计费方式可能发生变化
3.影片类型可能发生变化
无论哪一种变化,上面的Statement方法都不能很好地应对——它太胖了,涉及的逻辑、细节太多。所以,第一步,《重构》的作者选择将该方法中最长、并且同时涉及上述所有变化的,计算每一部影片花费的那个switch给抽取出去。
具体步骤是:
1.在Customer中新建一个计算花费的新方法AmountFor:
public int AmountFor(Rental rental)
{
    return 0;
}
2.把switch的代码copy(不是剪切)到AmountFor里:
public int AmountFor(Rental rental)
{
	switch (rental.Movie.PriceCode)
	{
		case Movie.REGULAR:
			thisAmount += 2;
			if (rental.DaysRented > 2)
				thisAmount += (rental.DaysRented - 2) * 1.5;
			break;
		case Movie.NEW_RELEASE:
			thisAmount += rental.DaysRented * 3;
			break;
		case Movie.CHILDRENS:
			thisAmount += 1.5;
			if (rental.DaysRented > 3)
				thisAmount += (rental.DaysRented - 3) * 1.5;
			break;
	}
	return 0;
}

3.但此时的代码是编译不了得,因为AmountFor中,thisAmount不存在。所以,在switch前面添加一个thisAmount的声明:

public int AmountFor(Rental rental)
{
	int thisAmount = 0;
	switch (rental.Movie.PriceCode)
	{
		case Movie.REGULAR:
			thisAmount += 2;
			if (rental.DaysRented > 2)
				thisAmount += (rental.DaysRented - 2) * 1.5;
			break;
		case Movie.NEW_RELEASE:
			thisAmount += rental.DaysRented * 3;
			break;
		case Movie.CHILDRENS:
			thisAmount += 1.5;
			if (rental.DaysRented > 3)
				thisAmount += (rental.DaysRented - 3) * 1.5;
			break;
	}
	return thisAmount;
}
4.此时,代码还是编译不了,因为花费计算时用到了小数,而thisAmount是整数。加上强制转换后:
public int AmountFor(Rental rental)
{
	int thisAmount = 0;
	switch (rental.Movie.PriceCode)
	{
		case Movie.REGULAR:
			thisAmount += 2;
			if (rental.DaysRented > 2)
				thisAmount += (int)((rental.DaysRented - 2) * 1.5);
			break;
		case Movie.NEW_RELEASE:
			thisAmount += rental.DaysRented * 3;
			break;
		case Movie.CHILDRENS:
			thisAmount += (int)1.5;
			if (rental.DaysRented > 3)
				thisAmount += (int)((rental.DaysRented - 3) * 1.5);
			break;
	}
	return thisAmount;
}
5.在认为新的方法完成后,去掉Statement中的switch代码块,变成:
public string Statement()
{
	double totalAmount = 0;
	int frequentRenterPoints = 0;
	string result = "Rental Record for " + Name + "\n"