重构手法之简化条件表达式【1】
本小节目录
1Decompose Conditional(分解条件表达式)
概要
你有一个复杂的条件(if-else if-else)语句。
从if、else if、else三个段落中分别提炼出独立函数。
动机
复杂的条件逻辑往往会导致程序复杂度上升。编写代码来检查不同的条件分支、根据不同的分支做不同的事往往又会导致函数过长。
将条件表达式分解为多个独立函数,根据每个小块代码的用途,为分解而得的新函数命名,并将原函数中对应的代码改为调用新建函数,从而更清楚地表达自己的意图。对于条件逻辑,将每个分支条件分解成新函数还可以突出条件逻辑,更清楚地表明每个分支的作用,并且突出每个分支的原因。
范例
假设要计算购买某样商品的总价,而这个商品在冬天和夏天的单价是不同的:
class Order { public double Quantity { get; set; } public double WinterRate { get; set; } public double SummerRate { get; set; } public double WinterServiceCharge { get; set; } public double GetCharge(DateTime date) { double result; if (date.Month < 3 || date.Month > 6) { result = Quantity * WinterRate + WinterServiceCharge; } else { result = Quantity * SummerRate; } return result; } }
现在把每个分支的判断条件都提炼到一个独立函数中:
class Order { public double Quantity { get; set; } public double WinterRate { get; set; } public double SummerRate { get; set; } public double WinterServiceCharge { get; set; } public double GetCharge(DateTime date) { double result; if (NotSummer(date)) { result = WinterCharge(Quantity); } else { result = SummerCharge(Quantity); } return result; } private bool NotSummer(DateTime date) { return date.Month < 3 || date.Month > 6; } private double SummerCharge(double quantity) { return quantity * SummerRate; } private double WinterCharge(double quantity) { return quantity * WinterRate + WinterServiceCharge; } }
通过这段代码可看出整个重构带来的清晰性。
小结
像上面这样的情况,很多人都不会去提炼分支条件。因为这些分支条件往往非常短,看上去似乎没有提炼的必要。但是,尽管这些条件往往很短,在代码意图和代码自身之间往往存在不小的差距。就像上面那样,NotSummer(date)这个语句比原本的代码更好地表达自己的意图。原来的代码,我必须看看,想一想,才能说出其作用。当然了,这里看起来似乎很简单。即使是这样,提炼出来的函数可读性也更高一些。
2Consolidate Conditional Expression(合并条件表达式)
概要
你有一系列条件测试,都得到相同结果。
将这些测试合并为一个条件表达式,并将这个条件表达式提炼成为一个独立函数。
动机
代码里经常有这样的检查:检查条件各不相同,最终行为却一致。如果发现这种情况,应该使用“逻辑与”和“逻辑或”将它们合并为一个条件表达式。
之所以合并条件代码,有两个原因。(1)合并后的条件代码会告诉你“实际上只有一次条件检查,只不过有多个并列条件需要检查而已”,从而使这一次检查的用意更清晰。(2)这项重构往往是为了使用Extract Method做好准备。
范例:使用逻辑或
class Amount { public int Seniority { get; set; } public int MonthsDisabled { get; set; } public bool IsPartTime { get; set; } double DisablilityAmount() { if (Seniority < 2) { return 0; } if (MonthsDisabled > 12) { return 0; } if (IsPartTime) { return 0; } //compute the disability amount //your code here return 1; } }
这段代码中,一连串的条件检查都在做同一件事情。对于这样的代码,上述检查等价于一个以逻辑或连接起来的语句:
class Amount { public int Seniority { get; set; } public int MonthsDisabled { get; set; } public bool IsPartTime { get; set; } double DisablilityAmount() { if (Seniority < 2 || MonthsDisabled > 12 || IsPartTime) { return 0; } //compute the disability amount //your code here return 1; } }
现在,我们观察这个新的条件表达式,并运用Extract Method将它提炼成一个独立函数,以函数名称表达该语句所检查的条件:
class Amount { public int Seniority { get; set; } public int MonthsDisabled { get; set; } public bool IsPartTime { get; set; } double DisablilityAmount() { if (IsNotEligibleForDisability()) { return 0; } //compute the disability amount //your code here return 1; } bool IsNotEligibleForDisability() { return Seniority < 2 || MonthsDisabled > 12 || IsPartTime; } }
范例:使用逻辑与
class Rate { public double GetRate() { if (OnVacation()) { if (LengthOfService() > 10) { return 1; } } return 0.5; } private bool OnVacation() { return true; } private int LengthOfService() { return 9; } }
这段代码可以变成这样:
class Rate { public double GetRate() { if (OnVacation() && LengthOfService() > 10) { return 1; } return 0.5; } private bool OnVacation() { return true; } private int LengthOfService() { return 9; } }
如果所观察的部分只是对条件进行检查并返回一个值,就可以使用三元操作符将这一部分变成一条return语句。因此,下列代码:
if (OnVacation() && LengthOfService() > 10) { return 1; } return 0.5;
就变成了:
return (OnVacation() && LengthOfService() > 1) ? 1 : 0.5;
小结
那我们什么时候不需要合并表达式呢?
即我们认为这些检查的确彼此独立,的确不应该被视为同一次检查,那就不使用本项重构。
To Be Continued...