【问题标题】:Trying to add a duplicate to a set - what kind of exception to throw?尝试将副本添加到集合中 - 抛出什么样的异常?
【发布时间】:2011-11-29 17:03:26
【问题描述】:

我正在 Doctrine 模型上创建一个方法来将相关对象添加到集合中,但我想在将重复对象添加到该集合时引发异常。

这是测试:

public function testFluentInterface(  )
{
  $sport = new Sport();
  $this->assertSame($sport, $sport->addCode('ANY'),
    'Expected Sport to implement fluent interface.'
  );
}

public function testCannotAddSameCodeMoreThanOnce(  )
{
  $code = 'BAZ';

  $sport = new Sport();
  $sport->addCode($code);

  try
  {
    $sport->addCode($code);
    $this->fail(
      'Expected error when trying to add the same code to a sport more than once.'
    );
  }
  catch( /*SomeKindOf*/Exception $e )
  {
  }
}

起初,我认为在这种情况下抛出OverflowException 可能是合适的,但我不确定“此值已存在”是否与“此容器已满”相同:

将元素添加到完整容器时引发异常。

UnexpectedValueException,但这似乎更适用于类型不正确的变量:

如果一个值与一组值不匹配,则会引发异常。通常,当一个函数调用另一个函数并期望返回值是某种类型或值时,会发生这种情况,不包括算术或缓冲区相关的错误。

我总是可以使用LogicException,但这对于这个用例来说似乎有点通用:

表示程序逻辑错误的异常。这种异常应该直接导致您的代码得到修复。

这里有更好的选择吗?我的观察正确吗?尝试将重复项添加到必须包含唯一值的集合时,最合适的 SPL 异常是什么?

【问题讨论】:

    标签: php exception spl


    【解决方案1】:

    没有真正适合的 SPL 例外。您最好创建自己的异常:

    /**
     * Raised when item is added to a collection multiple times. 
     */
    class DuplicateException extends RuntimeException {}; 
    

    在这种情况下抛出异常似乎有点激烈,有点等同于每当您将相同的值分配给数组中的相同键时抛出异常。您是否考虑过使用返回值而不是异常来检测这种情况?

    【讨论】:

    • 感谢弗朗西斯的建议。在这种情况下,该类实现了一个流畅的接口,因此调用例如$sport->addCode() 的代码将期望返回值为$sport
    • 我更新了问题以注意流畅的界面。再次感谢您的观察。他们就我处理问题的方式提出了一些非常重要的问题。
    【解决方案2】:

    例如,Java 集合框架中的许多类(即 Set)在这种情况下不会抛出异常,它们返回 false。

    我个人会走那条路。

    【讨论】:

    • 谢谢托尼。这是一个好点。不幸的是,在这种情况下,这是不可行的,因为该类支持流畅的接口(即,调用 $sport->addCode() 的代码期望返回值为 $sport)。
    • 在这种情况下,我会选择 LogicException,因为其他两个有点误导(同样,这只是个人喜好)。祝你好运!
    • 我更新了问题以注意流畅的界面。再次感谢您的建议。他们就我处理问题的方式提出了一些非常重要的问题。
    【解决方案3】:

    我想得越多——尤其是考虑到 Toni 和 Francis 各自的答案——我就越想知道在这种情况下抛出异常是否有用。

    根据 Toni 的观点,许多班级已经以不同的方式处理这种情况。尽管在这种情况下返回“失败”值不是一种选择(Sport 类实现了流畅的接口),但尝试将对象添加到集合中两次不一定是“例外”情况。

    Francis 还提出了一个很好的观点,即 PHP 中的其他构造(例如数组)也不关心这类事情。将重复项添加到集合中时抛出异常将是前所未有的行为,可能会导致后续的可维护性问题。

    此外,抛出异常可能被认为与流畅接口的概念不一致。如果开发人员必须通过条件检查来分解链,那么允许开发人员在实例上链接方法有什么意义?

    换句话说,如果目标是这样的:

    $sport
      ->addCode($codeA)
      ->addCode($codeB)
      ->setSomeOtherProperties(...)
      ->save();
    

    那么强迫开发者必须这样做是没有意义的:

    if( ! $sport->hasCode($codeA) )
    {
      $sport->addCode($codeA);
    }
    // etc. - Why bother having a fluent interface if it's unsafe to use it?
    

    上面的代码特别有问题,因为addCode() 无论如何都会调用hasCode(),这实际上迫使开发人员调用重复计算。

    使用 try/catch 也好不到哪里去:

    try
    {
      $sport
        ->addCode($codeA)
        ->addCode($codeB)
        ->setSomeOtherProperties(...)
        ->save();
    }
    catch( LogicException $e )
    {
      // $sport is now in an unknown state.
    }
    

    试图从上述代码块中的LogicException 恢复是不切实际的,在这里甚至没有必要;尝试添加两次代码不足以阻止 Sport 对象被持久化。

    【讨论】:

    • 我不知道addCode() 做了什么,但是无论您是默默地忽略重复项还是引发异常,都归结为它有多危险。如果您在使用相同参数多次调用addCode() 时可以安全地不做任何事情,并且这对开发人员来说不会是令人惊讶的行为,那么我认为您根本不需要指出这种情况,除了例外或返回值。如果用户真的需要知道,他们可以使用hasCode()。但是,如果这种方法更危险并且失败很严重,则抛出异常。无论如何流利使用可能太危险了。
    • 请注意,这也会改变原始测试的性质——您将不再测试公共接口,但必须测试对象的内部状态,可能是私有状态,以确保代码没有添加两次。
    猜你喜欢
    • 2010-11-19
    • 1970-01-01
    • 1970-01-01
    • 2010-11-18
    • 2015-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多