在 C# 中记录冒泡异常的正确方法

本文关键字:方法 异常 记录 | 更新日期: 2023-09-27 18:33:30

下面是我有一个简化的设置示例。我的问题是异常在低水平上抛出,然后会冒泡。但是,较高级别的类不知道在使用较低级别的类函数时等待他们的是什么恐怖。我的意思是开发人员会来使用Class3.DoWork3((,而对可怕的"MyCustomException"没有任何异常处理。

我的做法是不是错了?我不想多次捕获并抛出相同的异常(例如在 Class2 中,我不需要进行任何类型的清理(。

public class Class1
{
    <exception cref="MyCustomException">description</exception>
    public void DoWork1()
    {
        throw new MyCustomException("I have failed you class1.");
    }
}
public class Class2
{
    public void DoWork2()
    {
        var classOne = new Class1();
        // Here I can see that DoWork1() will throw a 'MyCustomException'
        classOne.DoWork1();
    }
}
public class Class3
{
    public void DoWork3()
    {
        var classTwo = new Class2();
        // I can no longer see that Class2.DoWork2() will throw a 'MyCustomException'
        classTwo.DoWork2();
    }
}

为了消除评论中似乎产生的一些混乱,我将提出我目前正在考虑的两个解决方案:

  1. 使用与 Class1 相同的 MyCustomException 注释标记 DoWork2
  2. 在 DoWork2 中抛出一个不同的异常,并用这个标记它新例外。此选项似乎是最强大的,因为它允许更详细的日志(例如,代码在其中的确切位置执行路径(。这种方式似乎有点过分简单的场景,这就是为什么我想知道(1(是否是可以接受的解决这个问题吗?

在 C# 中记录冒泡异常的正确方法

.NET 和 Java 中异常处理的主要限制之一是,代码无法区分由于方法预期的原因而从方法引发的某种类型的异常,以及由于外部方法未预料到的原因而由嵌套方法引发的相同类型的异常。 如果一个方法的目的是调用某个用户提供的委托,那么据我所知,可靠地区分被调用委托抛出的异常和调用它的过程中发生的异常的唯一方法是向该方法传递一个标识令牌,它将包含在它直接抛出的任何异常中, 或者,将该方法作为调用用户提供的回调时发生的策略包装异常的问题。 这两种方法都不是非常优雅。

因为没有标准化的方法来识别指示操作"干净"失败而没有副作用的异常,从那些指示存在更严重问题的异常中,我建议代码不应该依赖"严重"异常来防止"传染性"数据损坏。 相反,每当异常可能导致对象处于损坏状态时,都应显式使该对象失效,以防止从中读取损坏的数据并将其复制到其他地方(可能覆盖唯一好的副本(。 在不需要损坏的对象并且堆栈展开会导致其失效的情况下,没有问题。 在失效的对象被证明对系统操作至关重要的情况下,程序应该被强制关闭,并且使对象失效将比引发"严重"异常更可靠地发生这种情况。

遗憾的是,C# 和 VB.NET 都没有提供非常好的模式来以这种方式保护对象。 VB.NET 具有比 C# 更好的异常处理能力,并且正确的模式(根据引发的异常在 finally 块中执行操作,但不捕获异常(可以用该语言实现,尽管很笨拙。 在 C# 中,找出try块中发生异常的唯一方法是捕获并重新引发,该操作具有一些副作用。

Anders Hejlsberg 有一个很棒的采访,题为 检查异常的麻烦.(Anders 领导的团队设计了 C# 编程语言。

您正在陷入异常聚合陷阱。您可能不应该问"较低级别的系统可能会引发哪些异常?在许多情况下,答案将是一个包含数百个唯一例外的列表。

相反,您应该问自己"我如何向开发人员提供有用的信息?应记录代码显式引发的异常,或者在极少数情况下,记录很可能发生且调用方可能特别感兴趣的相关异常。调用代码的开发人员可以确定处理这些错误或将该信息传递给其代码调用方的必要性 - 或者干脆不执行任何操作。毕竟,异常是例外的,在某种程度上,抛出哪个数据库错误并不重要 - 响应中采取的步骤是相同的(例如,如果可能的话,稳定环境,写入错误日志并终止执行(。