Вы пишете исключения для конкретных проблем или общие исключения?

У меня есть код, который дает идентификатор пользователя утилите, которая затем отправляет электронное письмо этому пользователю.

emailUtil.sendEmail(userId, "foo");

public void sendEmail(String userId, String message) throws MailException {
    /* ... logic that could throw a MailException */
}

MailException может быть выброшен по ряду причин, из-за проблем с адресом электронной почты, проблем с почтовым шаблоном и т. д.

Мой вопрос заключается в следующем: вы создаете новый тип исключения для каждого из этих исключений, а затем обрабатываете их индивидуально, или вы создаете одно исключение MailException, а затем сохраняете что-то в исключении (что-то машиночитаемое, а не текст описания), которое позволяет нам делать разные вещи в зависимости от того, что произошло на самом деле.

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

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

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
10
0
1 147
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

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

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

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

В моем коде я обнаружил, что БОЛЬШИНСТВО исключений распространяется до уровня пользовательского интерфейса, где они перехватываются моими обработчиками исключений, которые просто отображают сообщение пользователю (и записывают в журнал). В конце концов, это неожиданное исключение.

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

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

public bool isEmailConfigured ()

... вызовите это первым, вместо того, чтобы искать конкретное исключение.

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

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

Вы также регистрируете свою серверную часть исключений?

pjp 07.08.2009 14:39

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

jm. 08.08.2009 21:21

Вместо использования исключений я обычно возвращаю список объектов состояния из методов, которые могут иметь проблемы при выполнении. Объекты состояния содержат перечисление важности (информация, предупреждение, ошибка, ...), имя объекта состояния, например «Адрес электронной почты», и читаемое пользователем сообщение, например «Адрес электронной почты в неправильном формате».

Затем вызывающий код будет решать, что фильтровать до пользовательского интерфейса, а что обрабатывать сам.

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

Еще одна причина использования списка статусных объектов состоит в том, что выявить множественные ошибки (например, во время проверки) НАМНОГО проще. В конце концов, вы можете вызвать только одно исключение, которое необходимо обработать, прежде чем двигаться дальше.

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

Обновлено: объединяя ответы

@Jonathan: Я хотел сказать, что я могу оценить действие, в данном случае отправив электронное письмо, и отправить обратно несколько причин сбоя. Например, «неверный адрес электронной почты», «пустой заголовок сообщения» и т. д.

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

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

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

Mnementh 14.08.2009 15:01

@ Chris.Lively

Вы знаете, что можете передать сообщение в своем исключении или даже «коды состояния». Здесь вы изобретаете велосипед.

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

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

Я бы просто прошел мимо

throw new exception("WhatCausedIt")

если вы хотите обрабатывать свои исключения, вы можете передать код вместо «WhatCausedIt», а затем отреагировать на разные ответы с помощью оператора switch.

Вместо кода в исключении, переданном в качестве параметра, вы можете создать подкласс исключения и использовать instanceof.

Mnementh 14.08.2009 14:55

Хотя вы можете различать выполнение кода, глядя на исключение, не имеет значения, выполняется ли это с помощью «режима иерархии catch exceptionType» или «if (...) else ... режима кода исключения»

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

Когда я использую библиотеку, и их методы просто запускают `` Исключение '', я всегда задаюсь вопросом: что может вызвать это исключение? Как моя программа должна реагировать? Если есть javadoc, возможно, причина будет объяснена, но обязательно не javadoc или исключение не объяснено. Слишком много накладных расходов можно избежать с помощью WellChossenExceptionTypeName

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

Я думаю, что сочетание вышеперечисленного даст вам лучший результат.

Вы можете генерировать разные исключения в зависимости от проблемы. например Отсутствует адрес электронной почты = ArgumentException.

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

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

Ответ принят как подходящий

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

Примером из Java-API является IOException, у которого есть подклассы, такие как FileNotFoundException или EOFException (и многие другие).

Таким образом, вы получаете преимущества обоих, у вас нет предложений throw, таких как:

throws SpecificException1, SpecificException2, SpecificException3 ...

генерал

throws GeneralException

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

Я обнаружил, что если вам нужно, чтобы КОД решал, что делать, на основе возвращенного исключения, создайте хорошо названное исключение, подклассифицирующее общий базовый тип. Переданное сообщение следует рассматривать как «только человеческие глаза» и слишком хрупкое, чтобы принимать решения. Пусть компилятор сделает всю работу!

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

Другие вопросы по теме