Подтвердить что ты не робот

Когда я пишу свой собственный класс исключения?

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

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

4b9b3361

Ответ 1

Вы должны расширить класс 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 позаботится об остальном.

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

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

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

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

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

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

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

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

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

И это действительно не усиливает "написать свои собственные исключения":

class UserStoppedLovingUsException extends Exception { }

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

Ответ 2

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

См

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

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

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

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

Ответ 3

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

Ответ 4

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