简化条件表达式
如果有一个复杂的条件语句,可以使用 Decompose Conditional(分解条件表达式) 从 if then else 三个段落中分别提炼出独立函数。
1 2 3 4 5 6 if (date.before(SUMMER_START) || date.after(SUMMER_END)) { charge = quantity * winterRate + winterServiceCharge; } else { charge = quantity * summerRate; }
1 2 3 4 5 6 if (isSummer(date)) { charge = summerCharge(quantity); } else { charge = winterCharge(quantity); }
好处
通过把条件代码提取为具有明确命名的方法,方便后期的维护;
这种重构技术也适用于条件中的短表达。例如字符串isSalaryDay()比直接比较日期的代码更漂亮,更具描述性。
合并条件表达式
如果有一系列条件测试,都得到相同的结果,那么可以通过 Consolidate Conditional Expression(合并条件表达式) 将这些测试合并为一个条件表达式,并将这个条件表达式提炼为一个独立函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 double disabilityAmount () { if (seniority < 2 ) { return 0 ; } if (monthsDisabled > 12 ) { return 0 ; } if (isPartTime) { return 0 ; } }
1 2 3 4 5 6 7 double disabilityAmount () { if (isNotEligableForDisability()) { return 0 ; } }
好处
消除重复的控制流代码。组合具有相同“目标”的多个条件有助于显示您只执行一次复杂检查,从而导致一个操作;
通过合并所有运算符成一个方法,您可以在使用恰当的方法名来解释条件的目的,使代码更具有可读性。
合并重复的条件片段
如果在条件表达式的每个分支上以后相同的一段代码,可以使用 Consolidate Duplicate Conditional Fragments(合并重复的条件片段) 将这段重复代码版一到条件表达式之外。
1 2 3 4 5 6 7 8 if (isSpecialDeal()) { total = price * 0.95 ; send(); } else { total = price * 0.98 ; send(); }
1 2 3 4 5 6 7 if (isSpecialDeal()) { total = price * 0.95 ; } else { total = price * 0.98 ; } send();
好处
移除控制标记
如果在一系列布尔表达式中,某个变量带有“控制标记”的作用,那么请使用 Remove Control Flag(移除控制标记) ,以break语句或者return语句取代控制标记。
1 2 3 4 5 6 for (const p of people) { if (! found) { if ( p === "Don" ) { sendAlert(); found = true ; }
1 2 3 4 5 for (const p of people) { if ( p === "Don" ) { sendAlert(); break ; }
重构原因
控制标志代码通常比使用控制流操作符编写的代码更加笨重。
以卫语句取代嵌套条件表达式
如果函数中的嵌套条件逻辑使人难以看清正常的执行路径,那么请尝试使用 Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套函数表达式) ,将所有特殊检查和边缘情况隔离到单独的子句中,并将它们放在主检查之前。理想情况下,你应该有一个接一个的“平行”条件列表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public double getPayAmount () { double result; if (isDead){ result = deadAmount(); } else { if (isSeparated){ result = separatedAmount(); } else { if (isRetired){ result = retiredAmount(); } else { result = normalPayAmount(); } } } return result; }
1 2 3 4 5 6 7 8 9 10 11 12 public double getPayAmount () { if (isDead){ return deadAmount(); } if (isSeparated){ return separatedAmount(); } if (isRetired){ return retiredAmount(); } return normalPayAmount(); }
好处
以多态取代条件表达式
如果你手上有个条件表达式,根据对象类型的不同而选择不同的行为,那么可以尝试使用 Replace Conditional with Polymorphism(以多态取代条件表达式) ,创建与条件的分支匹配的子类,将这个表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。用相关的方法调用替换条件,从而达到以多态取代条件表达式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Bird { double getSpeed () { switch (type) { case EUROPEAN: return getBaseSpeed(); case AFRICAN: return getBaseSpeed() - getLoadFactor() * numberOfCoconuts; case NORWEGIAN_BLUE: return (isNailed) ? 0 : getBaseSpeed(voltage); } throw new RuntimeException ("Should be unreachable" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 abstract class Bird { abstract double getSpeed () ; } class European extends Bird { double getSpeed () { return getBaseSpeed(); } } class African extends Bird { double getSpeed () { return getBaseSpeed() - getLoadFactor() * numberOfCoconuts; } } class NorwegianBlue extends Bird { double getSpeed () { return (isNailed) ? 0 : getBaseSpeed(voltage); } } speed = bird.getSpeed();
重构步骤
准备阶段:对于这种重构技术,您应该有一个包含各个行为的类的层次结构。如果您没有这样的层次结构,请创建一个,有两种选择:
如果条件是在执行其他操作的方法中,请执行“提取方法”;
对于每个层次结构子类,重新定义包含条件的方法,并将相应条件分支的代码复制到该位置;
从条件中删除此分支;
重复替换,直到条件为空。然后删除条件并声明方法abstract;
引入Null对象
很多的null检查会使您的代码变得更长,更丑陋。如果你需要再三检查某个对象是否为null,可以通过 Introduce Null Object(引入Null对象) 将null值替换为null对象。
1 2 3 4 5 6 if (customer == null ) { plan = BillingPlan.basic(); } else { plan = customer.getPlan(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class NullCustomer extends Customer { boolean isNull () { return true ; } Plan getPlan () { return new NullPlan (); } } customer = (order.customer != null ) ? order.customer : new NullCustomer (); plan = customer.getPlan();
好处
这也是利用了多态的好处:你不用在询问对象是什么类型,然后在根据答案来调用对象的某个行为了,你只管调用该行为就是了;
引入断言
如果某一段代码需要对程序状态做出某种假设,那么可以通过 Introduce Assertion(引入断言) 以断言明确表现这种假设。
1 2 3 4 5 6 7 double getExpenseLimit () { return (expenseLimit != NULL_EXPENSE) ? expenseLimit : primaryProject.getMemberExpenseLimit(); }
1 2 3 4 5 6 7 double getExpenseLimit () { Assert.isTrue(expenseLimit != NULL_EXPENSE || primaryProject != null ); return (expenseLimit != NULL_EXPENSE) ? expenseLimit: primaryProject.getMemberExpenseLimit(); }
好处
如果假设不正确并且代码因此给出错误的结果,那么最好在此之前停止执行导致致命后果和数据损坏。这也意味着您可能忽略了编写必要的测试用例。
其他说明
有时,异常比简单的断言更合适。您可以选择必要的异常类,并让其余代码正确处理它;
什么时候异常比断言更好?如果异常可能由用户或系统的操作引起,您可以处理异常。另一方面,普通的未命名和未处理的异常基本上等同于简单的断言,这个时候你不处理它们,它们只是由于一个永远不会发生的程序错误而导致的。