Как документировать @throws в документации по интерфейсу

Я пишу библиотеку PHP, и у меня есть проблема. В моих интерфейсах есть что-то похожее на следующее:

<?php
/**
 * My interface
 *
 * ...
 */
interface MyInterface
{
    /**
     * This method does foo.
     *
     * @throws \RuntimeException If foo can't be done.
     */
    public function fooAndBar();
}
?>

Теперь запись @throws не совсем правильная, поскольку интерфейс фактически ничего не делает и используется исключительно для абстрактных деталей реализации. Тем не менее, я всегда использовал его, потому что все мои реализации интерфейса вызвали исключение, когда что-то пошло не так.

Но другой разработчик может написать реализацию, которая не может потерпеть неудачу (поэтому она не может генерировать исключение), или он может захотеть использовать другой класс исключений.

В этой ситуации, как я должен документировать @throws в объявлениях интерфейса? Должен ли он быть документирован?

Ответ 1

Рассмотрим код, в котором вы используете интерфейс:

public function doSomething(MyInterface $my) { ... }

Если даже одна из реализаций может генерировать исключение, вы захотите убедиться, что вы обрабатываете возможность исключений.

Итак, да, это должно быть документировано.

Даже если только одна реализация генерирует исключение, обработка исключений все еще должна быть на месте. Конечно, это не означает, что каждый метод должен иметь @throws. Он должен по-прежнему использоваться только там, где это необходимо (там, где вы ожидаете, что реализация должна законно потребовать исключения).

В качестве более конкретного примера рассмотрим следующее:

interface LogWriter
{

    /**
     * @throws LogWriterException
     */
    public function write($entry);

}


class DbLogWriter
{

    public function __construct(PDO $db)
    {
        //store $db somewhere
    }

    public function write($entry)
    {
        try {
            //store $entry in the database
        } catch (PDOException $e) {
            throw new LogWriterException(...);
        }
    }

}

class NullLogWriter
{
    public function write($entry) { }
}

Некоторые вещи могут быть сделаны, чтобы попытаться снизить вероятность исключения при записи в базу данных, но в конце дня это не безопасная операция исключения. Поэтому DbLogWriter::write следует ожидать исключения.

Теперь рассмотрим нулевого автора, который просто отбрасывает записи. Там абсолютно ничего нет, что могло бы пойти туда неправильно, поэтому нет необходимости в исключениях.

Но что, если у вас есть $log, и все, что вы знаете об этом, это реализация LogWriter. Вы полагаете, что он не бросает исключений и потенциально случайно позволяет одному пузырю, или вы предполагаете, что он может выбросить LogWriterException? Я остался бы в безопасности и предполагал, что он может вызвать исключение LogWriterException.

Если все пользователи знают, что $log является LogWriter, но только DbLogWriter документируется как исключение, пользователь может не понимать, что $log->write(...) может генерировать исключение. Кроме того, когда FileLogWriter будет позже создан, это будет означать ожидания того, какие исключения могут быть реализованы и, возможно, будут выбрасываться, уже не будут установлены (никто не ожидал, что FileLogWriter будет выбросить RandomNewException).

Ответ 2

Интерфейсы определяют контракты. Вне зависимости от того, реализует ли класс реализации исключение, это деталь реализации в PHP, потому что в сигнатуре метода не существует ключевого слова throws (например, на Java). Добавление аннотации @throws не может выполнить контракт технически, но может указывать соглашение (то же самое для возвращаемых значений btw). Достаточно ли этого достаточно, чтобы решить.

В обозревателе, если разработчик придумал реализацию, которая не бросает вас, у вас нет проблемы, потому что вам придется добавить блок try/catch в любом случае для тех реализаций, которые выполняют бросок (по соглашению). Было бы проблемой, если реализация начинает бросать другое исключение, чем указано в DocBlock, потому что тогда оно не будет уловлено.