Как отличить исключения, которые я могу показать пользователю, и те, которые я не могу? - PullRequest
2 голосов
/ 08 июня 2010

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

Транзакция уже сторнирована

или

Невозможно сторнировать сторнирование транзакции

или

У вас нет разрешения на отмену транзакций

или

Эта транзакция находится в сеансекоторая уже была закрыта

или

Эта транзакция слишком старая, чтобы ее можно было стереть

Вопрос в том, как мне сообщить об этихИсключительные случаи возвращаются к вызывающему коду, чтобы они могли показать пользователю?

Создаю ли я отдельное исключение для каждого случая:

catch (ETransactionAlreadyReversedException)
    MessageBox.Show('Transaction already reversed')
catch (EReversingAReversingTransactionException)
    MessageBox.Show('Cannot reverse a reversing transaction')
catch (ENoPermissionToReverseTranasctionException)
    MessageBox.Show('You do not have permission to reverse transactions')
catch (ECannotReverseTransactionOnAlredyClosedSessionException)
    MessageBox.Show('This transaction is on a session that has already been closed')
catch (ECannotReverseTooOldTransactionException)
    MessageBox.Show('This transaction is too old to be reversed')

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

Транзакции, созданные NSL, не могут быть отменены

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

Альтернативой является создание одного класса исключений:

`EReverseTransactionException`

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

catch (EReverseTransactionException)

Но все равно понятно, что любые другие исключения, которые включают, например, ошибку четности ECC в памяти, остаются необработанными.

Другими словами, я не преобразую все ошибки, которые могутбыть брошенным методом ReverseTransaction() в EReverseTransactionException, только те, которые являются логически неверной причиной пользователя.

Ответы [ 5 ]

3 голосов
/ 08 июня 2010

Я считаю, что существуют различные широкие категории исключений:

  1. TransientException - то, что вы только что попробовали, не сработало, но если вы попытаетесь снова, это может сработать. Используется для случаев, таких как база данных в настоящее время недоступна.
  2. InvalidRequestException - вы только что попросили что-то, что не может быть сделано (ваши примеры подходят здесь)
  3. SystemException - Система больна, мы забыли все, что вы только что сказали, ваш сеанс мертв, вам нужно начинать все сначала.

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

3 голосов
/ 08 июня 2010

Для меня эмпирическое правило: полезно ли пользователю видеть точное сообщение об ошибке.Это сильно варьируется между различными типами приложений.Настольное приложение, используемое миллионами «средних пользователей», сильно отличается от корпоративного веб-приложения, используемого сотнями обученных профессионалов.

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

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

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

1 голос
/ 08 июня 2010

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

class transaction_error : public std::runtime_error { 
public:
    transaction_error(std::string const &caption) : std::runtime_error(caption) {}
};

который вы бы бросили что-то вроде:

throw transaction_error("Transaction already reversed");

... и поймать что-то вроде:

try { 
    execute_transaction(transaction_data);
}
catch(transaction_error const &e) { 
    MessageBox(NULL, e->what(), "Transaction Error", MB_OK);
}
1 голос
/ 08 июня 2010

Вы выводите все свои исключения из базы Exception (или EException или любого другого эквивалента на вашем языке?)

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

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

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

1 голос
/ 08 июня 2010

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

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

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

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

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