函数改名
如果一个函数未能揭示函数的用途,那么请通过 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]; }
|
注意
应使用异常来处理与意外错误相关的不规则行为。它们不应该作为测试的替代品。如果只需在运行前验证条件就可以避免异常,那么就这样做。应该为实际错误保留例外情况。
好处