一.代码的坏味道
1.重复代码
书中将"重复代码"作为代码的坏味道第一点,足以说明"重复代码"的危害。“坏味道行列中首当其冲的就是重复代码”。最单纯的重复代码就是“同一个类的两个函数含有相同的表达式”。这时候你需要使用提炼函数提炼出重复的代码,然后让这两个地点都调用被提炼出来的那一段代码。
2.过长函数
拥有短函数的对象会获得比较好,比较长。“程序越长越难以理解”,你应该积极地分解函数。我们需要遵循这样一条原则:没当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西写进一个独立函数中,并以其用途(而非实现手法)命名。如何确定该提炼哪一段代码呢?一个好的技巧是:寻找注释,如果代码前方有一行注释,就是在提醒你,可以将这段代码替换成一个函数,并且可以在注释的基础上给这个函数命名。条件表达式和循环常常也是提炼的信号。
3.过大的类
如果想利用单个类做太多事情,其内往往就会出现太多实例变量,一旦如此,重复代码也就接踵而至了。类内如果有太多代码,也是重复代码,混乱并最终走向死亡的源头。最简单的解决方案是把多余的东西消弭于类内部。如果有五个“百行函数”,他们之中很多代码都相同,那么或许你可以把他们变成五个“十行函数”和十个提炼出来的“双行函数”。
4.过长参数列
太长的参数列难以理解,太多参数会造成前后不一致,不易使用,而且一旦你需要更多数据,就不得不修改它。如果将对象传递给函数,大多数修改都将没有必要。
5.数据泥团
我们常常看到相同的三四项数据总是绑在一起出现,比如许多函数签名中相同的参数,两个类中相同的字段等,这些数据应该拥有属于它们自己的对象。
6.冗余类
我们创建的每一个类,都需要有人去理解和维护,如果一个类的所得不值其身价,它就应该消失。
二.重新组织函数
1.提炼函数
你有一段代码可以被组织在一起并独立出来,将这段代码放进一个独立函数中,并让函数名称解释该函数的用途。
void printOwing(double amount){
printBanner();
System.out.println("name:"+_name);
System.out.println("amount:"+amount);
}
改写为:
void printOwing(double amount){
printBanner();
printDetail(amount);
}
void printDetail(double amount){
System.out.println("name:"+_name);
System.out.println("amount:"+amount);
}
提炼函数就是尽量将大的函数拆开,提取其中有用的,可能被其他函数用到的部分,单独提炼到一个新的函数中去,是解决重复代码,解决大类大方法的主要手段。
简短而命名良好的函数有以下好处:首先,如果每个函数的粒度都很小,那么函数被复用的机会就更大。其次,这会使高层函数读起来就像一系列注释。再次,如果函数都是细粒度,那么函数的覆写也会更容易些。
2.内联临时变量
你有一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其他重构手法。
double basePrice = anOrder.basePrice();
return (basePrice > 1000);
改写为:
return (anOrder.basePrice() > 1000);
这里说明的是,没必要存在的临时变量,就不要存在。
3.分解临时变量
你的程序有某个临时变量被赋值超过一次,它既不是循环变量,也不被用于收集计算结果。
这里强调的是临时变量只能被赋值一次,不要多次使用。
4.移除对参数的赋值
如果一段代码对一个参数进行赋值,用一个临时变量取代该参数的位置。
int discount(int inputVal,int quantity,int yearToDate){
if(inputVal > 50){inputCal -=2;}
}
int discount(int inputVal,int quantity,int yearToDate){
int result = inputVal;
if(inputVal > 50){result -=2;}
}
对参数赋值意思是:如果你把一个名为foo的对象作为参数传给某个函数,那么"对参数赋值"意味改变foo,使它引用另一个对象。而不是指在"被传入对象"身上进行什么操作。
三.在对象之间搬移特性
1.搬移函数
你的程序中,有个函数与其所驻类之外的另一个类进行更多交流,调用后者,或被后者调用,在该函数最常引用的类中建立一个有着类似行为的新函数,将旧函数变成一个单纯的委托函数,或是将旧函数完全移除。
搬移函数是重构理论的支柱。如果一个类有太多行为,或者如果一个类和另一个类有太多合作而形成高度耦合,我们就应该搬移函数。即,当一个函数使用另一个对象的次数比使用自己所驻对象的次数还多,就需要搬移函数。
2.搬移字段
程序中,某个字段被其所驻类之外的另一个类更多地用到。在目标类新建一个字段,修改源字段的所有用户,令它们改用新字段。
3.提炼类
某个类做了应该由两个类做的事,建立一个新类,将相关的字段和函数从旧类搬移到新类。一个类应该是一个清楚的抽象,处理一些明确的责任,满足单一职责。
四.简化条件表达式
1.分解条件表达式
有一个复杂的条件语句,从if,then,else三个段落分别提炼出独立函数
if(date.before(SUMMER_START) || date.after(SUMMER_END)){
charge = quantity * _winterRate + _winterServiceCharge;
}else{
charge = quantity * _summerRate;
}
if(notSummer(date)){
charge = winterCharge(quantity);
}else{
charge = summerCharge(quantity);
}
2.合并条件表达式
有一系列条件测试,都得到相同结果,将这些测试合并为一个条件表达式,并将这个条件表达式提炼成为一个独立函数。
double disabilityAmount(){
if(_seniority < 2) return 0;
if(_monthsDisabled > 12) return 0;
if(_isPartTime) return 0;
}
double disabilityAmount(){
if(isNotEligibleForDisability()) return 0;
}
3.合并重复的条件片段
在条件表达式的每个分支上都有着相同的一段代码,将这段重复代码版一到条件表达式之外。
if(isSpecialDeal()){
total = price * 0.95;
send();
}else{
total = price * 0.98;
send();
}
if(isSpecialDeal()){
total = price * 0.95;
}else{
total = price * 0.98;
}
send();
4.移除控制标记
在一系列布尔表达式中,某个变量带有控制标记的作用,用break语句或return语句取代控制标记。
5.以卫语句取代嵌套条件表达式
函数中的条件逻辑使人难以看清正常的执行路径,使用卫语句表现所有的特殊情况。
如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回。这样的单独检查常常被称为"卫语句"。
double getPayAmount(){
double result;
if(_isDead) result = deadAmount();
else{
if(_isSeparated) result = separatedAmount();
else{
if(_isRetired) result = retiredAmount();
else result = normalPayAmount();
}
}
return result;
}
double getPayAmount(){
if(_isDead) return deadAmount();
if(_isSeparated) return separatedAmount();
if(_isRetired) return retiredAmount();
return normalPayAmount();
}
五.简化函数调用
1.函数改名
如果函数的名称未能揭示函数的用途,修改函数名称。
2.控制参数
如果某个函数需要从调用端得到更多信息,为此函数添加一个对象参数,让该对象带进函数所需信息。但是注意不要引起过长的参数列。
如果函数本体不再需要某个参数,就将该参数移除。
3.将查询函数和修改函数分离
某个函数即返回对象状态值,又修改对象状态,建立两个不同的函数,其中一个负责查询,另一个负责修改。
4.以函数取代参数
对象调用某个函数,并将所得结果作为参数,传递给另一个函数。而接受该参数的函数本身也能够调用前一个函数,让参数接受者去除该项参数,并直接调用前一个函数。
int basePrice = _quantity * _itemPrice;
discountLevel = getDiscountLevel();
double finalPrice = discountedPrice(basePrice,discountLevel);
int basePrice = _quantity * _itemPrice;
double finalPrice = discountedPrice(basePrice);
如果函数可以通过其他途径获得参数值,就不应该通过参数取得该值。
5.引入参数对象
某些参数总是很自然地同时出现,以一个对象取代这些参数。
6.以异常取代错误码
某个函数返回一个特定的代码,用以表示某种错误情况,应该改为异常。
int withdraw(int amount){
if(amount > _balance){
return -1;
}else{
_balance -= amount;
return 0;
}
}
void withdraw(int amount) throws BalanceException{
if(amount > _balance) throws BalanceException;
_balance -= amount;
}