两个异常管理问题

本文关键字:异常 管理 问题 两个 | 更新日期: 2023-09-27 18:24:44

我有几个关于网站异常管理的问题:

  • 在catch块中,我可以有一个带有handle-exception方法的静态类来处理异常吗

       catch (Exception ex)
        {
            throw ExceptionHandler.HandleException(...);
        }
    

    其中ExceptionHandler.HandleException是一个返回System.Exception类型变量的静态方法。这是一个好的做法吗?这种方法可能有什么问题吗?它是线程安全的吗?

  • 在我的应用程序中,我有一个DAL层,它由业务层调用,业务层由UI调用。那么,重新抛出所有自定义异常,使它们直接弹出到显示它们的UI,而System.Exception类型被记录下来,我在catch块中抛出一个自定义异常,这是一种好的做法吗?例如在DAL和类似的业务层:

        catch (CustomExceptionBase ex)
        {
            throw;
        }
        catch (Exception sysEx)
        {
            ICustomExceptionBase ex = new SysException(sysEx);
            ex.Handle();
            throw BusinessException("Some problem while serving your request");
        }
    

    在UI层像这样

        catch (CustomExceptionBase ex)
        {
            //when custom exception bubbles up; code to display text to user on screen
        }
        catch (Exception sysEx)
        {
            ICustomExceptionBase ex = new SysException(sysEx);
            ex.Handle();
            //display error on screen;
        }
    

    这里CustomExceptionBase实现ICustomExceptionBase并继承Exception。系统异常&BusinessException都继承自CustomExceptionBase。

谢谢你抽出时间。。。

编辑在系统中重新思考的意图。异常块是这样的,如果出现致命错误,如数据库连接丢失或类似错误,我会将其记录到技术支持台,并返回一个票证编号,然后重新打印,这样用户就知道出了问题,这是您要跟进的参考号。对于DAL层或业务层中的所有自定义异常,我只是将其一直冒泡到显示文本的UI。

两个异常管理问题

我怀疑至少有些答案完全取决于您的体系结构。在第一种情况下,这完全取决于ExceptionHandler.HandleException究竟做了什么。它是根据某些条件生成新的异常,还是只返回原始异常?

它的线程安全与否完全取决于它的实现。例如,在以下琐碎的情况下,我认为它是线程安全的:

public static Exception ExceptionHandler.HandleException(Exception ex)
{
    return ex;
}

在其他情况下,它可能很容易不安全。例如:

public static string message;
public static Exception ExceptionHandler.HandleException(Exception ex)
{
    message = ex.ToString;
    sleep(2000);
    return new Exception(message);
}

后一个示例清楚地为消息变量提供了在另一个线程处于休眠状态时由其更改的范围。

至于第二个。。。应该在有意义的地方处理异常。没有硬性规定。如果代码的某个部分能够从异常中恢复(或者愿意跳过它),那么就在此时捕获它,而不是更早。如果一个异常真的是致命的,那么任何事情都不应该试图抓住它并假装不是这样,所以你应该让它一直浮到顶部,并做一些事情,比如提醒你的用户事情已经崩溃,你需要重新启动或其他什么。

所以实际上,这取决于您的自定义异常的含义。如果他们只是说"你想重试",那么这与说"数据完整性已被破坏:0==1"的异常不同。这两者都可能是自定义的,所以真正由你来决定在哪里处理事情。

是的,您可以在catch块内调用静态异常处理程序,只要您不引用任何静态变量,它就可能是线程安全的。

你应该看看微软的企业图书馆。它具有几乎相同的设计,但使用web.config中定义的异常策略来控制如何冒泡、包装或丢弃异常。再加上应用程序日志记录块,您就有了一个完整的解决方案。

使用静态方法处理异常/重新抛出异常本身没有任何技术问题,但从最佳实践的角度来看,使用单个方法神奇地"处理"异常会让我觉得这是一种潜在的代码气味。例外就是例外,每个单独的情况都需要考虑,以确保你做的是正确的事情,所以我发现你的HandleException方法不太可能总是做一些明智的事情。

作为一个极端的例子,我知道有一个这样的应用程序,其中几乎每个方法都被封装在一个try-catch块中,并调用一个抛出通用MyApplicationException类型的静态异常处理程序方法。这是一个非常糟糕的想法:

  • 它扰乱了代码
  • 它使理解堆栈跟踪变得更加困难
  • 这使得调用者很难捕捉和处理特定的异常类型
  • 这使得抛出异常成为比以前更大的性能惩罚

我最喜欢的是一种没有实现的方法,它看起来有点像这样:

void SomeException()
{
    try
    {
        throw new NotImplementedException();
    }
    catch(Exception ex)
    {
        throw ExceptionHandler.HandleException(...);
    }
}

当然,最糟糕的是,它是完全无意识的。正如我之前所说的,异常是例外的——每个try ... catch块都需要仔细思考和考虑它应该如何表现,通用HandleException方法的使用是一个立即警告标志,表明情况可能并非如此。

正在重试异常

一般来说,您只应在两种情况下重新抛出异常:

  • 当您想向异常添加上下文信息时(例如正在处理的当前文件的名称)
  • 当您必须捕获异常以处理某些特定情况时,例如处理"磁盘空间不足"错误

    catch (IOException ex)
    {
        long win32ErrorCode = Marshal.GetHRForException(ex) & 0xFFFF;
        if (win32ErrorCode == ERROR_HANDLE_DISK_FULL || win32ErrorCode == ERROR_DISK_FULL)
        {
            // Specific "out of disk space" error handling code
        }
        else
        {
            throw;
        }
    }
    

"冒泡"(即在不做任何事情的情况下捕捉并重新思考异常)是完全不必要的——这就是异常已经被设计成可以自己完成的!

处理异常

其他人说"例外情况应该在有意义的地方处理",我甚至自己也给出了这个建议,但事后看来,我认为这不是特别有用的建议!:)

人们通常的意思是,由于特定原因,您应该处理异常,并且您应该根据该原因在应用程序中选择处理该异常的位置。

例如,如果你想显示一条错误消息来通知用户,如果你遇到访问被拒绝的错误,他们没有修改文件的权限,那么你的UI代码中可能有一个特定的try-catch块:

catch (IOException ex)
{
    long win32ErrorCode = Marshal.GetHRForException(ex) & 0xFFFF;
    if (win32ErrorCode == ERROR_ACCESS_DENIED)
    {
        // Display "access denied error"
    }
    else
    {
        throw;
    }
}

请注意,这是我们希望处理的一个非常具体的情况-它只捕获我们感兴趣的特定异常类型执行额外的检查以筛选到我们感感兴趣的具体情况。

或者,如果您想记录未处理的错误或向用户正常显示错误消息,而不是IIS 505错误屏幕,则可以在Global.asax中或通过自定义错误页面-ASP.Net自定义错误页面执行此操作

我的观点是,在处理异常时,我们会仔细考虑我们希望在应用程序功能(例如重试逻辑、错误消息、日志记录等)方面实现什么,并实现我们的异常处理逻辑,以尽可能有针对性的方式专门解决这些需求-没有神奇的异常处理框架,也没有样板密码

尽可能避免出现异常

我通常发现,最好的策略就是尽可能完全避免异常!例如,如果你的页面解析了用户输入的数字,并且你想在他们输入愚蠢的值时显示验证消息,那么就提前验证你的输入,而不是捕捉异常:

错误:

void DoSomething()
{
    int age = int.Parse(ageTextBox.Text);
    if (age < 0)
    {
        throw new ArgumentOutOfRangeException("age must be positive");
    }
    if (age >= 1000)
    {
        throw new ArgumentOutOfRangeException("age must be less than 1000");
    }
}
void Button_Click(object sender, EventArgs e)
{
    try
    {
        DoSomething();
    }
    catch (Exception ex)
    {
        DisplayError(ex.Message);
    }
}

好:

void Button_Click(object sender, EventArgs e)
{
    int age;
    if (!int.TryParse(ageTextBox.Text, out age))
    {
        DisplayError("Invalid age entered");
    }
    if (age < 0)
    {
        DisplayError("age must be positive");
    }
    if (age >= 1000)
    {
        DisplayError("age must be less than 1000");
    }
    DoSomething();
}

用户一直在输入无效数据——处理这实际上是应用程序逻辑,不应该属于异常处理的范畴——这当然不是我称之为"异常"的事件。

当然,这并不总是可能的,但是我发现使用这种策略可以简化代码,并使其更容易遵循应用程序逻辑。

首先我们需要考虑异常类型,在任何业务异常中都可以是BusinessException或Technical/SystemException。

  1. 在BusinessException中,我们可以发送带有错误的自定义异常详细信息。

  2. 在技术/系统异常中,我们不应该处理它,而是让它弹出到UI层。UI可以决定应在中显示的错误例外情况。

  3. 在您的方法中,如果您处理异常或抛出自定义异常调用跟踪丢失。