重构函数调用-Replace Exception with Test以测试取代异常十五

Source

重构函数调用-Replace Exception with Test以测试取代异常十五

1.以测试取代异常

1.1.使用场景

面对一个调用者可以预先检查的条件,你抛出了一个异常。修改调用者,使它在调用函数之前先做检查

异常的出现是程序语言的一大进步。运用Replace Error Code with Exception (310),异常便可协助我们避免很多复杂的错误处理逻辑。但是,就像许多好东西一样,异常也会被滥用,从而变得不再让人愉快(就连味道极好的Aventinus啤酒,喝得太多也会让我厌烦[Jackson])。“异常”只应该被用于异常的、罕见的行为,也就是那些产生意料之外的错误的行为,而不应该成为条件检查的替代品。如果你可以合理期望调用者在调用函数之前先检查某个条件,那么就应该提供一个测试,而调用者应该使用它。

1.2.如何做

  • 在函数调用点之前,放置一个测试语句,将函数内catch区段中的代码复制到测试句的适当if分支中。
  • 在catch区段起始处加入一个断言,确保catch区段绝对不会被执行。
  • 编译,测试。
  • 移除所有catch区段,然后将try区段内的代码复制到try之外,然后移除try区段。
  • 编译,测试。

1.3.示例

下面的例子中,我以一个ResourcePool对象管理一些创建代价高昂而又可以重复使用的资源(例如数据库连接)。这个对象带有两个“池”(pool):一个用以保存可用资源,一个用以保存已分配资源。当用户请求一份资源时,ResourcePool对象从“可用资源池”中取出一份资源交出,并将这份资源转移到“已分配资源池”。当用户释放一份资源时,ResourcePool对象就将该资源从“已分配资源池”放回“可用资源池”。如果“可用资源池”不能满足用户的请求,ResourcePool对象就创建一份新资源。
资源供应函数可能如下所示:

class ResourcePool
  Resource getResource() {
    
      
      Resource result;
      try {
    
      
          result = (Resource) _available.pop();
          _allocated.push(result);
          return result;
      } catch (EmptyStackException e) {
    
      
          result = new Resource();
          _allocated.push(result);
          return result;
      }
}
Stack _available;
Stack _allocated;

在这里,“可用资源用尽”并不是一种意料外的事件,因此我不该使用异常表示这种情况。
为了去掉这里的异常,我首先必须添加一个适当的提前测试,并在其中处理“可用资源池为空”的情况

  Resource getResource() {
    
      
      Resource result;
      if (_available.isEmpty()) {
    
      
         result = new Resource();
         _allocated.push(result);
         return result;
     }
      else {
    
      
          try {
    
      
             result = (Resource) _available.pop();
             _allocated.push(result);
             return result;
          } catch (EmptyStackException e) {
    
      
             result = new Resource();
             _allocated.push(result);
             return result;
          }
      }
   }

现在getResource()应该绝对不会抛出异常了。我可以添加断言保证这一点

  Resource getResource() {
    
      
      Resource result;
      if (_available.isEmpty()) {
    
      
          result = new Resource();
          _allocated.push(result);
          return result;
      }
      else {
    
      
          try {
    
      
              result = (Resource) _available.pop();
              _allocated.push(result);
              return result;
          } catch (EmptyStackException e) {
    
      
            Assert.shouldNeverReachHere("available was empty on pop");
             result = new Resource();
             _allocated.push(result);
             return result;
          }
      }
  }
class Assert...
  static void shouldNeverReachHere(String message) {
    
      
      throw new RuntimeException (message);
  }

编译并测试。如果一切运转正常,就可以将try 区段中的代码拷贝到try 区段之外,然后将区段全部移除:

  Resource getResource() {
    
      
      Resource result;
      if (_available.isEmpty()) {
    
      
          result = new Resource();
          _allocated.push(result);
          return result;
      }
      else {
    
      
          result = (Resource) _available.pop();
       _allocated.push(result);
       return result;
    }
  }

在这之后我常常发现,可以对条件代码加以整理。本例之中我可以使用Consolidate Duplicate Conditional Fragments (243):

  Resource getResource() {
    
      
      Resource result;
      if (_available.isEmpty())
          result = new Resource();
      else
          result = (Resource) _available.pop();
      _allocated.push(result);
      return result;
  }