1.2重构-第一章

1.2重构的第一步

         每当要进行重构的时候,第一个步骤永远相同:即为将修改的代码建立一组可靠的测试环境,这些测试是必要的,因为尽管遵循重构手法可以使我避免绝大多数引入bug的情形,但我毕竟是人,毕竟有可能犯错,所以我需要可靠的测试。

         接1.1,由于statement()的运作结果是个字符串,所以我首先假设一些客户,让他们每个人各租几部不同的影片,然后产生报表字符串,然后我就可以拿新的字符串和手上已经检查过的参考字符串做比较,运行这些测试只需要几秒钟,所以你会看到我经常运行他们。

         测试过程中很重要的一部分,就是测试程序对于结果的报告方式,他们要么说“OK”,表示所有新字符串都和参考参数一样,要么就列出失败清单,显示问题字符串的出现行号。这些测试都能够自我检验。

         进行重构的时候,我们需要依赖测试,让他告诉我们是否引入bug。好的测试是重构的根本。花时间建立一个优良的测试机制是完全值得的,因为当你修改程序时,好测试会给你必要的安全保障。测试机制在重构领域的地位实在太重要了。

1.3 分解并重组statement()

         第一个明显一起我们注意的就是长得离谱的statement()。每当看到这样长长的函数,我就想把它大卸八块。要知道,代码块越小,代码的功能就愈容易管理,代码的处理和移动也就越轻松。

         本章重构过程的第一个阶段中,我将说明如何把长长的函数切开,并把较小块的代码移至更合适的类。降低代码重复量,从而使新的(打印HTML格式详单的)函数更容易撰写。

       第一步找出代码的逻辑泥潭并运用Extract Method(提炼函数)。本例一个明显的逻辑泥团就是switch语句(计算金额功能),把它提炼到独立函数中似乎比较好。

         和任何重构手法一样,当我提炼一个函数时,我必须知道可能出什么错。如果提炼不好,就可能给程序引入bug。所以重构之前我们要先想出安全做法。可以参考重构列表中的安全步骤(后期补充)。

         首先在这段代码里找出函数内的局部变量和参数。我们找到了两个,each(租赁实体对象)和thisAmount(某种影片的总金额数),前者并未被修改,后者会被修改。任何不会被修改的变量都可以当作参数传入新的函数,至于会被修改的变量就需要格外的小心。如果只有一个变量会被修改,可以把它当作返回值。thisAmount是个临时变量,其值在每次循环起始处被设为0,并且在switch语句之前不会改变,所以可以直接把新函数的返回值赋给它。

         下面将展示重构前后的代码。重构前的代码在上,重构后的代码在下。凡是从函数提炼出来的代码,以及新代码所做的任何修改,只要不明显的都以粗体特别提醒。

 原始代码

/**
 * 顾客实体
 * @author harry
 */
public class Customer {
	private String _name;
	private Vector<Rental> _rentals = new Vector<>();
	public Customer(String _name) {
		super();
		this._name = _name;
	}
	
	public void addRental(Rental arg){
		_rentals.addElement(arg);
	}
	public String getName() {
		return _name;
	}
	public void setName(String _name) {
		this._name = _name;
	}
	
	public String statement(){
		double totalAmount = 0;//总金额
		int frequentRenterPoints = 0;//本次总积分
		
		Enumeration<Rental> rentals = _rentals.elements();
		// 租赁备案
		String result = "Rental Record for "+getName()+"\n";
		while(rentals.hasMoreElements()){
			double thisAmount = 0;
			Rental each = rentals.nextElement();
			
			// 计算金额
			switch(each.get_movie().get_priceCode()){
				case Movie.REGULAR:
					thisAmount += 2;
					if (each.get_dayRented() > 2) {
						thisAmount += (each.get_dayRented()-2)*1.5;
					}
					break;
				case Movie.NEW_RELEASE:
					thisAmount += each.get_dayRented()*3;
					break;
				case Movie.CHILDRENS:
					thisAmount += 1.5;
					if (each.get_dayRented() > 3) {
						thisAmount += (each.get_dayRented()-3)*1.5;
					}
					break;
			}
			
			// 常规积分累加
			frequentRenterPoints++;
			// 特殊新书积分计算
			if (each.get_movie().get_priceCode() == Movie.NEW_RELEASE &&
				each.get_dayRented() > 1) {
				frequentRenterPoints++;
			}
			
			// 显示凭条
			result += "\t"+each.get_movie().get_title()+"\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;
	}
}
重构后代码

/**
 * 顾客实体
 * @author harry
 */
public class Customer01 {
	private String _name;
	private Vector<Rental> _rentals = new Vector<>();
	public Customer01(String _name) {
		super();
		this._name = _name;
	}
	
	public void addRental(Rental arg){
		_rentals.addElement(arg);
	}
	public String getName() {
		return _name;
	}
	public void setName(String _name) {
		this._name = _name;
	}
	
	public String statement(){
		double totalAmount = 0;//总金额
		int frequentRenterPoints = 0;//本次总积分
		
		Enumeration<Rental> rentals = _rentals.elements();
		// 租赁备案
		String result = "Rental Record for "+getName()+"\n";
		while(rentals.hasMoreElements()){
			double thisAmount = 0;
			Rental each = rentals.nextElement();
			
			// 计算金额
			thisAmount = amountFor(each);
			
			// 常规积分累加
			frequentRenterPoints++;
			// 特殊新书积分计算
			if (each.get_movie().get_priceCode() == Movie.NEW_RELEASE &&
				each.get_dayRented() > 1) {
				frequentRenterPoints++;
			}
			
			// 显示凭条
			result += "\t"+each.get_movie().get_title()+"\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;
	}
	
	// 计算金额
	private double amountFor(Rental each){
		double thisAmount = 0;
		switch(each.get_movie().get_priceCode()){
		case Movie.REGULAR:
			thisAmount += 2;
			if (each.get_dayRented() > 2) {
				thisAmount += (each.get_dayRented()-2)*1.5;
			}
			break;
		case Movie.NEW_RELEASE:
			thisAmount += each.get_dayRented()*3;
			break;
		case Movie.CHILDRENS:
			thisAmount += 1.5;
			if (each.get_dayRented() > 3) {
				thisAmount += (each.get_dayRented()-3)*1.5;
			}
			break;
	}
		return thisAmount;
	}
}

         现在我们已经把原来的函数分为两块,可以分别处理它们。但amountFor()内的某些变量名称不太可爱,所以要修改掉,修改这些变量名是代码清晰的关键。

修改变量名后的代码:

private double amountFor(Rental aRental) {
	double result = 0;
	switch (aRental.get_movie().get_priceCode()) {
	case Movie.REGULAR:
		result += 2;
		if (aRental.get_dayRented() > 2) {
			result += (aRental.get_dayRented() - 2) * 1.5;
		}
		break;
	case Movie.NEW_RELEASE:
		result += aRental.get_dayRented() * 3;
		break;
	case Movie.CHILDRENS:
		result += 1.5;
		if (aRental.get_dayRented() > 3) {
			result += (aRental.get_dayRented() - 3) * 1.5;
		}
		break;
	}
	return result;
}

 最终UML如下图


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值