Использование исключений для управления потоком программ
Позвольте привести пример. У меня есть следующий веб-метод внутри моего файла aspx.cs, который я использую для вызовов AJAX:
[WebMethod]
public static ResponseMessage GetNextQuestion(string quizGuid)
{
using (DbEntities db = new DbEntities())
{
Quiz theQuiz = Quiz.Get(db, DataValidationHelper.GetGuid(quizGuid));
try
{
Question nextQuestion = QuizHelper.GetNextQuestion(db, theQuiz);
return new ResponseMessage() { Status = "Success", NextQuestion = new NextQuestionResponse(nextQuestion, theQuiz) };
}
catch (QuizNotFoundException)
{
return new ResponseMessage() { Status = "QuizNotFound" };
}
catch (QuizInvalidException)
{
return new ResponseMessage() { Status = "QuizInvalid" };
}
catch (QuizOverException)
{
return new ResponseMessage() { Status = "QuizOver" };
}
catch (QuestionTimedOutException)
{
return new ResponseMessage() { Status = "QuestionTimedOut" };
}
catch (Exception ex)
{
return new ResponseMessage() { Status = "Error", ErrorMessage = ex.Message };
}
}
}
QuizHelper.GetNextQuestion
Метод генерирует новый вопрос из базы данных и в некоторых конкретных случаях выдает следующие исключения:
QuizNotFoundException
: Когда тест с заданнымquizGuid
не найден в базе данных.QuizInvalidException
Брошенный в целях безопасности, например, когда кто-то пытается взломать HTTP-запросы.QuizOverException
: Каждый тест содержит 10 вопросов, и когда пользователь пытается получить 11-й вопрос, используяQuizHelper.GetNextQuestion
Метод, это исключение выбрасывается.QuestionTimedOutException
: Вы должны ответить на вопрос в течение определенного времени. Если вы этого не сделаете, это исключение выдается.Exception
Все остальные исключения сгруппированы по этой причине с единственной целью информирования пользователя о том, что произошла ошибка, для целей UX.
Затем внутри файла Javascript, ResponseMessage.Status
проверен и соответствующие действия предприняты.
Я знаю, что использование исключений, так как они используются в этом коде, для управления потоком - это плохо, но сделать это таким образом интуитивно понятнее и намного проще. Не говоря уже о том, что код легче понять для постороннего.
Я не уверен, как этот код мог быть переписан "правильным способом" без исключений, но в то же время сохраняя его простоту.
Я что-то упускаю, есть идеи?
ОБНОВЛЕНИЕ: Некоторые ответы предлагают использовать Enum для возврата статуса операции, но у меня много операций, и все они могут привести к различным сценариям (то есть я не могу использовать один и тот же Enum для всех операций). В этом случае создание одного Enum для каждой операции не кажется правильным. Какие-нибудь улучшения по сравнению с этой моделью?
4 ответа
Бросать и обрабатывать исключения дорого. Вы можете генерировать исключения, когда передан недопустимый аргумент или объект собирается войти в недопустимое состояние и т. Д. Здесь кажется, что вы используете исключения в обычном потоке программы.
Microsoft предлагает не использовать исключения для изменения потока программы.
Хотя использование обработчиков исключений для перехвата ошибок и других событий, которые нарушают выполнение программы, является хорошей практикой, использование обработчика исключений в качестве части обычной логики выполнения программ может быть дорогостоящим и его следует избегать. В большинстве случаев исключения следует использовать только в тех случаях, которые возникают нечасто и не ожидаются. Исключения не следует использовать для возврата значений в рамках типичного программного потока. Во многих случаях вы можете избежать возникновения исключений, проверяя значения и используя условную логику, чтобы остановить выполнение операторов, вызывающих проблему.
Вот правило анализа кода, которое утверждает это.
Итак, в вашем случае вам нужно вернуть какой-то ErrorCode
как Enum
или же int
, Будет намного лучше или оберните ErrorCode
в QuizException
в случае, если вы чувствуете, что вам нужно исключение, чтобы бросить здесь!
Редактировать: на основании вашего редактирования, я думаю, вы можете сделать это очень много. Почему невозможно создать Enum или Int в качестве кода ошибки? Например, возьмите пример WindowsSocket
который выставляет SocketErrorCodes содержит все виды ошибок сокета. Или еще более уместным здесь является то, что сама операционная система использует уникальные SystemErrorCodes, которые могут быть обернуты в Enum
Не так ли?
Поправьте меня, если я ошибаюсь или упускаю вашу точку зрения!
Например, вы бы использовали enum Quiz.Status
и изменить
Question nextQuestion = QuizHelper.GetNextQuestion(db, theQuiz)
в
Question nextQuestion;
Quiz.Status status = QuizHelper.TryGetNextQuestion(db, theQuiz, out nextQuestion);
switch(status) {
case QuizNotFoundException:
return new ResponseMessage() { Status = "QuizNotFound" };
// ...
}
или даже упростить это до
Question nextQuestion;
Status status = QuizHelper.TryGetNextQuestion(db, theQuiz, out nextQuestion);
if(status != Status.ok) {
return new ResponseMessage() (Status = status);
}
избегая длинного каскада разных случаев.
Здесь нет необходимости управлять потоком через исключение (кроме Exception
а также TimeOutException
). Помните, что это исключение само по себе является достаточно тяжелым артефактом и не подходит для потоков контроля жидкости, а также, как следует из названия: оно предназначено для исключительных ситуаций.
Есть места, где вы просто не можете избежать исключений, например, когда вы работаете с устройствами. Это очень распространенное поведение команд, основанное на исключениях, полученных от устройства или ввода-вывода.
Но, как я уже сказал, это не похоже на ваш случай, так что просто справьтесь с этим простым if/else
всякий раз, когда это возможно.
Это "дорого", когда выдается исключение, так как при вызове исключения выполняется несколько вызовов.
Я бы вместо этого рекомендовал Question.Status
или же Quiz.Status
это Enum.
Изменить: Для получения дополнительной информации Почему попробовать блоки дорого