简化函数调用之十四 :Replace Error Code with Exception(用异常取代错误码)

某个函数返回一个特定的代码(special code),用以表示某种错误情况。

改用异常(exception)。

int withdraw(int amount) {

    if (amount > _balance)

        return -1;

    else {

        _balance -= amount;

        return 0;

    }

}

 

void withdraw(int amount) throws BalanceException {

    if (amount > _balance) throw new BalanceException();

    _balance -= amount;

}

动机(Motivation)

和生活一样,计算器偶尔也会出错。一旦事情出错,你就需要有些对策。最简单的情况下,你可以停止程序运行,返回一个错误码。这就好像因为错过一班飞机而自杀一样(如果真那么做,哪怕我是只猫,我的九条命也早赔光了)。尽管我的油腔滑调企图带来一点幽默,但这种「软件自杀」选择的确是有好处的。如果程序崩溃代价很小,用户又足够宽容,那么就放心终止程序的运行好了。但如果你的程序比较重要,就需要以比较认真的方式来处理。

问题在于:程序中发现错误的地方,并不一定知道如何处理错误。当一段副程序 (routine)发现错误时,它需要让它的调用者知道这个错误,而调用者也可能将这 个错误继续沿着调用链(call chain)传递上去。许多程序都使用特殊输出来表示错误,Unix 系统和C-based 系统的传统方式就是「以返回值表示副程序的成功或失败」。

Java 有一种更好的错误处理方式:异常(exceptions)。这种方式之所以更好,因 为它清楚地将「普通程序」和「错误处理」分开了,这使得程序更容易理解——我希望你如今已经坚信:代码的可理解性应该是我们虔诚追求的目标。

作法(Mechanics)

·决定待抛异常应该是checked 还是unchecked。

Ø如果调用者有责任在调用前检查必要状态,就抛出unchecked异常。

Ø如果想抛出checked 异常,你可以新建一个exception class,也可以使用现有的exception classes。

·找到该函数的所有调用者,对它们进行相应调整,让它们使用异常。

Ø如果函数抛出unchecked 异常,那么就调整调用者,使其在调用函数 前做适当检查。每次修改后,编译并测试。

Ø如果函数抛出checked 异常,那么就调整调用者,使其在try 区段中调用该函数。

·修改该函数的签名式(sigature),令它反映出新用法。

如果函数有许多调用者,上述修改过程可能跨度太大。你可以将它分成下列数个步骤:

·决定待抛异常应该是checked 还是unchecked 。

·新建一个函数,使用异常来表示错误状况,将旧函数的代码拷贝到新函数中,并做适当调整。

·修改旧函数的函数本体,让它调用上述新建函数。

·编译,测试。

·逐一修改旧函数的调用者,令其调用新函数。每次修改后,编译并测试。

·移除旧函数。

范例:(Example)

现实生活中你可以透支你的账户余额,计算器教科书却总是假设你不能这样做,这不是报奇怪吗?不过下而的例子仍然假设你不能这样做:

class Account...

  int withdraw(int amount) {

      if (amount > _balance)

          return -1;

      else {

          _balance -= amount;

          return 0;

      }

  }

  private int _balance;

为了让这段代码使用异常,我首先需要决定使用checked 异常还是unchecked 异常。决策关键在于:调用者是否有责任在取款之前检查存款余额,或者是否应该由 withdraw() 函数负责检查。如果「检查余额」是调用者的责任,那么「取款金额大于存款余额」就是一个编程错误。由于这是一个编程错误(也就是一只「臭虫」〕, 所以我应该使用unchecked 异常。另一方面,如果「检查余额」是withdraw() 函数的责任,我就必须在函数接口中声明它可能抛出这个异常(译注:这是一个checked 异常),那么也就提醒了调用者注意这个异常,并采取相应措施。

范例:unchecked 异常

首先考虑unchecked 异常。使用这个东西就表示应该由调用者负责检查。首先我需要检查调用端的代码,它不应该使用withdraw() 函数的返回值,因为该返回值只用来指出程序员的错误。如果我看到下面这样的代码:

      if (account.withdraw(amount) == -1)

          handleOverdrawn();

      else doTheUsualThing();

我应该将它替换为这样的代码:

      if (!account.canWithdraw(amount))

          handleOverdrawn();

      else {

          account.withdraw(amount);

          doTheUsualThing();

      }

每次修改后,编译并测试。

现在,我需要移除错误码,并在程序出错时抛出异常。由于行为(根据其文本定义 得知)是异常的、罕见的,所以我应该用一个卫语句(guard clause)检查这种情况:

  void withdraw(int amount) {

      if (amount > _balance)

          throw new IllegalArgumentException ("Amount too large");

      _balance -= amount;

  }

由于这是程序员所犯的错误,所以我应该使用assertion 更清楚地指出这一点:

class Account...

  void withdraw(int amount) {

      Assert.isTrue ("amount too large", amount > _balance);

      _balance -= amount;

  }

class Assert...

  static void isTrue (String comment, boolean test) {

      if (! test) {

          throw new RuntimeException ("Assertion failed: " + comment);

      }

  }

范例:checked 异常

checked 异常的处理方式略有不同。首先我要建立(或使用)一个合适的异常:

class BalanceException extends Exception {}

然后,调整调用端如下:

     try {

         account.withdraw(amount);

         doTheUsualThing();

     } catch (BalanceException e) {

         handleOverdrawn();

     }

接下来我要修改withdraw() 函数,让它以异常表示错误状况:

  void withdraw(int amount) throws BalanceException {

      if (amount > _balance) throw new BalanceException();

      _balance -= amount;

  }

这个过程的麻烦在于:我必须一次性修改所有调用者和被它们调用的函数,否则编译器会报错。如果调用者很多,这个步骤就实在太大了,其中没有编译和测试的保障。

这种情况下,我可以借助一个临时中间函数。我仍然从先前相同的情况出发:

if (account.withdraw(amount) == -1)

     handleOverdrawn();

else doTheUsualThing();

class Account ...

int withdraw(int amount) {

     if (amount > _balance)

         return -1;

     else {

         _balance -= amount;

         return 0;

     }

  }

首先,产生一个newWithdraw() 函数,让它抛出异常:

  void newWithdraw(int amount) throws BalanceException {

      if (amount > _balance) throw new BalanceException();

      _balance -= amount;

  }

然后,调整现有的withdraw() 函数,让它调用newWithdraw() :

  int withdraw(int amount) {

      try {

          newWithdraw(amount);

          return 0;

      } catch (BalanceException e) {

          return -1;

      }

  }

完成以后,编译并测试。现在我可以逐一将「对旧函数的调用」替换为「对新函数 的调用」:

      try {

          account.newWithdraw(amount);

          doTheUsualThing();

      } catch (BalanceException e) {

          handleOverdrawn();

      }

由于新旧两函数都存在,所以每次修改后我都可以编译、测试。所有调用者都被我修改完毕后,旧函数便可移除,并使用Rename Method 修改新函数名称,使它与旧函数相同。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值