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

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

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

Вы должны расширить класс 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 символов. Если это не так, объект не может быть сконструирован (потому что выбрано исключение).
  • Объекты My 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 вызовом вложенной функции в $db->save . Исключение уже преодолело не менее двух уровней. Поэтому я просто позволю этому путешествовать еще дальше, потому что я настроил свой глобальный обработчик ошибок для работы с PDOExceptions (он регистрирует ошибку и отображает хорошую страницу ошибок). Я не хочу беспокоиться об этой конкретной ошибке здесь. Итак, вот оно:

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

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

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

 class UserStoppedLovingUsException extends Exception { } 

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

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

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

Видеть

  1. Исходные PHP-исключения
  2. Исключения основного PHP-SPL

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

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

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

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

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