重构手法之简化条件表达式【3】
本小节目录
5Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式)
概要
函数中的条件逻辑使人难以看清正常的执行路径。
使用卫语句表现所有特殊情况。
动机
条件表达式通常有两种表现形式。(1)所有分支都属于正常行为;(2)条件表达式提供的答案中只有一种是正常行为,其他都是不常见的情况。
如果两条分支都是正常行为,就应该使用形如if...else...的条件表达式;如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回。这样的单独检查被称为“卫语句”。
该重构手法的精髓就是:给一条分支以特别的重视。
范例
下面的函数以特殊规则处理死亡员工、驻外员工、退休员工的薪资:
class Amount { public bool IsDead { get; set; } public bool IsSeparated { get; set; } public bool IsRetired { get; set; }
public double GetPayAmount() { double result; if (IsDead) { result = DeadAmount(); } else { if (IsSeparated) { result = SeparatedAmount(); } else { if (IsRetired) { result = RetiredAmount(); } else { result = NormalAmount(); } } } return result; } private double DeadAmount() { return default(double); } private double SeparatedAmount() { return default(double); } private double RetiredAmount() { return default(double); } private double NormalAmount() { return default(double); } }
我们可以看到,这段代码中,非正常情况掩盖了正常情况的检查,所以应该用卫语句来取代这些检查,以提高程序清晰度。
我们从最上面的条件动作开始:
public double GetPayAmount() { double result; if (IsDead) { return DeadAmount(); } if (IsSeparated) { result = SeparatedAmount(); } else { if (IsRetired) { result = RetiredAmount(); } else { result = NormalAmount(); } } return result; }
然后继续替换下一个:
public double GetPayAmount() { double result; if (IsDead) { return DeadAmount(); } if (IsSeparated) { return SeparatedAmount(); } if (IsRetired) { result = RetiredAmount(); } else { result = NormalAmount(); } return result; }
然后是最后一个:
public double GetPayAmount() { double result; if (IsDead) { return DeadAmount(); } if (IsSeparated) { return SeparatedAmount(); } if (IsRetired) { return RetiredAmount(); } result = NormalAmount(); return result; }
此时,result变量已经没有意义了,所以可以删掉,最终代码如下:
class Amount { public bool IsDead { get; set; } public bool IsSeparated { get; set; public bool IsRetired { get; set; } public double GetPayAmount() { if (IsDead) { return DeadAmount(); } if (IsSeparated) { return SeparatedAmount(); } if (IsRetired) { return RetiredAmount(); } return NormalAmount(); } private double DeadAmount() { return default(double); } private double SeparatedAmount() { return default(double); } private double RetiredAmount() { return default(double); } private double NormalAmount() { return default(double); } }
范例:将条件反转
class AdjustedCapital { public double Capital { get; set; }
public double IntRate { get; set; } public double Income { get; set; } public double Duration { get; set; } public double GetAdjustedCapital() { double result = 0; if (Capital > 0) { if (IntRate > 0 && Duration > 0) { result = Income / Duration; } } return result; } }
同样地,我们进行逐一替换。不过在插入卫语句时,需要将条件反转过来:
public double GetAdjustedCapital() { double result = 0; if (Capital <= 0) { return result; } if (IntRate > 0 && Duration > 0) { result = Income / Duration; } return result; }
再替换下一个:
public double GetAdjustedCapital() { double result = 0; if (Capital <= 0) { return result; } if (IntRate <= 0 || Duration <= 0) { return result; } result = Income / Duration; return result; }
最后,我们可以删除临时变量:
public double GetAdjustedCapital() { if (Capital <= 0) { return 0; } if (IntRate <= 0 || Duration <= 0) { return 0; } return Income / Duration; }
小结
许多程序员都有这样一个观念:“每个函数只能有一个入口和一个出口。”现代编程语言都会限制函数只有一个入口。但“函数只有一个出口”,其实并不是那么管用。
书中有这么一句话:嵌套的条件分支往往是由一些深信“每个函数只能有一个出口的”程序员写出的。但实际上,如果对函数的剩余部分不感兴趣,那就应该立即退出。 引导阅读者去看一些没有用的else片段,只会妨碍他们对程序的理解。
6Replace Conditional with Polymorphism(以多态取代条件表达式)
概要
你手上有个条件表达式,它根据对象类型的不同而选择不用的行为。
将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。
动机
如果需要根据对象的不同类型而采取不同的行为,使用多态可以不用编写明显的条件表达式。
有一组条件表达式,如果想添加一种新类型,就必须查找并更新所有的条件表达式。而使用多态,只需要建立一个新的子类,并提供适当的函数即可。这就大大降低了系统各部分之间的依赖,使系统升级更加容易。
范例
如下代码所示,OrderProcessor类的ProcessOrder方法根据Customer的类型分别执行一些操作:
public abstract class Customer { } public class Employee : Customer { } public class NonEmployee : Customer { } public class Product { public int Price { get; set; } } public class OrderProcessor { public decimal ProcessOrder(Customer customer, IEnumerable<Product> products) { // do some processing of order decimal orderTotal = products.Sum(p => p.Price); Type customerType = customer.GetType(); if (customerType == typeof(Employee)) { orderTotal -= orderTotal * 0.15m; } else if (customerType == typeof(NonEmployee)) { orderTotal -= orderTotal * 0.05m; } return orderTotal; } }
重构后的代码如下,每个Customer子类都封装自己的算法,然后OrderProcessor类的ProcessOrder方法的逻辑也变得简单并且清晰了。
public abstract class Customer { public abstract decimal DiscountPercentage { get; } } public class Employee : Customer { public override decimal DiscountPercentage => 0.15m; } public class NonEmployee : Customer { public override decimal DiscountPercentage => 0.05m; } public class Product { public int Price { get; set; } } public class OrderProcessor { public decimal ProcessOrder(Customer customer, IEnumerable<Product> products) { // do some processing of order decimal orderTotal = products.Sum(p => p.Price); orderTotal = orderTotal * customer.DiscountPercentage; return orderTotal; } }
小结
“以多态取代条件表达式”这个重构在很多时候会出现设计模式中(常见的工厂家族、策略模式等都可以看到它的影子),因为运用它可以省去很多的条件判断,同时也能简化代码、规范类和对象之间的职责。
To Be Continued……