简化条件表达式之五 :Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件式)

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

使用卫语句(guard clauses)表现所有特殊情况。

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();

};

动机(Motivation)

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

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

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

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

作法(Mechanics)

·对于每个检查,放进一个卫语句(guard clauses)。
Ø卫语句要不就从函数中返回,要不就抛出一个异常(exception)。

·每次将「条件检查」替换成「卫语句」后,编译并测试。
Ø如果所有卫语句都导致相同结果,请使用Consolidate Conditional Expressions。

范例:(Example)

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

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

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();

};

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

范例:将条件逆反(Reversing the Conditions)

审阅本书初稿时,Joshua Kerievsky 指出:你常常可以将条件表达式逆反,从而实现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;

  }

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

   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;

   }

下一个条件稍微复杂一点,所以我分两步进行逆反。首先加入一个"logical-NOT"操作:

  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;

  }

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

  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;

  }

这时候我比较喜欢在卫语句(guard clause)内返回一个明确值,因为这样我可以一 目了然地看到卫语句返回的失败结果。此外,这种时候我也会考虑使用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;

  }

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值