Пытаюсь добавить дубликат в набор - какое исключение кидать?

Я создаю метод в модели 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 будет наиболее подходящим при попытке добавить дубликат в коллекцию, которая должна содержать уникальные значения?


person Community    schedule 29.11.2011    source источник


Ответы (3)


Нет исключения SPL, которое действительно подходит. Возможно, вам лучше создать собственное исключение:

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

Генерация исключения в этом случае кажется немного радикальной, в чем-то эквивалентной генерации исключения всякий раз, когда вы присваиваете одно и то же значение одному и тому же ключу в массиве. Рассматривали ли вы возможность использования возвращаемого значения вместо исключения для обнаружения этого случая?

person Francis Avila    schedule 29.11.2011
comment
Спасибо за предложение Фрэнсис. В этом случае класс реализует гибкий интерфейс, поэтому код, вызывающий, например, $sport->addCode(), будет ожидать, что возвращаемое значение будет $sport. - person ; 29.11.2011
comment
Я обновил вопрос, чтобы отметить свободный интерфейс. Еще раз спасибо за ваши наблюдения. Они подняли несколько очень важных вопросов о моем подходе к проблеме. - person ; 29.11.2011

Например, многие классы в Java Collection Framework (например, Set) в таких случаях не выдают исключение, они возвращают false.

Лично я бы пошел именно так.

person Toni Toni Chopper    schedule 29.11.2011
comment
Спасибо Тони. Это хороший момент. К сожалению, в данном случае это невозможно, так как класс поддерживает гибкий интерфейс (т. е. код, вызывающий $sport->addCode(), будет ожидать, что возвращаемое значение будет $sport). - person ; 29.11.2011
comment
В этом случае я бы выбрал LogicException, потому что два других немного вводят в заблуждение (опять же, это только личные предпочтения). Хорошей работы и удачи! - person Toni Toni Chopper; 29.11.2011
comment
Я обновил вопрос, чтобы отметить свободный интерфейс. Еще раз спасибо за ваши предложения. Они подняли несколько очень важных вопросов о моем подходе к проблеме. - person ; 29.11.2011

Чем больше я думаю об этом, особенно в свете соответствующих ответов Тони и Фрэнсиса, тем больше я задаюсь вопросом, полезно ли создание исключения в этом сценарии.

По словам Тони, многие классы уже по-разному обрабатывают эту ситуацию. Хотя в этом случае возврат значения «сбой» невозможен (класс Sport реализует гибкий интерфейс), попытка дважды добавить объект в коллекцию не обязательно является «исключительным» случаем.

Фрэнсис также хорошо заметил, что другие конструкции в 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.

person Community    schedule 29.11.2011
comment
Я не знаю, что делает addCode(), но независимо от того, игнорируете ли вы дубликаты или создаете исключение, зависит от того, насколько это опасно. Если вы можете безопасно ничего не делать, когда addCode() вызывается несколько раз с одним и тем же параметром, и это не будет удивительным поведением для разработчика, то я не думаю, что вам нужно вообще указывать это условие, будь то исключение или возвращаемое значение. Если пользователю действительно нужно это знать, он может использовать hasCode(). Однако, если этот метод более опасен и сбой имеет большое значение, создайте исключение. В любом случае, бегло использовать его может быть слишком опасно. - person Francis Avila; 30.11.2011
comment
Обратите внимание, что это также изменит характер вашего первоначального теста — вы больше не будете тестировать общедоступный интерфейс, а должны будете проверить внутреннее, возможно, приватное состояние объекта, чтобы гарантировать, что код не будет добавлен дважды. - person Francis Avila; 30.11.2011