Fork me on GitHub

重构手法之简化条件表达式【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……

posted @ 2017-11-28 09:00  NaYoung  阅读(1161)  评论(1编辑  收藏  举报