函数中的条件逻辑,使人难以看清正常的执行路径。

使用卫语句表现所有特殊情况。

double getPayAmount() {
double result;
if (_isDead) result =
deadAmount();
else {
if (_isSeparated) result = separatedAmount();
else
{
if (_isRetired) result = retiredAmount();
else result =
normalPayAmount();
};
}
return result;
};

==〉

double getPayAmount() {
if (_isDead) return deadAmount();
if
(_isSeparated) return separatedAmount();
if (_isRetired) return
retiredAmount();
return normalPayAmount();
};

动机

根据我的经验,条件式通常有两种呈现形式。第一种形式是所有分支都属于正常行为,第二种形式则是条件式提供的答案只有一种是正常行为,其他都是不常见的情况。

这两类条件式有不同的用途,这一点应该通过代码表现出来。如果两条分支都是正常行为,就应该使用形如[if...else...]的条件式;如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回,这样的单独检查常常被称为[卫语句]。

Replace Nested Conditional with Guard
Clauses的精髓就是给某一条分支以特别的重视,如果使用if-then-else结构,你对if分支和else分支重视是同等的。这样的代码结构传递给阅读者的消息就是:各个分支有同样的重要性,卫语句就不同了,它告诉读者,这种情况很罕见,如果它真的发生了,请做一些必要的整理工作,然后退出。

每个函数只能有一个入口和一个出口的观念,根深蒂固与某些程序员的脑海里。我发现,当我处理他们编写的代码时,我经常需要使用Replace Nested
Conditional with Guard
Clauses。现在的编程语言都会强制保证每个函数只有一个入口,至于单一出口规则,其实不是那么有用。在我看来保持代码清晰才是最关键的。如果[单一出口]能使这个函数更清楚易读,那么就使用单一出口;否则就不必这么做。

作法

1. 对于每个检查,放进一个卫语句

卫语句要不就从函数中返回,要不就抛出一个异常。

2. 每次将[条件检查]替换成[卫语句]后,编译并测试。

如果所有卫语句都导致相同结果,请使用Consolidate Conditional Expressions。

想象一个薪资系统,它以特殊的规则处理死亡员工、驻外员工、退休员工的薪资。这些情况不常有,但的确偶尔会出现:

假设我在这个系统中,看到下列代码:

double getPayAmount() {
double result;
if (_isDead) result =
deadAmount();
else {
if (_isSeparated) result = separatedAmount();
else
{
if (_isRetired) result = retiredAmount();
else result =
normalPayAmount();
};
}
return result;
};

在这段代码中,非正常情况的检查覆盖了正常情况的检查,所以我应该使用卫语句来取代这些检查。以提高程序清晰度。我可以逐一引入卫语句。让我们从最上面的条件检查动作开始:

double getPayAmount() {
double result;
if (_isDead) return
deadAmount();

if (_isSeparated) result = separatedAmount();
else
{
if (_isRetired) result = retiredAmount();
else result =
normalPayAmount();
};
return result;
};

然后继续下去,仍然一次替换一个检查动作:

double getPayAmount() {
double result;
if (_isDead) return
deadAmount();
if (_isSeparated) return
separatedAmount();

if (_isRetired) result = retiredAmount();
else
result = normalPayAmount();
return result;
};

然后是最后一个

double getPayAmount() {
double result;
if (_isDead) return
deadAmount();
if (_isSeparated) return separatedAmount();
if
(_isRetired) return retiredAmount();

result =
normalPayAmount();
return result;
};

此时result变量已经没有价值了,所以我把它删掉。

double getPayAmount() {
if (_isDead) return deadAmount();
if
(_isSeparated) return separatedAmount();
if (_isRetired) return
retiredAmount();
return normalPayAmount();
};

嵌套条件代码往往由那些深信[每个函数只能有一个出口]的程序员写出。我发现那条规则实在有点简单化了,如果对函数剩余部分不再有兴趣,当然应该立即退出。引导阅读者去看一个没有用的else区段,只会妨碍他们的理解。

将条件逆反

你常常可以将条件表达式逆反,从而实现Replace Nested Conditional with Guard Clauses。请看下面的例子:

public double getAdjustedCapital() {
double result = 0.0;
if (_capital
> 0.0) {
if (_intRate > 0.0 && _duration > 0.0) {
result
= (_income / _duration) * ADJ_FACTOR;
}
}
return result;
}

同样的,我逐一进行替换。不过这次在插入卫语句时,我需要将相应的条件逆反过来:

public double getAdjustedCapital() {
double result = 0.0;
if
(_capital <= 0.0) return result;

if (_intRate > 0.0 &&
_duration > 0.0) {
result = (_income / _duration) *
ADJ_FACTOR;
}
return result;
}

下一个条件稍微复杂一点,所以我分两步进行逆反,首先加入一个逻辑非

public double getAdjustedCapital() {
double result = 0.0;
if (_capital
<= 0.0) return result;
if (!(_intRate > 0.0 &&
_duration > 0.0)) return result;

result = (_income / _duration) *
ADJ_FACTOR;
return result;
}

但是在这样的条件式中留下一个条件非,会把我的脑袋拧成一团乱麻,所以我把它简化成下面这样:

public double getAdjustedCapital() {
double result = 0.0;
if (_capital
<= 0.0) return result;
205
if (_intRate <= 0.0 || _duration
<= 0.0) return result;

result = (_income / _duration) *
ADJ_FACTOR;
return result;
}

这时,我比较喜欢在卫语句内返回一个明确值。因为这样我可以一目了然的看到卫语句返回的失败结果。此外,这时候我也会考虑使用Replace Magic
Number with Symbolic Constant。

public double getAdjustedCapital() {
double result = 0.0;
if (_capital
<= 0.0) return 0.0;
if (_intRate <= 0.0 || _duration
<= 0.0) return 0.0;
result = (_income / _duration) *
ADJ_FACTOR;
return result;
}

完成替换后,我同样可以将临时变量移除

public double getAdjustedCapital() {
if (_capital <= 0.0) return
0.0;
if (_intRate <= 0.0 || _duration <= 0.0) return
0.0;
return (_income / _duration) * ADJ_FACTOR;
}

Replace Nested Conditional with Guard Clauses(用卫语句代替嵌套循环)的更多相关文章

  1. 控制结构(2) 卫语句(guard clause)

    // 上一篇:分枝/叶子(branch/leaf) // 下一篇:状态机(state machine) 基于语言提供的基本控制结构,更好地组织和表达程序,需要良好的控制结构. 典型代码: 同步版本 f ...

  2. 控制结构(2): 卫语句(guard clause)

    // 上一篇:分枝/叶子(branch/leaf) // 下一篇:状态机(state machine) 基于语言提供的基本控制结构,更好地组织和表达程序,需要良好的控制结构. 典型代码: 同步版本 f ...

  3. java - 策略模式、状态模式、卫语句,避免多重if-else(转)

    前言 当代码中出现多重if-else语句或者switch语句时.弊端之一:如果这样的代码出现在多处,那么一旦出现需求变更,就需要把所有地方的if-else或者switch代码进行更改,要是遗漏了某一处 ...

  4. Java重构-策略模式、状态模式、卫语句

    前言 当代码中出现多重if-else语句或者switch语句时.弊端之一:如果这样的代码出现在多处,那么一旦出现需求变更,就需要把所有地方的if-else或者switch代码进行更改,要是遗漏了某一处 ...

  5. 【转】Java重构-策略模式、状态模式、卫语句

    前言 当代码中出现多重if-else语句或者switch语句时.弊端之一:如果这样的代码出现在多处,那么一旦出现需求变更,就需要把所有地方的if-else或者switch代码进行更改,要是遗漏了某一处 ...

  6. MySQL Block Nested Loop and Batched Key Access Joins(块嵌套循环和批量Key访问连接)

    Block Nested-Loop and Batched Key Access Joins Batched Key Access (BKA) Join算法通过index和join buffer访问j ...

  7. 重构 改善既有代码的设计 (Martin Fowler 著)

    第1章 重构, 第一个案例 1.1 起点 1.2 重构的第一步 1.3 分解并重组 statement() 1.4 运用多态取代与价格相关的条件逻辑 1.5 结语 第2章 重构原则 2.1 何谓重构 ...

  8. 《重构》中Tips总结

    1         如果你发现自己需要为程序添加一个特性,而代码结构使你无法很方便地达到目的,那就先重构那个程序,使特性的添加比较容易进行,然后再添加特性. 2         重构之前,首先检查自己 ...

  9. Simplifying Conditional Expressions(简化条件表达式)

    1.Decompose Conditional(分解条件表达式) 2.Consolidate Conditional Expressions(合并条件表达式) 3.Consolidate Duplic ...

随机推荐

  1. Linux中断技术、门描述符、IDT(中断描述符表)、异常控制技术总结归类

    相关学习资料 <深入理解计算机系统(原书第2版)>.pdf http://zh.wikipedia.org/zh/%E4%B8%AD%E6%96%B7 独辟蹊径品内核:Linux内核源代码 ...

  2. KMP 算法总结

    KMP算法是基本的字符串匹配算法,但是代码实现上有一些细节容易错.这篇随笔将认真总结一下. KMP算法的核心是: The KMP algorithm searches for occurrences ...

  3. FluentData,它是一个轻量级框架,关注性能和易用性。

    http://www.cnblogs.com/zengxiangzhan/p/3250105.html FluentData,它是一个轻量级框架,关注性能和易用性. 下载地址:FlunenData.M ...

  4. 搭建的SSH 框架

    公用JDBC 方法,如果要保存数据,不许再service 中写,而且必须带save*  update* 的方法名才受事物控制,ajax 返回json 控制,登录拦截器, 用户体系我没有建立,个人需要不 ...

  5. 如何通俗地理解 Gradle

    http://www.zhihu.com/question/30432152 一句话概括就是:依赖管理和任务执行. 像Ruby里面的bundler+rake,像iOS中的cocoapods,像node ...

  6. 牛顿迭代法求n方根

    一.简单推导 二.使用 借助上述公式,理论上可以求任意次方根,假设要求a(假设非负)的n次方根,则有xn=a,令f(x)=xn-a,则只需求f(x)=0时x的值即可.由上述简单推导知,当f(x)=0时 ...

  7. CodeForces 705B (训练水题)

    题目链接:http://codeforces.com/problemset/problem/705/B 题意略解: 两个人玩游戏,解数字,一个数字可以被分成两个不同或相同的数字 (3可以解成 1 2) ...

  8. Adele的生活

    3个小时的电影,有点冗长,中间抽空吃了个饭,跑出去抽了3根烟,接着洗了个脸才回来接着看完,法国人做事真是拖沓啊有木有. 电影看完后给人一种淡淡的忧伤,第一次了解lesbians的真实世界(如果电影是来 ...

  9. php 命名空间

    命名空间一个最明确的目的就是解决重名问题,PHP中不允许两个函数或者类出现相同的名字,否则会产生一个致命的错误.这种情况下只要避免命名重复就可以解决,最常见的一种做法是约定一个前缀. 例:项目中有两个 ...

  10. WPF 任务栏图标闪烁提醒

    using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServi ...