使用异常来控制程序流

时间:2013-08-29 20:29:45

标签: c# architecture exception-handling program-flow

让我举个例子。我在我的aspx.cs文件中有以下web方法,用于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方法从数据库生成一个新问题,在某些特定情况下抛出以下异常:

  1. QuizNotFoundException:当在数据库中找不到具有给定quizGuid的测验时。
  2. QuizInvalidException:出于安全目的而抛出,例如当有人试图破解HTTP请求时。
  3. QuizOverException:每个测验都有10个问题,当用户尝试使用QuizHelper.GetNextQuestion方法获取第11个问题时,会抛出此异常。
  4. QuestionTimedOutException:您必须在给定时间内回答问题。如果不这样做,则抛出此异常。
  5. Exception:所有其他例外情况都归于此目的,其唯一目的是告知用户出现错误,用于UX目的。
  6. 然后在Javascript文件中,检查ResponseMessage.Status并采取相应的操作。

    我知道使用异常,因为它们在此代码中用于控制流程是不好的,但这样做更直观,更简单。更不用说代码对于局外人来说更容易理解了。

    我不确定如何在没有例外的情况下以“正确的方式”重写此代码,但同时保留其简单性。

    我错过了什么,有什么想法吗?

    更新:有些答案建议使用枚举来返回操作的状态,但我有很多操作,它们都可能导致不同的场景(即我不能对所有操作使用相同的枚举)。在这种情况下,每次操作创建一个枚举并不是正确的方法。对这个模型的任何改进?

4 个答案:

答案 0 :(得分:1)

你会的。 G。使用枚举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);
}

避免不同案例的长期级联。

答案 1 :(得分:1)

抛出和处理异常是昂贵的。当无效参数传递或对象即将进入无效状态等时,您可以自由地抛出异常。在这里,您似乎在常规程序流中使用异常。

Microsoft建议不要使用例外来改变程序流程。

  

虽然使用异常处理程序来捕获错误和其他破坏程序执行的事件是一种很好的做法,但使用异常处理程序作为常规程序执行逻辑的一部分可能很昂贵,应该避免使用。在大多数情况下,异常应仅用于不经常发生且不期望的情况。例外情况不应用于返回值作为典型程序流程的一部分。在许多情况下,您可以通过验证值并使用条件逻辑来停止引发问题的语句的执行来避免引发异常。

以下是代码分析rule,其中说明了这一点。

因此,在您的情况下,您需要将某种ErrorCode作为Enumint返回。会更好或将ErrorCode包裹在QuizException中以防您觉得需要在此处抛出异常!

编辑:根据您的修改,我认为您可以做到这一点。为什么不能创建Enum或Int作为错误代码?例如,举一个WindowsSocket的示例,它公开SocketErrorCodes包含所有类型的套接字错误。或者更合适的一个是操作系统本身使用唯一SystemErrorCodes,它可以包含在Enum不是吗?

如果我错了或者错过了你的观点,请纠正我!

答案 2 :(得分:0)

此处无需通过例外管理流程(可能 ExceptionTimeOutException)。请记住,异常本身就是一个足够重要的工件,并且不适合流体控制流程,而且顾名思义:它适用于特殊情况。

有些地方您根本无法避免异常流动,例如:当您使用设备时。这是一种非常常见的命令行为,它基于从设备或IO收到的异常。

但是,正如我所说,它似乎不是你的情况,所以只要有可能就用简单的if/else处理它。

答案 3 :(得分:0)

抛出异常是“昂贵的”,因为在引发异常时会进行多次调用。

我建议使用Question.StatusQuiz.Status作为枚举。

修改:了解详情Why are try blocks expensive