如何在库实现中冒泡/处理 C# 异常

本文关键字:处理 异常 实现 | 更新日期: 2023-09-27 18:37:03

从库中冒泡异常的最佳方法是什么?当我实现接口时,使用有关我的实现细节的异常来错误调用方是一种好的做法还是坏的做法?我有以下实现,其中每个实现错误都隐藏在远离调用方的地方。在我看来,这是实施关注点分离的最干净的方式。但我到处读到你需要冒泡

例外。
public class OneException : Exception
{
    public OneException()
    {
    }
    public OneException(string message): base(message)
    {
    }
    public OneException(string message, Exception innerException)
      : base(message, innerException)
    {
    }
}

我的库实现:

public class MyLib : IMyLib
{
    public int Divide(int a, int b)
    {
        try
        {
            if (b == 1) throw new OneException();
            return a / b;
        }
        catch (OneException e)
        {
            throw new ApplicationException("Please do not divide by 1", e);
        }
        catch (Exception e) // divide by zero and others
        {
            throw new ApplicationException("Oops", e);
        }
    }
}

如何在库实现中冒泡/处理 C# 异常

根据单一责任原则,您应该处理库应该做的事情(如果可能的话)。 否则,您应该让调用方处理它。

在您的示例中,您正确地抛出了一个被零除的异常。 处理此问题不是您的图书馆的责任(除非您将其宣传为"安全"的除法)。 该错误是由于不正确的客户端输入导致的,因此应该由客户端处理它。

但是,您的OneException将是一个不寻常的异常。 为什么客户端不能除以一? 它当然不会造成伤害。 这可能是一个人为的示例,但除非确实存在无法在内部解决的问题,否则您不应抛出异常。 如果有人想除以一,为什么不应该允许他们这样做?

良好的异常处理将通知客户端其输入和/或系统故障的问题,而不是逻辑中的错误。

异常冒泡选择正确的策略取决于两个主要因素:

  1. 是否要向调用方提供有关异常上下文的尽可能多的详细信息?
  2. 是否要对调用方隐藏敏感的异常数据?

如果要提供有关异常上下文的更多详细信息,将异常包装到某个更具体的异常中是一个不错的策略。这里最明显的例子是业务异常

如果要对异常隐藏敏感数据,可以考虑替换异常。此方法通常用于服务中,当调用方不受信任时。

注意:您永远不应该"吞下"异常,除非它不是正常程序流的一部分。

作为一般规则,只处理那些你可以处理的异常。

在库实现中,您可以捕获不同的异常OneExceptionException并将其重新抛出为 ApplicationException 。我认为这是一种不好的做法,因为您完全隐藏了原始错误的含义。

使用库 Divide() 方法的客户端代码应该如何知道如果冒泡的唯一异常是没有原因线索的通用ApplicationException,究竟发生了什么?

您在这里要做的是抛出一个异常,在相同的方法中捕获它,然后重新抛出一个不同的异常。

让异常冒泡到客户端代码,以便客户端可以自己决定如何处理它 - 或者如果有的话

这取决于。如果要处理异常并由于某些业务规则引发自定义异常,请确保处理异常并记录它或引发新的自定义异常,例如在示例代码中,您正在为divide by 1引发异常。

另一个示例可能是,如果要在表中输入数据并获取主键约束异常,则可以捕获异常并根据库规范引发自己的自定义异常,也可以保留异常,以便在调用代码中处理。

如果要维护内部错误/异常日志,则处理库中的异常可能会有所帮助。您可以记录异常,然后将其抛出,以便它可以冒泡到调用代码。但恕我直言,如果您既不记录也不引发自定义异常,最好保持异常不变,以便可以在调用代码中处理它

尝试记录异常,这始终是一种很好的做法。这样,客户实际上就会知道出了什么问题。

问自己这些问题:

  • 作为开发人员,您希望使用者(调用方)了解您的库的哪些信息?

  • 作为消费者,如果发生错误,您希望什么?

仅当标准 .Net 异常不足以达到目的时,才应开始公开自己的异常类型。这方面的一个例子是与 COM 相关的错误 - 您可能希望为特定错误条件创建自定义异常,以避免抛出嵌入了特殊数字的 COMExceptions。

我会说你示例中的OneException是浪费时间——为什么要扔、接住然后包装和重新投掷作为标准例外?为什么不一开始就抛出ApplicationException呢?这接近于使用异常来控制程序流,这是一种反模式。

系统涵盖了这两种例外情况。对于 b = 1 的情况,请使用 ArgumentOurOfRange 异常,对于 b = 0 的情况,请使用 DivideByZeroException。因此,对于非常简单的示例库,无需创建其他异常(这不是一个好的做法)。立即抛出 not 的异常是一个要求问题。在大多数情况下(良好做法),你会抛出调用方可以使用try/catch(/last)处理的异常。