函数改名
如果一个函数未能揭示函数的用途,那么请通过 Rename Method(函数改名) 修改函数名称。
1
   | function circum(radius) {...}
  | 
 
1
   | function circumference(radius) {...}
  | 
 
 好处
- 增加代码可读性。尝试为新方法指定一个反映其功能的名称。像
createOrder(),renderCustomerInfo()等。 
 添加参数
假设某个函数需要从欧冠调用端得到更多信息,那么可以尝试通过 Add Parameter(添加参数) 为此函数添加一个对象参数,让该对象带进函数所需信息。
1
   | function getContact() {...}
  | 
 
1
   | function getContact(var date) {...}
  | 
 
 注意
除了添加参数外,你常常还有其他选择,只要可能,都比添加参数要好,因为它们不会增加参数列表长度。过长参数往往伴随着坏味道Data Clumps。
可选做法:
 好处
- 您可以选择添加新参数或者添加包含方法所需数据的新私有字段。 当您需要偶尔或经常更改的数据时,最好将字段保存在对象中。
 
 移除参数
如果函数体不再需要某个参数了,请 Remove Parameter(移除参数) 。
1
   | function getContact(var date) {...}
  | 
 
1
   | function getContact() {...}
  | 
 
 好处
 注意
如果方法在子类或超类中以不同方式实现,并且您的参数在这些实现中使用,请保持参数不变。
 将查询函数和修改函数分离
如果某个函数既返回对象状态,有修改对象状态,那么通过 Separate Query from Modifier(将查询函数和修改函数分离) 建立两个不同的函数,其中一个负责查询,另一个负责修改,任何有返回值的函数,都不应该有看得到的副作用。
1 2 3 4 5
   | function getTotalOutstandingAndSendBill() {   const result = customer.invoices.reduce((total, each) => each.amount + total, 0);   sendBill();   return result; }
  | 
 
1 2 3 4 5 6
   | function totalOutstanding() {   return customer.invoices.reduce((total, each) => each.amount + total, 0); } function sendBill() {   emailGateway.send(formatBill(customer)); }
  | 
 
获取数据的代码一般被命名为query。
 好处
- 如果您的查询没有改变程序的状态,那么您可以根据需要多次调用它,而不必担心因调用该方法而导致意外更改数据;
 
- 但请记住,仅在修改器改变对象的可见状态的情况下,副作用才是危险的。例如,这些可以是从对象的公共接口,数据库中的条目,文件等中访问的字段。如果修饰符仅缓存复杂的操作并将其保存在类的私有字段中,则几乎不会导致任何一方效果。
 
 令函数携带参数
如果若干个函数做了类似的工作,但是在函数本地中却包含了不同的值,那么尝试使用 Parameterize Method(令函数携带参数) 建立单一函数,以参数表达那些不同的值。
1 2 3 4 5 6
   | function tenPercentRaise(aPerson) {   aPerson.salary = aPerson.salary.multiply(1.1); } function fivePercentRaise(aPerson) {   aPerson.salary = aPerson.salary.multiply(1.05); }
  | 
 
1 2 3 4 5 6
   | function tenPercentRaise(aPerson) {   aPerson.salary = aPerson.salary.multiply(1.1); } function fivePercentRaise(aPerson) {   aPerson.salary = aPerson.salary.multiply(1.05); }
  | 
 
 注意
- 有时这种重构技术可能会把你带偏,导致一个漫长而复杂的常用方法,而不是多个简单的方法;
 
- 将功能的激活/取消激活移动通过参数标识的时候也要小心。这最终可能导致创建一个大的条件运算符,需要通过使用显式方法替换参数来处理。
 
 以明确函数取代参数
如果你有一个函数,其中完全取决于不同的参数值而采取不同的行为,那么请通过 Replace Parameter with Explicit Method(以明确函数取代参数) 。
包含参数依赖变体的方法将会变得非常庞大。
1 2 3 4 5 6 7 8 9 10 11
   | void setValue(String name, int value) {   if (name.equals("height")) {     height = value;     return;   }   if (name.equals("width")) {     width = value;     return;   }   Assert.shouldNeverReachHere(); }
  | 
 
1 2 3 4 5 6
   | void setHeight(int arg) {   height = arg; } void setWidth(int arg) {   width = arg; }
  | 
 
 好处
- 提高代码可读性。理解
startEngine()的目的要比setValue("engineEnabled", true)容易得多。 
 保持对象完整
如果您从对象获取多个值,然后将它们作为参数传递给方法,请通过 Preserve Whole Object(保持对象完整) 改为传递整个对象。
1 2 3
   | int low = daysTempRange.getLow(); int high = daysTempRange.getHigh(); boolean withinPlan = plan.withinRange(low, high);
   | 
 
1
   | boolean withinPlan = plan.withinRange(daysTempRange);
   | 
 
 注意
如果被调用函数使用了来自另一个对象的很对象数据,这可能意味着函数实际上应该被定义在那些数据所属的对象中,可以考虑使用 Preserve Whole Object 的同时,也考虑 Move Method 。如果此时还没有一个完整对象,可以先使用 Introduce Parameter Object(引入参数对象) 。
 好处
- 您可以看到一个具有易于理解的名称的对象,而不是大量的参数;
 
- 如果方法需要来自对象的更多数据,则不需要重写使用该方法的所有的调用位置,仅在方法本身内部调整即可。
 
 以函数调用取代参数
如果发现对象调用了某个函数,然后将结果作为参数传递给另一个函数,而接受参数的那个函数本身也可以调用前一个函数,那么,请使用 Replace Parameter with Method Call(以函数调用取代参数) 。
1 2 3 4
   | int basePrice = quantity * itemPrice; double seasonDiscount = this.getSeasonalDiscount(); double fees = this.getFees(); double finalPrice = discountedPrice(basePrice, seasonDiscount, fees);
   | 
 
1 2
   | int basePrice = quantity * itemPrice; double finalPrice = discountedPrice(basePrice);
   | 
 
 好处
- 我们摆脱了不需要的参数并简化了方法调用,这些参数通常不是像现在这样为项目创建的,而是着眼于未来可能永远不会出现的需求,你应该在必要关头才添加参数,预先添加的参数很可能并不是你所需要的。
 
例外:如果修改接口灰度整个程序造成非常痛苦的结果,那么可以考虑保留前人预先加入的参数。此时你也应该评估系统各部分之间的依赖,判断接口是否合理。
 引入参数对象
某些参数总是很自然的同时存在,可以考虑使用 Introduce Parameter Object(引入参数对象) 以一个对象取代这些参数。
1 2 3
   | function amountInvoiced(startDate, endDate) {...} function amountReceived(startDate, endDate) {...} function amountOverdue(startDate, endDate) {...}
  | 
 
1 2 3
   | function amountInvoiced(startDate, endDate) {...} function amountReceived(startDate, endDate) {...} function amountOverdue(startDate, endDate) {...}
  | 
 
 好处
- 更易读的代码。您可以看到一个具有易于理解的名称的对象,而不是大量的参数;
 
- 在各处分散的相同参数组(虽然没有调用相同的代码,但是经常遇到相同的参数和参数组)创建了它们自己的对象,从而避免了重复代码。
 
 移除设值函数
如果类中的某个字段在对象创建的时候被设值,然后就不会在改变了,而您希望阻止对字段值的任何更改,那么请 Remove Setting Method(移除设值函数) 。
1 2 3
   | class Person {   get name() {...}   set name(aString) {...}
  | 
 
1 2
   | class Person {   get name() {...}
  | 
 
 隐藏函数
如果有一个函数从来没有被其他任何类调用到,那么请使用 Hide Method(隐藏函数) 将这个函数修改为private。
 好处
- 隐藏方法使您的代码更容易发展。当您更改私有方法时,您只需要担心如何不破坏当前类,因为您知道该方法不会在其他任何地方使用;
 
- 通过将方法设为私有,您强调了类的公共接口和保持公共的方法的重要性。
 
 以工厂函数取代构造函数
如果你希望在创建对象的时候不仅仅是做简单的构建动作,那么请 Replace Constructor with Factory Method(以工厂函数取代构造函数) 。
1 2 3 4 5 6
   | class Employee {   Employee(int type) {     this.type = type;   }    }
  | 
 
1 2 3 4 5 6 7 8
   | class Employee {   static Employee create(int type) {     employee = new Employee(type);          return employee;   }    }
  | 
 
使用 Replace Constructor with Factory Method(以工厂函数取代构造函数) 最显而易见的动机,就是在派生子类的过程中,以工厂函数取代类型码。
 好处
- 工厂方法不一定返回调用它的类的对象。通常这些可以是它的子类,根据给予该方法的参数进行选择;
 
- 工厂方法可以有一个更好的名称来描述它返回的内容和方式,例如
Troops :: GetCrew(myTank);; 
- 工厂方法可以返回已创建的对象,与构造函数不同,后者始终创建新实例。
 
 封装向下转型
如果某个函数返回的对象,需要由汉调用者执行向下转型,那么可以通过 Encapsulate Downcast(封装向下转型) 将向下转型动作移动到函数中。
1 2 3
   | Object lastReading() {   return readings.lastElement(); }
  | 
 
1 2 3
   | Reading lastReading() {   return (Reading) readings.lastElement(); }
  | 
 
 以异常取代错误码
如果某个函数返错误码,那么请 Repalce Error Code with Exception(以异常取代错误码) 。
返回错误代码是程序编程中的一种过时的处理方法。在现代编程中,错误处理由特殊类执行,这些类被命名为exception。如果出现问题,则“抛出”一个错误,然后由一个异常处理程序“捕获”。
1 2 3 4 5 6 7 8 9
   | int withdraw(int amount) {   if (amount > _balance) {     return -1;   }   else {     balance -= amount;     return 0;   } }
  | 
 
1 2 3 4 5 6
   | void withdraw(int amount) throws BalanceException {   if (amount > _balance) {     throw new BalanceException();   }   balance -= amount; }
  | 
 
 好处
- 避免了在大量条件中书写各种错误代码检查。异常处理程序是将普通程序和错误处理区分开来的更为简洁的方法;
 
- 异常类可以实现自己的方法,因此包含部分错误处理功能(例如用于发送错误消息);
 
- 与异常不同,错误代码不能在构造函数中使用,因为构造函数必须只返回一个新对象。
 
 以测试取代异常
如果可以预先检查的条件,却抛出了异常,请通过 Replace Exception with Test(以测试取代异常) 修改调用者,使它在调用函数之前先做检查。
1 2 3 4 5 6 7
   | double getValueForPeriod(int periodNumber) {   try {     return values[periodNumber];   } catch (ArrayIndexOutOfBoundsException e) {     return 0;   } }
  | 
 
1 2 3 4 5 6
   | double getValueForPeriod(int periodNumber) {   if (periodNumber >= values.length) {     return 0;   }   return values[periodNumber]; }
  | 
 
 注意
应使用异常来处理与意外错误相关的不规则行为。它们不应该作为测试的替代品。如果只需在运行前验证条件就可以避免异常,那么就这样做。应该为实际错误保留例外情况。
 好处