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

17

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

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

    
задан frostymarvelous 08.07.2011 в 09:50
источник

4 ответа

33

Вы должны расширить класс 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 сбой всей программы.
      • Я не могу передать только anything в качестве второго аргумента, он должен быть допустимым объектом 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 . Я действительно not проверяет, имеет ли имя пользователя не менее 3 символов, потому что это нарушит принцип DRY. Вместо этого я просто позволю конструктору беспокоиться об этом. Если сбой конструкции с InvalidArgumentException , я знаю, что имя пользователя неверно, поэтому я дам знать об этом пользователю.

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

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

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

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

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

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

class UserStoppedLovingUsException extends Exception { }

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

    
ответ дан deceze 08.07.2011 в 10:00
источник
2

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

    
ответ дан SteeveDroz 08.07.2011 в 09:56
источник
2

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

См

  1. Исключения основного PHP
  2. Исключения основного пакета PHP PHP

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

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

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

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

    
ответ дан hakre 08.07.2011 в 09:54
источник
1

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

    
ответ дан Rob 08.07.2011 в 09:56
источник