简化函数调用之十五 :Replace Exception with Test(以测试取代异常)

本文探讨了如何通过优化调用者逻辑来避免不必要的异常抛出,介绍了具体的实现步骤,并以ResourcePool资源管理类为例进行了详细说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

面对一个「调用者可预先加以检查」的条件,你抛出了一个异常。

修改调用者,使它在调用函数之前先做检查。

double getValueForPeriod (int periodNumber) {

    try {

        return _values[periodNumber];

    } catch (ArrayIndexOutOfBoundsException e) {

        return 0;

    }

}

 

double getValueForPeriod (int periodNumber) {

if (periodNumber >= _values.length) return 0;

    return _values[periodNumber];

}

动机(Motivation)

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

作法(Mechanics)

·在函数调用点之前,放置一个测试句,将函数内的catch 区段中的代码拷贝到测试句的适当if 分支中。

·在catch 区段起始处加入一个assertion,确保catch 区段绝对不会被执行。

·编译,测试。

·移除所有catch 区段,然后将区段内的代码拷贝到try 之外,然后移除try 区段。

·编译,测试,

范例:(Example)

下面的例子中,我以一个ResourcePool 对象管理「创建代价高昂、可复用」的资源(例如数据库连接,database connection)。这个对象带有两个「池」(pools), 一个用以保存可用资源,一个用以保存已分配资源。当用户索求一份资源时,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;

在这里,「可用资源用尽」并不是一种意料外的事件,因此我不该使用异常 (exceptions)表示这种情况。

为了去掉这里的异常,我首先必须添加一个适当的提前测试,并在其中处理「可用 资源池为空」的情况:

  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() 应该绝对不会抛出异常了。我可以添加assertion 保证这一点:

  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;

    }

  }

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

  Resource getResource() {

      Resource result;

      if (_available.isEmpty())

          result = new Resource();

      else

          result = (Resource) _available.pop();

      _allocated.push(result);

      return result;

  }

当我们进行单元测试时,有时候我们需要测试一些代码中的日志记录逻辑,这时候就可以使用mock来模拟日志记录器(Logger)的行为。如果我们想测试一个函数,该函数中有记录日志的代码,我们可以使用mock来模拟Logger的行为,并判断是否正确地记录了日志。 下面的例子是一个记录日志的函数: ```python import logging def my_func(): try: # some code here except Exception as e: logging.error(f"An error occurred: {e}") ``` 要测试这个函数,我们需要使用mock来模拟Logger的行为。使用mock可以让我们控制Logger的行为,例如: 让Logger记录日志、不记录日志、记录错误级别的日志等等。 下面是一个使用mock测试Logger异常的例子: ```python import unittest from unittest import mock import logging def my_func(): try: # some code here except Exception as e: logging.error(f"An error occurred: {e}") class TestMyFunc(unittest.TestCase): def test_my_func_logs_exception(self): # create a mock logger mock_logger = mock.Mock(spec=logging.Logger) # replace the global logger with the mock logger logging.getLogger = mock.MagicMock(return_value=mock_logger) # call the function that logs an exception my_func() # assert that the logger's error method was called once mock_logger.error.assert_called_once() ``` 在这个例子中,我们使用了unittest模块和mock模块来对my_func()函数进行测试。我们首先创建了一个mock日志记录器(mock_logger),接着使用mock模块的MagicMock()方法来替换全局的日志记录器(logging.getLogger),让其返回我们创建的mock_logger。 然后,我们调用my_func()函数,这时候会触发mock_logger.error()方法的调用,我们可以通过assert_called_once()方法来判断该方法是否被调用了一次。 这就是mock测试Logger异常的例子。使用mock可以让我们在测试中模拟各种场景,包括模拟日志记录器的行为。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值