昨天讲了那么多坏味道,今天就说下该怎样清理那些坏味道。
1.提炼函数(Extract Method)
将一段代码放进一个独立函数中,并让函数名称解释该函数的用途。
void printOwing(double amount){ printBanner(); //print details System.out.println("name:" + _name); System.out.println("amount" + amount); }
↓↓↓↓↓↓↓↓替换
void printOwing(double amount){ printBanner(); printDetails(amount); } void printDetails(double amount){ System.out.println("name:" + _name); System.out.println("amount" + amount); }
动机:简短而命名良好的函数有以下几个优点:
- 如果每个函数的粒度都很小,那么函数被复用的机会就更大
- 会使高层函数读起来就像一系列注释
- 如果函数都是细粒度,那么函数的覆写也会更容易些
注意:创建函数时,根据这个函数的意图来对它命名(以它“做什么”来命名,而不是以它“怎样做”命名)
2.内联函数(Inline Method)
在函数调用点插入函数本体,然后移除该函数。
int getRating(){ return (moreThanFiveLateDeliveries()) ? 2 : 1; } boolean moreThanFiveLateDeliveries(){ return _numberOfLateDeliveries > 5; }
↓↓↓↓↓↓↓↓替换
int getRating(){ return (_numberOfLateDeliveries > 5) ? 2 : 1; }
动机:当你遇到某些函数,其内部代码和函数名称同样清晰易读时,就可以试试“内联函数”。
如果使用了太多间接层,使得系统中的所有函数都似乎只是对另一个函数的简单委托,那就应该去除无用的间接层
3.内联临时变量(Inline Temp)
将所有对该变量的引用动作,替换为对它赋值的那个表达式自身。
double basePrice = anOrder.basePrice(); return (basePrice > 100) ↓↓↓↓↓↓↓↓替换 return (anOrder.basePrice() > 100)
动机:Inline Temp多半是作为Replace Temp with Query的一部分使用,唯一单独使用的情况是:发现某个临时变量被赋予某个函数调用的返回值。
4.以查询取代临时变量(Replace Temp With Query)
将这个表达式提炼到一个独立函数中,将这个临时变量的所有引用点替换为对新函数的调用,此后,新函数就可被其他函数使用
double basePrice = _quantity * _itemPrice; if (basePrice > 1000) return basePrice * 0.95; else return basePrice * 0.98; ↓↓↓↓↓↓↓↓替换 if (basePrice() > 1000) return basePrice() * 0.95; else return basePrice() * 0.98; ... double basePrice(){ return _quantity * _itemPrice; }
动机:临时变量的问题在于:它们是暂时的,而且只能在所属函数内使用,如果把临时变量替换为一个查询,那么同一个类中的所有函数都将可以获得这份信息
Replace Temp with Query往往是运用Extract Method之前必不可少的一个步骤,局部变量会使代码难以被提炼,所以应该尽可能把它们替换为查询式
5.引入解释性变量(Introduce Explaning Variable)
将该复杂表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途
if ((platform.toUpperCase().indexOf("MAC") > -1) && (brower.toUpperCase().indexOf("IE") > -1) && wasInitialized() && resize > 0) { // do something } ↓↓↓↓↓↓↓↓替换 final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1; final boolean isIEBrowser = brower.toUpperCase().indexOf("IE") > -1; final boolean wasResized = resize > 0; if (isMacOs && isIEBrowser && wasInitialized() && wasResized){ // do something }
动机:表达式有可能非常复杂而难以阅读,这种情况下,临时变量可以帮助将表达式分解为比较容易管理的形式。使用这项重构的另一种情况是,在较长算法中,可以运用临时变量来解释每一步运算的意义
6.分解临时变量(Split Temporary Variable)
针对每次赋值,创造一个独立、对应的临时变量
double temp = 2 * (_height + _width); System.out.println(temp); temp = _height * _width; System.out.println(temp); ↓↓↓↓↓↓↓↓替换 final double perimeter = 2 * (_height + _width); System.out.println(temp); final double area = _height * _width; System.out.println(area);
动机:很多临时变量用于保存一段冗长代码的运算结果,以便稍后使用,这种临时变量应该只被赋值一次,如果它们被赋值超过一次,就意味着它们在函数中承担了一个以上的责任
,如果临时变量承担多个责任,就应该被替换(分解)为多个临时变量,每个变量只承担一个责任
7.移除对参数的赋值(Remove Assignments to Parameters)
以一个临时变量取代该参数的位置
int discount(int inputVal, int quantity, int yearToDate){ if (inputVal > 50) inputVal -= 2; ↓↓↓↓↓↓↓↓替换 int discount(int inputVal, int quantity, int yearToDate){ int result = inputVal; if (inputVal > 50) result -= 2;
动机:如果把一个名为foo的对象作为参数传给某个函数,那么”对参数赋值”意味改变foo,使它引用另一个对象,这会降低代码的清晰度,而且混用了按值传递和按引用传递两种参数传递方式。如果在”被传入对象”身上进行操作就没有问题
8.以函数对象取代函数(Replace Method with Method Object)
如果有一个大型函数,可以将这个函数放进一个单独对象中,如此一来局部变量就成了对象内的字段,然后就可以在同一个对象中将这个大型函数分解为多个小型函数
Class Account int gamma(int inputVal, int quantity, int yearToDate){ int importantValue1 = (inputVal * quantity) + delta(); int importantValue2 = (inputVal * yearToDate) + 100; if ((yearToDate - importantValue1) > 100) importantValue2 -= 20; int importantValue3 = importantValue2 * 7; // and so on. return importantValue3 - 2 * importantValue1; } ↓↓↓↓↓↓↓↓替换 class Gamma... private final Account _account; private int inputVal; private int quantity; private int yearToDate; private int importantValue1; private int importantValue2; private int importantValue3; Gamma(Account source, int inputValArg, int quantityArg, int yearToDateArg){ _account = source; inputVal = inputValArg; quantity = quantityArg; yearToDate = yearToDateArg; } int compute(){ importantValue1 = (inputVal * quantity) + _account.delta(); importantValue2 = (inputVal * yearToDate) + 100; importantThing(); int importantValue3 = importantValue2 * 7; // and so on. return importantValue3 - 2 * importantValue1; } void importantThing(){ if ((yearToDate - importantValue1) > 100) importantValue2 -= 20; } class Account int gamma(int inputVal, int quantity, int yearToDate){ return new Gamma(this, inputVal, quantity, yearToDate).compute(); }
动机:局部变量的存在会增加函数分解难度。如果一个函数之中局部变量泛滥成灾,那么想分解这个函数就非常困难。
9.替换算法(Substitute Algorithm)
将函数本体替换为另一个算法
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"; } if(people[i].equals("Kent")){ return "Kent"; } } return ""; } ↓↓↓↓↓↓↓↓替换 String foundPerson(String[] people){ List candidates = Arrays.asList(new String[]{"Don", "John", "Kent"}); for(int i = 0; i < people.length; i++){ if(candidates.contants(people[i])){ return people[i]; } return ""; } }
动机:如果发现做一件事可以有更清晰的方式,就应该以较清晰的方式取代复杂的方式
10.搬移函数(Move Method)