【问题标题】:Managing exceptions in a .NET client library for a web API为 Web API 管理 .NET 客户端库中的异常
【发布时间】:2015-04-08 21:02:20
【问题描述】:

我正在为公共 Web API 构建一个 .NET 客户端库。我正在努力解决的一件事是如何最好地向库的用户指示错误情况。显然,当执行失败时,我的库应该向调用者抛出或传递异常,但是我应该使用现有的异常还是为我的库创建新的异常?如果我创建新的异常,我应该只创建一个异常还是多个?

选项 1:仅使用现有例外

在最基本的情况下,我可以例如 throw new Exception("API authentication error.");,但这是非常通用的,之后需要大量处理才能处理。我还可以使用更合适的现有异常,例如System.Security.Authentication.AuthenticationException,但是由于所有不同的命名空间,使用该库会很尴尬。在某些情况下,没有什么比InvalidOprationException 更合适的了,因此,对于使用该库处理错误的人来说,采用这种方法似乎会变得很麻烦。最简单的可能是只使用HttpException 类,因为大多数错误条件在任何情况下都是 HTTP 状态代码和消息的转换。

选项 2:为每种情况创建一个例外

我还可以为每个场景创建一个例外,但将它们全部保存在库中。所以我可以创建一个NotFoundExceptionAuthorizationExceptionTransactionsPendingException 和其他特定的异常。

选项 3:为我的客户创建一个例外

第三个选项是创建一个自定义异常MyApiException,其中包含出现的不同错误的状态代码(当然还有适当的消息)。所以我可能会throw new MyApiException(401,"Not authorized.")

在我看来,选项 3 是最容易实现且调用方法最容易处理的选项。我在 GitHub 和 Codeplex 上看到过使用这些选项的 API 客户端。是否有推荐的客户端库方法或所有方法都有效?

【问题讨论】:

标签: c# .net exception


【解决方案1】:

微软在 MSDN 上有一些关于此的有价值的信息:

https://msdn.microsoft.com/en-us/library/seyhszts%28v=vs.110%29.aspx

就个人而言,我倾向于做以下事情:

  1. 在有意义的时候使用现有的异常

    例如当传递一个无效的键时,一个基于键的操作抛出KeyNotFoundException

  2. 为需要的场景创建自定义例外

  3. 避开异常以支持Either<TSuccess,TFail> 这样的结果类型
    1. 这里有一个例子:https://gist.github.com/siliconbrain/3923828
    2. 这对于迭代器块等可能引发异常的问题或需要支持 continue-on-error 时特别有用

现有异常

public int GetMyItem(string key)
{
   if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key");
   if (!_myDictionary.ContainsKey(key))
   {
      throw new KeyNotFoundException();
   }
   // Additional implementation left out
}

自定义异常

public class ArgumentNotOddException : ArgumentException
{
    public ArgumentNotOddException(string paramName)
        : base(@"The argument must be odd", paramName)
    {

    }
}

public class MyClass
{
    public void DoSomething(int x)
    {
        if (x%3 == 0) throw new ArgumentNotOddException("x");
    }
}

任一种类型

public class MyClass
{
    public IEnumerable<IEither<int, string>> Divide(int left, IEnumerable<int> right)
    {
        foreach (var num in right)
        {
            if (num == 0)
            {
                yield return Either.Right<int, string>("cannot divide by zero");
            }
            yield return Either.Left<int, string>(left/num);
        }
    }
}

您也可以只创建一个使用异常代码和消息的MyAppException,但随后您会失去表达能力,异常代码容易输入错误(除非您使用枚举),而MyAppException 不会和NotFoundException说的完全一样。

我最好的建议是认真考虑代码是否应该抛出,或者返回混合结果/错误是否合理;在 C# 中这样做有点笨拙,但它消除了未处理异常的负担,并允许您在调用代码时更加灵活。

【讨论】:

  • 谢谢@Clint,那里有一些有用的建议。我为应用程序设置一个异常的原因是它使调用代码更容易捕获和处理。您可以只使用catch (MyApiException ex) 并根据状态代码适当地处理结果。这相当于处理 Either 结果类型的失败端,但更容易实现?
  • @rudivonstaden 可能,但是如果一个方法返回多个错误代码怎么办?然后你必须在捕获它之后使用 if / switch 语句来处理它,如果它吐出你不期望的代码会发生什么?
  • @rudivonstaden 真的,这表明一个方法应该更小,并且只做一个简单的通过/失败类型的结果,这里没有一刀切的答案,但我喜欢非此即彼的方法由于它遵循大多数函数式语言(例如 f#)设置的模式,您仍然可以抛出异常,但最好的方法是将失败视为程序流程的一部分。
  • 只有一个错误代码。大概是它遇到的第一个错误,它阻止了请求的成功处理。如果它吐出意外的代码,您仍然可以使用try..catch 捕获它。默认情况下,您可以假设该操作没有工作并做出相应的响应。我查询 Either 方法的原因是 Microsoft 建议不要在您链接到的最佳实践页面中返回错误代码。我仍在思考利弊。
  • @rudivonstaden 欢迎来到错误处理的精彩世界!最佳实践现在已经很老了,我大多数时候都同意异常,只是有时应用程序错误(异常)和处理过程中的可恢复错误之间存在差异,可能导致错误代码或某些默认值.这真的取决于每个案例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-12-10
  • 1970-01-01
  • 1970-01-01
  • 2019-09-18
  • 2015-08-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多