когда я пишу свой собственный класс исключений? - PullRequest
20 голосов
/ 08 июля 2011

Мне было интересно, когда я вошел в мутные воды ООП и написал пару или около того распределенных библиотек, когда необходимо написать собственное расширение класса исключения.

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

Ответы [ 4 ]

42 голосов
/ 08 июля 2011

Вы должны расширить класс Exception своими собственными типами Exception, если вам необходимо различать различные типы ошибок. Бросок Exception просто означает что-то пошло не так . Вы не знаете, , что пошло не так, хотя Стоит ли все прерывать? Это ожидаемая ошибка? Бросок UserIsNotAllowedToDoThisException означает нечто гораздо более конкретное. Важно различать, какой код может обрабатывать какие ошибки:

try {
    new Foo($bar);
} catch (UserIsNotAllowedToDoThisException $e) {
    echo "Sorry, you're not allowed to do this.";
}

Этот код обрабатывает простой случай, когда что-то не разрешено. Если Foo вызовет какое-то другое исключение, например TheDatabaseJustCrashedAndIsBurningException, вы не хотите знать об этом здесь, вы хотите, чтобы какой-то глобальный обработчик ошибок обрабатывал его. Различая , что пошло не так, это позволяет вам правильно решать проблемы.


Хорошо, вот немного более полный пример:

Во-первых, если вы используете правильный ООП, вам нужно Исключения для сбоя конструкций объекта. Не имея возможности провалить конструкцию объекта, вы игнорируете большую часть ООП: безопасность типов и, следовательно, целостность данных. См. Например:

class User {

    private $name = null;
    private $db = null;

    public function __construct($name, PDO $db) {
        if (strlen($name) < 3) {
            throw new InvalidArgumentException('Username too short');
        }
        $this->name = $name;
        $this->db = $db;
        $this->db->save($this->name);  // very fictional DB call, mind you
    }

}

В этом примере мы видим много вещей:

  • Мои User объекты имеют , чтобы иметь имя. Если не передать аргумент $name конструктору, PHP потерпит неудачу во всей программе.
    • Имя пользователя должно содержать длиной не менее 3 символов. Если это не так, объект не может быть построен (потому что выброшено исключение).
  • Мои User объекты имеют , чтобы иметь действующее и работающее соединение с базой данных.
    • Если не передать аргумент $db, PHP потерпит неудачу во всей программе.
    • Если не передать действительный экземпляр PDO, PHP не сможет выполнить всю программу.
      • Я не могу передать только что-нибудь в качестве второго аргумента, это должен быть действительный объект PDO.
      • Это означает , если создание экземпляра PDO завершилось успешно, у меня есть действующее соединение с базой данных. Мне не нужно беспокоиться или проверять действительность моего соединения с базой данных впредь. По этой же причине я создаю объект User; если конструкция завершается успешно, у меня есть действительный пользователь (допустимый, что означает, что его имя длиной не менее 3 символов). Мне не нужно проверять это снова. Когда-либо. Мне нужно набрать подсказку только для User объектов, PHP позаботится обо всем остальном.

Итак, вы видите силу, которую дает вам OOP + Exceptions. Если у вас есть экземпляр объекта определенного типа, вы можете быть на 100% уверены, что его данные действительны. Это огромный шаг по сравнению с передачей массивов данных в любом наполовину сложном приложении.

Теперь вышеприведенный __construct может завершиться ошибкой из-за двух проблем: слишком короткое имя пользователя или база данных по какой-либо причине не работает . Объект PDO действителен, поэтому соединение работало в то время, когда объект был создан, но, может быть, оно тем временем оборвалось. В этом случае вызов $db->save выдаст свой PDOException или его подтип.

try {
    $user = new User($_POST['username'], $db);
} catch (InvalidArgumentException $e) {
    echo $e->getMessage();
}

Так что я бы использовал приведенный выше код для создания User объекта. Я не проверяю заранее, является ли имя пользователя длиной не менее 3 символов, потому что это нарушило бы принцип СУХОЙ. Вместо этого я просто позволю конструктору беспокоиться об этом. Если конструкция не удалась с InvalidArgumentException, я знаю, что имя пользователя было неверным, поэтому я сообщу пользователю об этом.

Что если база данных не работает? Тогда я не могу продолжать делать что-нибудь в моем текущем приложении. В этом случае я хочу полностью остановить свое приложение, отображая страницу HTTP 500 Internal Server Error. Вот один способ сделать это:

try {
    $user = new User($_POST['username'], $db);
} catch (InvalidArgumentException $e) {
    echo $e->getMessage();
} catch (PDOException $e) {
    abortEverythingAndShowError500();
}

Но это плохой способ. База данных может выйти из строя в любое время в любом месте приложения. Я не хочу делать эту проверку в любой момент, когда я передаю соединение с базой данных чему-либо. Вместо этого я позволю исключению всплыть . На самом деле, это уже всплыло. new User исключение не было сгенерировано, оно было вызвано вызовом вложенной функции в $db->save. Исключение уже прошло как минимум два слоя. Так что я просто позволю этому продвинуться еще дальше, потому что Я настроил свой глобальный обработчик ошибок для обработки PDOExceptions (он регистрирует ошибку и отображает красивую страницу с ошибкой). Я не хочу беспокоиться об этой конкретной ошибке здесь. Итак, вот оно:

Использование различных типов исключений позволяет мне игнорировать определенные типы ошибок в определенных точках моего кода и позволяет другим частям моего кода обрабатывать их. Если у меня есть объект определенного типа, я делаю никогда не нужно ставить под сомнение или проверять или беспокоиться о его действительности. Если бы это не было действительным, у меня не было бы экземпляра этого во-первых. И, если когда-либо не сможет быть действительным (как внезапно оборвавшееся соединение с базой данных), объект может сам сигнализировать, что произошла ошибка . Все, что мне нужно сделать, это catch Исключение в нужной точке (которая может быть очень высокой), я не должен проверить, удалось ли что-то или нет в каждой отдельной точке моей программы. Результат - меньше, более надежный, лучше структурированный код . В этом очень простом примере я использую только общий InvalidArgumentException. В несколько более сложном коде с объектами, которые принимают много аргументов, вы, вероятно, захотите провести различие между разными типами недопустимых аргументов. Следовательно, вы создадите свои собственные подклассы исключений.

Попробуйте воспроизвести это, используя только один тип исключения. Попробуйте повторить это, используя только вызовы функций и return false. Вам нужно намного больше кода , чтобы сделать это каждый раз вам нужно сделать эту проверку. Написание пользовательских исключений и пользовательских объектов требует немного больше кода и очевидной сложности заранее, но это сэкономит вам массу кода позже и сделает вещи на 1132 * намного проще в долгосрочной перспективе. Потому что все, что не должно быть (как у пользователя с слишком коротким именем пользователя), гарантированно вызовет какую-то ошибку. Вам не нужно проверять каждый раз. Напротив, вам нужно беспокоиться только о том, на каком слое вы хотите содержать ошибку, а не о том, найдете ли вы ее вообще.

И действительно, не нужно «писать свои собственные исключения»:

class UserStoppedLovingUsException extends Exception { }

Там вы создали свой собственный подкласс исключений. Теперь вы можете throw и catch в соответствующих точках вашего кода. Вам не нужно делать больше, чем это. Фактически, теперь у вас есть формальное объявление типов вещей, которые могут пойти не так в вашем приложении. Разве это не бьет много неофициальной документации и if с и else с и return false с?

2 голосов
/ 08 июля 2011

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

2 голосов
/ 08 июля 2011

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

См.

  1. Основные исключения PHP
  2. Основные исключения PHP SPL

Существует множество исключений, уже встроенных в PHP, поэтому длянормальный ход программы должен охватываться в исключительных случаях.

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

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

И поскольку в настоящее время существуют пространства имен, это действительно чертовски легко, если вы придерживаетесь класса Exception из первых рук изначало;)

1 голос
/ 08 июля 2011

ИМХО: просто если вы введете свой собственный домен.Это обычно указывается, когда вам нужно предоставить дополнительные данные в вашем исключении.Так что на самом деле вы также должны создать свой собственный класс, чтобы добавить дополнительных членов класса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...