在分层应用程序中处理异常的建议方法或最佳实践是什么?
try/catch
块? 考虑一个简单的例子。假设您有一个调用业务层的UI,它调用数据层:
//UI
protected void ButtonClick_GetObject(object sender, EventArgs e)
{
try {
MyObj obj = Business.GetObj();
}
catch (Exception ex) {
Logger.Log(ex); //should the logging happen here, or at source?
MessageBox.Show("An error occurred");
}
}
//Business
public MyObj GetObj()
{
//is this try/catch block redundant?
try {
MyObj obj = DAL.GetObj();
}
catch (Exception ex) {
throw new Exception("A DAL Exception occurred", ex);
}
}
//DAL
public MyObj GetObj()
{
//Or is this try/catch block redundant?
try {
//connect to database, get object
}
catch (SqlException ex) {
throw new Exception("A SQLException occurred", ex);
}
}
您对上述异常处理有何批评?
谢谢
答案 0 :(得分:19)
我的经验法则通常是在顶层捕获异常并在那里记录(或以其他方式报告),因为这是您获得有关错误的最多信息的地方 - 最重要的是完整堆栈跟踪。
然而,可能有一些理由在其他层中捕获异常:
SqlException
不会告诉您。DatabaseUnavailableException
。对于不重要的操作,BL可能会忽略它,或者可能让它为那些操作传播。如果BL捕获SqlException
而不是它将暴露给DAL的实现细节。相反,抛出DatabaseUnavailableException
的可能性是DAL界面的一部分。在多个层中记录相同的错误通常没有用,但我可以想到一个例外:当较低层不知道问题是否严重时,它可以将其记录为警告。如果更高层确定 关键,则可以将相同的问题记录为错误。
答案 1 :(得分:11)
以下是我遵循的与异常处理相关的一些规则:
Application.ThreadException
事件。在ASP .Net应用程序中,应实现global.asax中的Application_Error
事件处理程序。这些位置是您可以在代码中捕获异常的最高位置。在许多应用程序中,这将是您将捕获大多数异常的地方,除了日志记录之外,您还可以在此处实现一个通用且友好的错误消息窗口,该窗口将呈现给用户。 SqlConnection conn = null;
try
{
conn = new SqlConnection(connString);
...
}
// do not implement any catch in here. db exceptions logic should be implemented at upper levels
finally
{
// finalization code that should always be executed.
if(conn != null) conn.Dispose();
}
throw;
即可。这将确保保留堆栈跟踪。使用throw ex;
将重置堆栈跟踪。 答案 2 :(得分:3)
要解决的首要问题是永远不要抛出一般Exception
。
第二,除非真的有理由包装异常,否则只需在catch子句中使用throw;
而不是throw new...
。
第三个(这不是硬和快速规则),不要在UI层下面的任何位置捕获常规异常。 UI层应该捕获常规异常,以便可以向最终用户显示用户友好的消息,而不是爆炸的技术细节。如果你在层中更深层次地捕捉到一般性的例外,那么它可能会被无意中吞下并造成难以追踪的错误。
答案 3 :(得分:2)
任何应用程序都难以处理异常。您需要考虑每个例外,并快速进入适合您的模式。我尝试将例外分组为以下类别之一...
示例:也许您的持久性框架要求您捕获可能由格式错误的SQL引起的SQL异常,但是,您正在执行硬编码查询。
处理:根据我的经验,大多数例外属于此类别。至少,记录它们。更好的是,将它们发送到记录它们的异常处理服务。然后在将来,如果您决定以不同方式记录它们或使用它们执行不同的操作,则可以在一个位置进行更改。也许你还想向UI层发送一个标志,说明发生了某种错误,他们应该重试他们的操作。也许你邮寄管理员。
您还需要将某些内容返回到更高层,以便服务器上的生命继续存在。也许这是一个默认值,或者这可能是null。也许你有办法取消整个行动。
另一种选择是为异常处理服务提供两种处理方法。 handleUnexpectedException()
方法会通知用户但不会重新抛出异常,如果您有能力自行展开堆栈或以某种方式继续,则可以返回默认值。 handleFatalException()
方法会通知用户并重新抛出某种异常,这样您就可以让异常为您展开堆栈。
示例:用户正在尝试更新foobar小部件并为其指定一个新名称,但已存在具有所需名称的foobar小部件。
处理:在这种情况下,您需要将异常发回给用户。在这种情况下,您可以继续抛出异常(或者更好,甚至不要抓住它),直到它到达UI层,然后UI层应该知道如何处理异常。确保记录这些异常,以便您(或任何人编写您的UI)知道它们存在并且知道期望它们。
示例:您进行远程服务呼叫并且远程服务超时,但您知道他们有这样做的历史记录,您应该重做该呼叫。
处理:有时这些例外情况从第一类开始。在您的应用程序处于野外状态一段时间之后,您会意识到您确实有一个很好的方法来处理它。有时,与乐观锁定或中断异常的异常一样,捕获异常并对其执行某些操作只是业务的一部分。在这些情况下,处理异常。如果您的语言(我在想Java)区分已检查和未检查的异常,我建议您始终检查这些异常。
为了解决您的上述问题,我会将初始异常委托给一个服务,该服务将通知用户并根据MyObj的对象类型(例如设置)我可能会将此作为非致命异常并返回默认值或者如果我不能这样做(例如用户帐户),那么我可能会将此作为一个致命的例外,所以我不必担心它。
答案 4 :(得分:0)
我每个层都有一个异常的异常类(DALException,BLException,...)来记录(例如:在文件中)层边界的异常(这是针对管理员的),因为用户只应该看清楚且易懂错误信息。这些异常应该处理所有数据访问层calsses所有层继承的DAlBase。我们可以在几个类中集中异常处理,开发人员只会抛出layerexception(例如:DALException) 查看更多信息MultiTier Exception Handling。