重构-改善既有代码设计(一)
一、重构的风险
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

浙公网安备 33010602011771号