简化函数调用
函数改名
将复杂的处理过程分解成小函数。
private String getTelphoneNumber() {
return getTelphoneNumber();
}
private String getOfficeTelphoneNumber() {
return _officeAreaCode + officeNumber();
}
函数改名是一件比较简单的事情,这里有一个注意的地方。我们不可能一次性把所有的调用地方全部修改完,那么此时的做法是:
- 先将旧函数复制一份,然后修改函数名。
- 旧函数里面调用新函数。
- 再找到所有调用旧函数的地方,修改成调用新函数。
- 全部修改完成后,再
- 将旧函数直接删除。
添加参数
修改一个函数,为这个函数参加一个参数。
《重构》作者认为,这不是一种好的重构手法,增加参加就意味着要增加复杂度。如果有其他选择,尽量不要增加参数。
移除参数
与上面的重构动作相反,去除调用函数中多余的参数。
将查询函数和修改函数分离
某个函数既返回对象状态值,又修改对象状态。建立两个不同的函数,其中一个负责查询,另一个负责修改。
String foundMiscreant(String[] people) {
for (int i = 0; i < people.length; i++) {
if (people[i].equals("Don")) {
sendAlert();
return "Don";
}
if (pople[i].equals("John")) {
sendAlert();
reuturn "John";
}
}
return "";
}
void checkSecurity(String[] people) {
String found = foundMiscreant(people);
someLaterCode(found);
}
// 重构后
String foundPerson(String[] people) {
for (int i = 0; i < people.length; i++) {
if (people[i].equals("Don")) {
return "Don";
}
if (people[i].equals("John")) {
return "John";
}
}
return "";
}
void foundMiscreant(String[] people) {
for (int i = 0; i < people.length; i++) {
if (people[i].equals("Don")) {
sendAlert();
return ;
}
if (people[i].equals("John")) {
sendAlert();
return ;
}
}
}
void checkSecurity(String[] people) {
foundMiscreant(people);
String found = foundPerson(people);
someLaterCode(found);
}
令函数携带参数
两个具有相同行为的函数,只是少数几个值导致的行为不同,这种情况下,可以将其统一起来,通过参数来处理变化情况。
在平时写代码的过程中,这也是我们最常用的做法。
// 书中的示例
class Employee {
void temPercentRasise() {
salary *= 1.1;
}
void fivePercentRasise() {
salary *= 1.05;
}
// 重构后
void raise (double factor) {
salary *= (1 + factory);
}
}
以明确函数取代参数
与上面的重构手法相反,一个函数行为取决于函数,将其进行拆分,分别建立独立的函数。
void setValues(String name, int value) {
if (name.equals("height")) {
_height = value;
return;
}
if (name.equals("width")) {
_width = value;
return;
}
Assert.shouldNaverReachHere();
}
// 重构后
void setHeight(int arg) {
_height = arg;
}
void setWidth(int arg) {
_width = arg;
}
另外一个示例
static final int ENGINEER = 0;
static final int SALESMAN = 1;
static final int MANAGER = 2;
static Employee create(int type) {
swithc (type) {
case ENGINEER:
return new Engineer();
case SALESMAN:
return new Salesman();
case MANAGER:
return new Manager();
default:
throw new IllegalArgumentException():
}
}
// 有函数调用的时候会做出如下调用
Employee kent = Epmloyee.create(ENGINEER);
Employee jhon = Emplyee.create(MANAGER);
// 重构后
static Employee createEngineer() {
return new Engineer();
}
static Employee createSalesman() {
return new Salesman();
}
static Employee createManager() {
return new Manager();
}
static Employee create(int type) {
swithc (type) {
case ENGINEER:
return Employee.createEngineer();
case SALESMAN:
return Employee.createSalesman();
case MANAGER:
return Employee.createManager();
default:
throw new IllegalArgumentException():
}
}
// 在调用 create 的地方,进行修改
//调用前
Employee kent = Employee.createEngineer();
// 当所有地方都修改完成后,再将 create 方法删除。同时删除对应常量
保持对象完整
传递参数的过程中,传递多个参数,不如将参数封装起来,直接传递这个对象。
// 书中的示例
int low = daysTempRange().getLow();
int high = daysTempRange().getHigh();
withinPlan = plan.withinRange(low, high);
// 上面的示例,将 low 和 high 做为函数的参数,重构后,将 daysTemRange() 整个参数传递到函数中
// 重构后
withinPlan = plan.withinRange(dayTempRange());
以函数取代参数
如果函数可以通过其他途径获得参数值,那么它就不应该通过参数取得该值。过长的参数列会增加程序阅读者的理解难度,因此我们应该尽可能缩短参数列的长度。
// 书中的示例
public double getPrice() {
int basePrice = _quantity * _itemPrice;
int discountLevel;
if (_quantity > 100) {
discountLevel = 2;
} else {
discountLevel = 1;
}
double finalPrice = discountedPrice(basePrice, discountLevel);
return finalPrice;
}
private double discountedPrice(int basePrice, int discountLevel) {
if (discountLevel == 2) {
return basePrice * 0.1;
} else {
return basePrice * 0.05;
}
}
// 重构后
public double getPrice() {
if (getDiscountLevel() == 2) {
return getBasePrice() * 0.1;
} else {
return getBasePrice() * 0.05;
}
}
private int getBasePrice() {
return _quantity * _itemPrice;
}
private int getDiscountLevel() {
if (_quantity > 100) {
return 2;
} else {
return 1;
}
}
引入参数对象
运用一个对象,将传递的参数列表中的参数封装起来,然后传递这个对象,这样就可以缩短参数列表。
// 书中的示例
class Entity {
private double value;
private Date chargeDate;
Entity(double value, Date chargeDate) {
this.value = value;
this.chargeDate = chargeDate;
}
Date getChargeDate() {
return this.chargeDate;
}
double getValue() {
return this.value;
}
}
class Account {
couble getFlowBetween(Date start, Date end) {
double result = 0;
Enumeration e = this.entiries.elements();
while (e.hasMoreElements()) {
Entity each = (Entity) e.nextElement();
if (each.getDate().equals(start)
|| each.getDate.equals(end)
||(each.getDate().after(start) && each.getDate().before(end))) {
result += each.getValue();
}
}
return result;
}
private Vector entiries = new Vector();
}
// 重构后,将 start, 和 end 参数封装成一个对象 DateRange
class DataRange {
private final Date start;
private final Date end;
DateRange(Date start, Date end) {
this.start = start;
this.end = end;
}
Date getStart() {
return this.start;
}
Date getEnd() {
return this.end;
}
}
class Account {
couble getFlowBetween(DataRange dataRange) {
double result = 0;
Enumeration e = this.entiries.elements();
while (e.hasMoreElements()) {
Entity each = (Entity) e.nextElement();
if (each.getDate().equals(dataRange.getStart())
|| each.getDate.equals(dataRange.getEnd())
||(each.getDate().after(dataRange.getStart()) && each.getDate().before(dataRange.getEnd()))) {
result += each.getValue();
}
}
return result;
}
private Vector entiries = new Vector();
}
移除设值函数
期望类中的某个字段,在创建之后,其值就不要再变化,那么就将 setXXX 方法去除,并且将该字段设置为 final ,只在构造器中提供一个设置值的机会。
隐藏函数
有一个函数,从来没有被其他任何类用到,那么将这个函数设置为 private。在现在的 IDE 中已经有了很充足的检查功能,检查到该函数未使用过,都会提示出来。如果检查到该函数的访问修饰符过大,也会提示出来。
以工厂函数取代构造函数
书中的示例是,创建同一个类的子类对象,但是构造器传递的值不一样,创建不同的子类对象。本质上是将原来应该有的判断语句封装到了一个工厂方法中了。这也符合设计原则中的:发现变化并且封装变化的原则。
封装向下转型
在 Java1.5 中新增了泛型,该场景几乎不会遇到。
以异常取代错误码
这一点,是我觉得有争议的地方。这也体现出了仁者见仁,智者见智的代码风格。有人说,异常应该由错误码来代替。
以测试取代异常
其本质的意思就是,防御性检查。如可能会出异常,使用一条 if 语句,就该种情况处理掉,那么异常就永远不会出现。这对程序员的要求就是,得代码得足够健壮。