Replace Error Code with Exception (以异常取代错误码)

Replace Error Code with Exception (以异常取代错误码)

http://book.51cto.com  2010-02-03 13:58  熊节 译  人民邮电出版社   我要评论(0)
  • 摘要:《重构:改善既有代码的设计》清晰揭示了重构的过程,解释了重构的原理和最佳实践方式,并给出了何时以及何地应该开始挖掘代码以求改善。第10章讲述简化函数调用。本节说的是Replace Error Code with Exception (以异常取代错误码)
  • 标签:重构  代码  函数  重构:改善既有代码的设计

 

10.14 Replace Error Code with Exception (以异常取代错误码)

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

改用异常。

  
  
  1. int withdraw(int amount) {  
  2.     if (amount > _balance)  
  3.         return -1;  
  4.     else {  
  5.         _balance -= amount;  
  6.         return 0;  
  7.     }  
  8. }  
  9.    
  10. void withdraw(int amount) throws BalanceException {  
  11.     if (amount > _balance) throw new BalanceException();  
  12.     _balance -= amount;  

动机

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

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

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

做法

决定应该抛出受控(checked)异常还是非受控(unchecked)异常。

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

如果想抛出受控异常,你可以新建一个异常类,也可以使用现有的异常类。

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

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

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

修改该函数的签名,令它反映出新用法。

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

决定应该抛出受控异常还是非受控异常。

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

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

编译,测试。

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

移除旧函数。

范例

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

  
  
  1. class Account...  
  2.   int withdraw(int amount) {  
  3.       if (amount > _balance)  
  4.           return -1;  
  5.       else {  
  6.           _balance -= amount;  
  7.           return 0;  
  8.       }  
  9.   }  
  10.  
  11.   private int _balance; 

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

范例:非受控异常

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

  
  
  1. if (account.withdraw(amount) == -1)  
  2.   handleOverdrawn();  
  3. else doTheUsualThing();  
  4. 我应该将它替换为这样的代码:  
  5. if (!account.canWithdraw(amount))  
  6.   handleOverdrawn();  
  7. else {  
  8.   account.withdraw(amount);  
  9.   doTheUsualThing();  

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

现在,我需要移除错误码,并在程序出错时抛出异常。由于这种行为是异常的、罕见的,所以我应该用一个卫语句检查这种情况:

  
  
  1. void withdraw(int amount) {      
  2.     if (amount > _balance)  
  3.         throw new IllegalArgumentException ("Amount too large");  
  4.     _balance -= amount;  
  5. }  
  6. 由于这是程序员所犯的错误,所以我应该使用断言更清楚地指出这一点:  
  7. class Account...  
  8.   void withdraw(int amount) {  
  9.       Assert.isTrue("sufficient funds", amount <= _balance);  
  10.       _balance -= amount;  
  11.   }  
  12.  
  13. class Assert...  
  14.   static void isTrue(String comment, boolean test) {  
  15.       if (!test) {  
  16.           throw new RuntimeException("Assertion failed: " + comment);  
  17.       }  
  18.   }  
  19.  
  20. 范例:受控异常  
  21.  
  22. 受控异常的处理方式略有不同。首先我要建立(或使用)一个合适的异常:  
  23.  
  24. class BalanceException extends Exception {}  
  25.  
  26. 然后,调整调用端如下:  
  27.  
  28. try {  
  29.     account.withdraw(amount);      
  30.     doTheUsualThing();  
  31. catch (BalanceException e) {  
  32.     handleOverdrawn();  
  33. }  
  34. 接下来我要修改withdraw()函数,让它以异常表示错误状况:  
  35.  
  36. void withdraw(int amount) throws BalanceException {  
  37.     if (amount > _balance) throw new BalanceException();  
  38.     _balance -= amount;  

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

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

  
  
  1.   if (account.withdraw(amount) == -1)      
  2.       handleOverdrawn();  
  3.   else doTheUsualThing();  
  4.  
  5. class Account ...  
  6.   int withdraw(int amount) {  
  7.       if (amount > _balance)  
  8.           return -1;  
  9.       else {  
  10.           _balance -= amount;  
  11.           return 0;  
  12.       }  
  13.   }  
  14. 首先,创建一个newWithdraw()函数,让它抛出异常:  
  15. void newWithdraw(int amount) throws BalanceException {  
  16.     if (amount > _balance) throw new BalanceException();  
  17.     _balance -= amount;  
  18. }  
  19. 然后,调整现有的withdraw()函数,让它调用newWithdraw():  
  20.  
  21. int withdraw(int amount) {  
  22.     try {  
  23.         newWithdraw(amount);  
  24.         return 0;  
  25.     } catch (BalanceException e) {  
  26.         return -1;  
  27.     }  
  28. }  
  29. 完成以后,编译并测试。现在我可以逐一将调用旧函数的地方改为调用新函数:  
  30.  
  31. try {  
  32.     account.newWithdraw(amount);  
  33.     doTheUsualThing();  
  34. catch (BalanceException e) {  
  35.     handleOverdrawn();  

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

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值