n 层应用程序中的异常处理?

发布于 2024-09-30 17:50:55 字数 986 浏览 2 评论 0原文

在分层应用程序中处理异常的建议方法或最佳实践是什么?

  • 您应该在哪里放置 try/catch 块?
  • 您应该在哪里实施日志记录?
  • 是否有建议的模式用于管理 n 层应用程序中的异常?

考虑一个简单的例子。假设您有一个 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);
    }
}

您对上述异常处理有何批评?

谢谢

What is the suggested approach or best practice for handling exceptions in tiered applications?

  • Where should you place try/catch blocks?
  • Where should you implement logging?
  • Is there a suggested pattern for managing exceptions in n-tiered applications?

Consider a simple example. Suppose you have a UI, that calls a business layer, that calls a data layer:

//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);
    }
}

What criticisms would you make of the above exception handling?

thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

情话墙 2024-10-07 17:50:55

我的经验法则通常是在顶层捕获异常并在那里记录(或以其他方式报告)它们,因为这是您拥有有关错误的最多信息的地方 - 最重要的是完整的堆栈跟踪。

然而,可能有一些原因需要在其他层捕获异常:

  1. 异常已实际处理。连接失败,但层会重试。
  2. 重新抛出异常并提供更多信息(通过查看堆栈尚无法获得这些信息)。例如。 DAL 可能会报告它正在尝试连接到哪个数据库,而 SqlException 不会告诉您。
  3. 异常被转换为更通用的异常,它是该层接口的一部分,并且可能(或可能不)被更高层处理。例如。 DAL 可能会捕获连接错误并抛出DatabaseUnavailableException。对于不重要的操作,BL 可能会忽略这一点,或者对于那些不重要的操作,BL 可能会让它传播。相反,如果 BL 捕获了 SqlException,它将暴露于 DAL 的实现细节。相反,抛出DatabaseUnavailableException 的可能性是DAL 接口的一部分。

在多个层记录相同的错误通常没有用,但我可以想到一个例外:当较低层不知道问题是否严重时,它可以将其记录为警告。如果更高层认为它很重要,那么它可以将相同的问题记录为错误。

My rule of thumb is generally to catch exceptions at the top level and log (or otherwise report) them there, because this is where you have the most information about the the error - most importantly the full stack trace.

There may be some reasons to catch exceptions in other tiers, however:

  1. The exception is actually handled. Eg. a connection failed, but the tier re-tries it.
  2. The exception is re-thrown with more information (that isn't already available from looking at the stack). Eg. the DAL might report which DB it was trying to connect to, which SqlException wouldn't tell you.
  3. The exception is translated into a more general exception that is part of the interface for that tier and may (or may not) be handled higher up. Eg. the DAL may catch connection errors and throw a DatabaseUnavailableException. The BL may ignore this for operations that are not critical or it may let it propogate for those that are. If the BL caught SqlException instead it would be exposed to the implementation details of the DAL. Instead the possibility of throwing DatabaseUnavailableException is part of the interface of the DAL.

Logging the same error at multiple tiers is generally not useful, but I can think of one exception: when a lower tier doesn't know whether a problem is critical or not it can log it as a warning. If a higher tier decides that it is critical it can then log the same problem as an error.

等往事风中吹 2024-10-07 17:50:55

以下是我遵循的与异常处理相关的一些规则:

  • 异常应该只在可以实现处理它们的逻辑的层中捕获。大多数情况下,这种情况发生在最上层。根据经验,在层内实现 catch 之前,问问自己任何上层的逻辑是否以任何方式依赖于异常的存在。例如,在业务层中,您可能有以下规则:如果外部服务可用,则从外部服务加载数据,如果不可用,则从本地缓存加载数据。如果调用服务抛出一个错误,您可以在 BL 级别捕获并记录它,然后从缓存中返回数据。在这种情况下,上层 UI 层不必采取任何操作。但是,如果服务和缓存调用都失败,则异常必须转到 UI 级别,以便可以向用户显示错误消息。
  • 应用程序内的所有异常都应该被捕获,如果没有特殊的逻辑来处理它们,那么至少应该记录它们。当然,这并不意味着您必须将所有方法的代码包装在 try/catch 块中。相反,任何应用程序类型都有一个用于未捕获异常的全局处理程序。例如,在 Windows 应用程序中,应实现 Application.ThreadException 事件。在 ASP .Net 应用程序中,应实现 global.asax 中的 Application_Error 事件处理程序。这些位置是您可以在代码中捕获异常的最上面的位置。在许多应用程序中,这将是您捕获大多数异常的地方,除了日志记录之外,您还可能在这里实现一个通用且友好的错误消息窗口,该窗口将呈现给用户。
  • 您可以在需要的地方实现 try/finally 功能,而无需实现 catch 块。如果不需要实现任何异常处理逻辑,则不应实现 catch 块。例如:
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();
}
  • 如果您需要从 catch 块重新抛出异常,只需使用 throw; 即可。这将确保保留堆栈跟踪。使用 throw ex; 将重置堆栈跟踪。
  • 如果需要使用对上层更有意义的另一种类型重新抛出异常,请将原始异常包含在新创建的异常的 InnerException 属性中。
  • 如果您需要从代码中抛出异常,请使用有意义的异常类型。

Here are a few rules I follow related to exception handling:

  • exceptions should only be caught in the layers that can implement the logic for handling them. Most of the cases this happens in the upmost layers. As a rule of thumb, before implementing a catch inside a layer, ask yourself if the logic of any of the upper layers depends in any way on the existence of the exception. For example in your business layer you might have the following rule: load data from an external service if the service is available, if not load it from the local cache. If calling the service throws an exption you can catch and log it at BL level and then return the data from the cache. The upper UI layer does not have to take any action in this case. However, if both the service and the cache call fail the exception would have to go to the UI level so that an error message can be displayed to the user.
  • all exceptions inside an application should be caught and if there is no special logic for handling them, they should be at least logged. Of course, this does not mean that you have to wrapp the code from all methods in try/catch blocks. Instead any application type has a global handler for uncaught exceptions. For example in Windows Applications the Application.ThreadException event should be implemented. In ASP .Net applications the Application_Error event handler from global.asax should be implemented. These places are the upper most locations where you can catch exceptions in your code. In many applications this will be the place where you will catch most of the exceptions and besides logging, in here you will probably also implement a generic and friendly error message window that will be presented to the user.
  • you can implement try/finally functionality where you need it without implementing a catch block. A catch block should not be implemented if you do not need to implement any exception handling logic. For example:
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();
}
  • if you need to rethrow an exception from a catch block do that by simply using throw;. This will ensure that the stack trace is preserved. Using throw ex; will reset the stack trace.
  • if you need to rethrow an exception with another type that would make more sense for the upper levels, include the original exception in the InnerException property of the newly created exception.
  • if you need to throw exceptions from your code use meaningful exception types.
清晰传感 2024-10-07 17:50:55

首先要解决的问题是永远不要抛出一般异常

第二,除非确实有充分的理由包装异常,否则只需在 catch 子句中使用 throw; 而不是 throw new...

第三(这不是一个硬性规定),不要在 UI 层以下的任何点捕获一般异常。 UI 层应该捕获一般异常,以便可以向最终用户显示用户友好的消息,而不是发生爆炸的技术细节。如果您在更深的层中捕获一般异常,则它可能会无意中被吞没,并导致很难追踪的错误。

The first thing to fix would be to never throw a general Exception.

The second, unless there's really a good reason to wrap an exception, just have throw; instead of throw new... in your catch clause.

The third (and this isn't a hard & fast rule), don't catch general Exceptions at any point below the UI layer. The UI layer should catch general Exceptions so that a user-friendly message can be displayed to the end user, not the technical details of what blew up. If you catch a general exception deeper in the layers it's possible for it to be swallowed unintentionally and makes for bugs that are quite difficult to track down.

末が日狂欢 2024-10-07 17:50:55

异常处理在任何应用程序中都是困难的。您需要考虑每一个例外情况,并快速形成适合您的模式。我尝试将异常分为以下类别之一...

  • 只有在您的代码错误(或者您不理解,或者您无法控制它们)时才应该发生的异常:

示例:也许您的持久性框架要求您捕获可能由格式错误的 SQL 引起的 SQL 异常,但是,您正在执行硬编码的查询。

处理:根据我的经验,大多数异常都属于这一类。至少,记录它们。更好的是,将它们发送到记录它们的异常处理服务。然后,将来如果您决定要以不同的方式记录它们或对它们执行不同的操作,您可以在一处进行更改。也许您还想向 UI 层发送一个标志,表明发生了某种错误,他们应该重试操作。也许您给管理员发了邮件。

您还需要向更高层返回一些内容,以便服务器上的生命继续进行。也许这是一个默认值,或者也许这是空。也许您有某种方法可以取消整个操作。

另一种选择是为异常处理服务提供两种处理方法。 handleUnexpectedException() 方法会通知用户,但不会重新抛出异常,如果您能够自行展开堆栈或以某种方式继续,则可以返回默认值。 handleFatalException() 方法会通知用户并重新抛出某种异常,以便您可以让抛出的异常为您展开堆栈。

  • 实际由用户引起的异常:

示例: 用户尝试更新 foobar 小部件并为其指定新名称,但具有该名称的 foobar 小部件已存在他们想要。

处理:在这种情况下,您需要将异常返回给用户。在这种情况下,您可以继续抛出异常(或者更好,甚至不捕获它),直到它到达 UI 层,然后 UI 层应该知道如何处理异常。确保记录这些异常,以便您(或编写 UI 的任何人)知道它们的存在并知道它们的存在。

  • 您实际可以处理的异常:

示例:您进行远程服务调用,远程服务超时,但您知道他们有这样做的历史记录,您应该重做的电话。

处理:有时,这些异常从第一类开始。当你的应用程序运行了一段时间后,你意识到你实际上有一个很好的方法来处理它。有时,例如乐观锁定的异常或中断的异常,捕获异常并对其执行某些操作只是业务的一部分。在这些情况下,处理异常。如果你的语言(我认为是 Java)区分受检查和非受检查的异常,我建议这些异常始终是受检查的。

为了解决您的上述问题,我会将初始异常委托给一个服务,该服务会通知用户,并且根据 MyObj 是什么类型的对象(例如设置),我可能会让这是一个非致命异常并返回默认值,或者如果我不能这样做(例如用户帐户),那么我可能会让这是一个致命的异常,所以我不必担心它。

Exception handling is difficult in any application. You need to think about each and every exception and quickly get into a pattern that works for you. I try and group exceptions into one of the following categories...

  • Exceptions that should only happen if your code is wrong (or you don't understand, or you don't have control over them):

Example: Maybe your persistence framework requires you to catch SQL exceptions that could arise from malformed SQL, however, you're executing a hard coded query.

Handling: In my experience most exceptions fall in this category. At the very least, log them. Even better, send them to an exception handling service which logs them. Then in the future if you decide you want to log them differently or do something differently with them you can change it in one place. Maybe you also want to send a flag up to the UI layer saying that some kind of error occurred and they should retry their operation. Maybe you mail an administrator.

You will also need to return something to the higher layers so that life on the server goes on. Maybe this is a default value, or maybe this is null. Maybe you have some way of canceling the entire operation.

Another choice is to give the exception handling service two handling methods. A handleUnexpectedException() method would notify the user but not rethrow an exception and you could then return a default value if you have the ability to unwind the stack yourself or continue on in some way. A handleFatalException() method would notify the user and rethrow some kind of exception so that you can let the exception throwing unwind the stack for you.

  • Exceptions that are actually caused by the user:

Example: The user is trying to update a foobar widget and give it a new name, but a foobar widget already exists with the name they want.

Handling: In this case you need to get the exception back to the user. In this case you can go ahead and keep throwing the exception (or better, don't even catch it) until it reaches the UI layer and then the UI layer should know how to handle the exception. Make sure to document these exceptions so that you (or whomever is writing your UI) is aware that they exist and knows to expect them.

  • Exceptions that you can actually handle:

Example: You make a remote service call and the remote service times out but you know they have a history of doing so and you should just redo the call.

Handling: Sometimes these exceptions start off in the first category. After your application has been in the wild for a while you realize that you actually have a good way to handle it. Sometimes, like with exceptions from optimistic locking or interrupted exceptions, catching the exception and doing something with it is just a part of business. In these cases, handle the exception. If you're language (I'm thinking Java) distinguishes between checked and unchecked exceptions I would recommend these always be checked exceptions.

To address your above question I would delegate the initial exception to a service that would notify the user and depending on what kind of object MyObj is (e.g. settings) I might let this be a non-fatal exception and return a default value or if I can't do that (e.g. user account) then I might let this be a fatal exception so I don't have to worry about it.

夏花。依旧 2024-10-07 17:50:55

我为每一层使用单独的异常类(DALException,BLException,...)来记录(例如:在文件中)层边界处的异常(这是针对管理员的),因为用户只应该看到清晰且可理解的错误消息。这些异常应该在由所有数据访问层继承的 DAlBase 上处理,并在所有层上进行此类调用。这样我们就可以将异常处理集中在几个类中,开发人员只会抛出层异常(例如:DALException)
查看更多信息多层异常处理

I am with sepearate exception Class for each layer (DALException,BLException,...) to log (e.g: in file) the exceptions at the layer boundaries (this is for administrator) ,because user only should see clear and understandable error message. those exception should deal with on DAlBase which inherirted by all Data access layer calsses an so on all layers. with that we can centralize the exception handling in few class and the developer will only throw layerexception (e.g: DALException)
see more information MultiTier Exception Handling.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文