重构改善既有代码的设计--读书笔记 1

重构是在不改变软件可观察行为的前提下改善其内部结构。

在软件的设计前期过度的追求设计模式常常导致过度工程,单凭对完美的追求,无法写出实用的代码。

保持代码易读、易修改的关键就是重构。

重构具有风险,他必须修改运作中的程序,这可能会引入一些不易察觉的错误。设计模式为重构提供了目标。

从一个案例中入手。按照书中的例子,我在这里又复制了一遍:

案例:

是一个影片出租店的项目 ,需要计算每一位顾客的消费金额,并打印详单。使用者提供客户租了影片、租期多长的数据之后由程序根据影片的类型和租期来计算最终的费用。影片分为三类:普通片、儿童片、新片。 除了计算出费用还需要为客户计算积分,积分会根据影片是否为新片会不同。

代码:

Movie 实体类:

package com.wc.study.demo01;

public class Movie {
	public static final int NEW_RELEASE = 1;
	public static final int REGULAR = 0;
	public static final int CHILDRENS = 2;
	
	private String title;
	private int priceCode;
	
	public Movie(String title, int priceCode) {
		super();
		this.title = title;
		this.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;
	}
	
	

}

Rental 租赁  实体类:

package com.wc.study.demo01;
//表示某客户租了一步影片
public class Rental {
	private Movie movie;
	private int daysRented;
	
	public Rental(Movie movie, int daysRented) {
		super();
		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;
	}
}

客户实体类:

package com.wc.study.demo01;

import java.util.Enumeration;
import java.util.Vector;

public class Customer {
	private String name;
	private Vector<Rental> vectors = new Vector<>();
	
	public Customer(String name) {
		this.name = name;
	}
	
	public void addRental(Rental rental){
		vectors.add(rental);
	}
	
	public String getName(){
		return name;
	}
	
	public String statement(){
		double totalAmout = 0;
		int integral = 0;
		String result = "current customer is " + name + " rental \n";
		Enumeration<Rental> rentals = vectors.elements();
		while(rentals.hasMoreElements()){
			double thisAmout = 0;
			Rental each = rentals.nextElement();
			//计算金额
			switch(each.getMovie().getPriceCode()){
				case Movie.REGULAR:
					thisAmout += 2;
					if(each.getDaysRented() > 2){
						thisAmout += (each.getDaysRented() - 2) * 1.5;
					}
					break;
				case Movie.NEW_RELEASE:
					thisAmout += each.getDaysRented() * 3;
					break;
				case Movie.CHILDRENS:
					thisAmout += 1.5;
					if(each.getDaysRented() > 3){
						thisAmout += (each.getDaysRented() - 3) * 1.5;
					}
					break;
			}
			//计算积分
			integral++;
			if(each.getMovie().getPriceCode() == Movie.NEW_RELEASE &&
					each.getDaysRented() > 1){
				integral++;
			}
			
			totalAmout += thisAmout;
			result += "\t" +each.getMovie().getTitle() +"\t" + thisAmout  + "\n";
		}
		result += " your cost is " + totalAmout + " increment integral :" + integral;
		return result; 
	}
}

总体来说看:当前的代码在当前的需求上是安全没有问题,如果只是一个小小的系统,那么使用当前的代码没有问题,但是如果是一个非常复杂的系统,那么当前的代码完全没有面向对象的精神。customer中的statement() 方法做的事情相当的多。如果目前需要修改需求,需要输出的详单是html 格式的,那么当前的代码完全不符,可以复制当前的代码新增一个一模一样的,只是修改其中的输出语句,就可以完成需求,大量充斥着statement() 方法。 当计费的标准发生改变又得再复制一遍?

如果你发现你需要为当前的代码新增一个特性,而当前的代码不能够很方便的达成这个目的,那么就需要先对当前的代码进行重构,使得特性添加比较容易的进行,然后再添加特性。

 

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

因为重构代码很可能为原来的代码引入新的bug,需要牢固可靠的测试。

进行重构的时候我们需要依赖测试,好的测试是重构的根本,花时间建立一个良好的测试是完全值得的。而且测试需要具备自我检验能力李,免去我们逐行进行比对,降低开发效率。

 

针对当前的代码,首先引入眼帘想要去修改的地方就是 statement() 方法,代码块越小,代码的功能就越容易管理,代码的处理和移动也就越松动

针对当前的statement方法,首先需要找出代码中的逻辑泥团,及本例中的switch 方法,然后找到本例中的局部变量,each thisAmount,each在其中并没有被修改,而thisAmount是每次判断的价格,每次进入都会重新赋值,可以将其当做返回值来处理。

 

第一步修改之后的statement 方法:(将计算每一步影片的价格独立出去了!)

public String statement() {
		double totalAmout = 0;
		int integral = 0;
		String result = "current customer is " + name + " rental \n";
		Enumeration<Rental> rentals = vectors.elements();
		while (rentals.hasMoreElements()) {
			double thisAmout = 0;
			Rental each = rentals.nextElement();
			// 计算金额
			thisAmout = getAmount(each);
			// 计算积分
			integral++;
			if (each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() > 1) {
				integral++;
			}

			totalAmout += thisAmout;
			result += "\t" + each.getMovie().getTitle() + "\t" + thisAmout + "\n";
		}
		result += " your cost is " + totalAmout + " increment integral :" + integral;
		return result;
	}

	private double getAmount(Rental each) {
		double thisAmout = 0;
		switch (each.getMovie().getPriceCode()) {
		case Movie.REGULAR:
			thisAmout += 2;
			if (each.getDaysRented() > 2) {
				thisAmout += (each.getDaysRented() - 2) * 1.5;
			}
			break;
		case Movie.NEW_RELEASE:
			thisAmout += each.getDaysRented() * 3;
			break;
		case Movie.CHILDRENS:
			thisAmout += 1.5;
			if (each.getDaysRented() > 3) {
				thisAmout += (each.getDaysRented() - 3) * 1.5;
			}
			break;
		}
		return thisAmout;
	}

tips : 你可以将扩展出来的方法 修改其中的参数名字  以起到见名思意的作用。

修改之后的扩展方法:

private double getAmount(Rental rental) {
		double amount = 0;
		switch (rental.getMovie().getPriceCode()) {
		case Movie.REGULAR:
			amount += 2;
			if (rental.getDaysRented() > 2) {
				amount += (rental.getDaysRented() - 2) * 1.5;
			}
			break;
		case Movie.NEW_RELEASE:
			amount += rental.getDaysRented() * 3;
			break;
		case Movie.CHILDRENS:
			amount += 1.5;
			if (rental.getDaysRented() > 3) {
				amount += (rental.getDaysRented() - 3) * 1.5;
			}
			break;
		}
		return amount;
	}

更改代码中变量的名称是值得的吗?好的代码应该清楚的表达出自己的功能,变量名称是代码功能的关键。

任何一个傻瓜都可以写出计算机能理解的代码,唯有写出人类能理解的代码,才是优秀的程序员!

代码应该表现自己的目的,这一点非常重要。

 

在上面修改的基础上,我们发现当前计算金额的代码与Customer类没有大方向上的关系,考虑将其移动到Rental 类中,移动之后需要进行修改原来的参数,以适应新家。(我们将原来在Customer中的代码 移动到了 Rental中来)

public double getCharge() {
		double amount = 0;
		switch (getMovie().getPriceCode()) {
		case Movie.REGULAR:
			amount += 2;
			if (getDaysRented() > 2) {
				amount += (getDaysRented() - 2) * 1.5;
			}
			break;
		case Movie.NEW_RELEASE:
			amount += getDaysRented() * 3;
			break;
		case Movie.CHILDRENS:
			amount += 1.5;
			if (getDaysRented() > 3) {
				amount += (getDaysRented() - 3) * 1.5;
			}
			break;
		}
		return amount;
	}

修改之后,我们发现当前的局部变量 thisAmount 就变的没有什么用了,我们将使用方法调用来替换当前的局部变量(修改之后,方法会执行多次,我们在后续的重构与性能优化中再对其进行修改)。

public String statement() {
		double totalAmout = 0;
		int integral = 0;
		String result = "current customer is " + name + " rental \n";
		Enumeration<Rental> rentals = vectors.elements();
		while (rentals.hasMoreElements()) {
			Rental each = rentals.nextElement();
			// 计算积分
			integral++;
			if (each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() > 1) {
				integral++;
			}

			totalAmout += each.getCharge();
			result += "\t" + each.getMovie().getTitle() + "\t" + each.getCharge() + "\n";
		}
		result += " your cost is " + totalAmout + " increment integral :" + integral;
		return result;
	}

临时变量往往会引发问题,他们会导致大量的参数被传来传去,而这完全没有必要,我们在看代码时很容易跟丢他们。

 

 

将计算积分的部分提炼到Rental中去,(观察方法的上下文,可以看到的是积分在每次循环中或取出来之后只是参与了一次累加,没有其他用处 替换方法如下:)

public int getIntegral() {
		int integral = 0;
		integral++;
		if (getMovie().getPriceCode() == Movie.NEW_RELEASE && getDaysRented() > 1) {
			integral++;
		}
		return integral;
	}
public String statement() {
		double totalAmout = 0;
		int integral = 0;
		String result = "current customer is " + name + " rental \n";
		Enumeration<Rental> rentals = vectors.elements();
		while (rentals.hasMoreElements()) {
			Rental each = rentals.nextElement();
			//增加积分
			integral += each.getIntegral();
			//增加金额
			totalAmout += each.getCharge();
			result += "\t" + each.getMovie().getTitle() + "\t" + each.getCharge() + "\n";
		}
		result += " your cost is " + totalAmout + " increment integral :" + integral;
		return result;
	}

 

 

再次修改原先的局部变量  总金额  总积分(将计算总金额、总积分的方法独立出来到Customer 类中,):

public String statement() {
		String result = "current customer is " + name + " rental \n";
		Enumeration<Rental> rentals = vectors.elements();
		while(rentals.hasMoreElements()){
			double thisAmout = 0;
			Rental each = rentals.nextElement();
			result += "\t" +each.getMovie().getTitle() +"\t" + thisAmout  + "\n";
		}
		result += " your cost is " + getTotalAmout() + " increment integral :" + getTotalIntegral();
		return result;
	}
	
	public double getTotalAmout(){
		double totalAmout = 0;
		Enumeration<Rental> rentals = vectors.elements();
		while (rentals.hasMoreElements()) {
			Rental each = rentals.nextElement();
			//增加金额
			totalAmout += each.getCharge();
		}
		return totalAmout;
	}
	
	public int getTotalIntegral(){
		int totalIntegral = 0;
		Enumeration<Rental> rentals = vectors.elements();
		while (rentals.hasMoreElements()) {
			Rental each = rentals.nextElement();
			//增加积分
			totalIntegral += each.getIntegral();
		}
		return totalIntegral;
	}

这样初版的代码就已经完成了,我们可以将statement() 方法再次改造一下,让其符合输出为html格式的详单。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值