错误处理我应该抛出异常吗?或者在源头处理?

时间:2009-08-29 21:01:27

标签: c# asp.net-mvc exception-handling

我有这种格式

asp.net MVC视图 - >服务层 - >库中。

因此视图调用服务层,其中包含业务/验证逻辑,然后调用存储库。

现在,我的服务层方法通常具有bool返回类型,因此如果数据库查询已经完成,我可以返回true。或者如果失败了。然后向用户显示通用消息。

我当然会用elmah记录错误。但是我不确定我应该怎么做到这一点。

就像现在我的Repository有更新,创建,删除的void返回类型。

所以说如果更新失败我应该在我的存储库中有一个try / catch抛出错误,然后我的服务层捕获它并执行elmah信令并返回false?

或者我应该让这些存储库方法返回“bool”,尝试/捕获存储库中的错误,然后将“true”或“false”返回到服务层,然后返回“true”或“false”观点?

异常处理仍然让我感到困惑的是如何处理错误以及何时抛出以及何时捕获错误。

4 个答案:

答案 0 :(得分:20)

我一直使用的经验法则是:

  • 在低水平时,因特殊情况而无法完成手术时抛出。
  • 在中间层中,捕获多个异常类型并重新包装在一个异常类型中。
  • 在最后一个负责任的时刻处理例外情况。
  • DOCUMENT!

以下是多层ASP.NET MVC应用程序(UI,Controller,Logic,Security,Repository)的伪代码示例:

  1. 用户点击提交按钮。
  2. 执行控制器操作并调用逻辑(业务)层。
  3. 逻辑方法使用当前用户凭据调用Security
    • 用户无效
      • 安全层抛出SecurityException
      • 逻辑层捕获,使用更通用的错误消息包装在LogicException中
      • Controller捕获LogicException,重定向到Error页面。
    • 用户有效且安全返回
  4. 逻辑层调用存储库以完成操作
    • 存储库失败
      • Repository抛出RepositoryException
      • 逻辑层捕获,使用更通用的错误消息包装在LogicException中
      • Controller捕获LogicException,重定向到Error页面。
    • 存储库成功
  5. 逻辑层返回
  6. Controller重定向到Success视图。
  7. 注意,Logic层只抛出一个异常类型 - LogicException。冒泡的任何低级异常都会被捕获,包含在抛出的LogicException的新实例中。这给了我们很多好处。

    首先,可以访问堆栈跟踪。其次,调用者只需要处理单个异常类型而不是多个异常。第三,可以按摩技术异常消息以显示给用户,同时仍保留原始异常消息。最后,只有负责处理用户输入的代码才能真正知道用户的意图是什么,并确定操作失败时的适当响应。存储库不知道UI是否应显示错误页面或请求用户使用不同的值再次尝试。控制器知道这一点。


    顺便说一句,没有什么说你不能这样做:

    try
    {
      var result = DoSomethingOhMyWhatIsTheReturnType();
    }
    catch(LogicException e)
    {
      if(e.InnerException is SqlException)
      {
        // handle sql exceptions
      }else if(e.InnerException is InvalidCastException)
      {
        // handle cast exceptions
      }
      // blah blah blah
    }
    

答案 1 :(得分:2)

虽然返回错误(或成功)代码通常是更好的方法,但异常比返回代码或静默抑制错误有一个巨大的优势:至少你不能忽略它们!

不要滥用简单的流量控制例外 - 这将是最愚蠢的事情。

但如果你的某个功能真的遇到了“异常”问题,那么肯定会抛出一个执行。然后调用者必须明确地处理它,从而知道发生了什么,或者它会轰炸他。

只是返回一个错误代码是危险的,因为调用者可能只是不打扰检查代码并且可能仍然继续 - 即使在你的应用程序的逻辑中,确实有问题需要处理。

所以:不要滥用异常,但如果发生真正的异常需要调用者对其做些什么,我肯定会建议使用该机制来发出异常情况。

至于处理异常:处理那些你真正可以处理的异常。例如。如果您尝试保存文件并获得安全异常,请向用户显示一个对话框,要求保存到其他位置(因为他可能没有权限保存到他想要的位置)。

然而,您无法真正处理的异常(您想要对“OutOfMemory异常”做什么,真的吗?)应该保持不变 - 可能是调用堆栈中的调用者可以处理那些 - 或不处理。 马克

答案 2 :(得分:1)

我喜欢以这种方式考虑异常处理:您可以定义方法签名,以及您希望执行的操作。现在,如果你无法做到这一点,那么你必须抛出异常。因此,如果您希望根据您拥有的输入数据(忽略环境状态)而失败,那么您的方法签名必须指示操作是成功还是失败。但是,如果您的方法不希望基于您的输入(再次忽略所有其他环境状态)而失败,那么当方法失败时,异常就会顺序。

考虑这两个API:

int int.Parse(string integerValue); // In this case, the method will return int
                                    // or it will die! That means your data must be
                                    // valid for this method to function.

bool int.TryParse(string integerValue, out number); // In this case, we expect the data
                                                    // we passed in might not be fully
                                                    // valid, hence a boolean.

答案 3 :(得分:0)

首先,没有一种方法,肯定没有一种完美的方式,所以不要过度思考它。

通常,您希望对异常情况使用异常(异常会导致性能开销,因此过度使用它们尤其是在“循环”情况下会产生性能影响)。因此,假设存储库由于某种原因无法连接到数据库服务器。然后你会使用一个例外。但是如果存储库通过id执行搜索某个对象并且找不到该对象,那么您将希望返回null而不是抛出异常,表示ID不存在的对象。

验证逻辑也是如此。由于它正在验证,因此假设有时输入不会验证,因此在这种情况下从验证服务返回false是好的(或者可能是更复杂的类型,包括一些关于它为什么不验证的附加信息)。但是,如果验证逻辑包括检查是否使用了用户名,并且由于某种原因它无法执行此操作,那么您将抛出异常。

  

所以说如果更新失败我应该   在我的存储库中有一个try / catch   抛出错误,然后我的服务   层捕获它并做elmah   信令并返回false?

为什么更新会失败?有没有一个完美的理由发生这种情况,这是正常过程的一部分?然后如果由于一个奇怪的原因(假设在更新之前删除了正在更新的记录的某些内容),则不抛出异常,然后异常接合逻辑。真的没有办法从这种情况中恢复过来。