在为其他开发人员提供的API中处理异常时的最佳C#实践

本文关键字:异常 处理 最佳 实践 API 开发 其他 | 更新日期: 2023-09-27 18:08:59

如果你想分享更多信息,你可以在这里找到完整的来源指针:

https://github.com/sergiotapia/DreamInCode.Net

基本上,我的API将为其他开发人员提供一种从http://www.dreamincode.net-在我图书馆的一个方法中,我写了这样的代码:

public UserProfile FindUserById(int id)
{
    if (id <= 0)
        throw new ArgumentOutOfRangeException("id", id, "The user ID must be greater than 0.");
    string xmlEndPoint = string.Format("http://www.dreamincode.net/forums/xml.php?showuser={0}", id.ToString());
    string xmlResponse;
    using (WebClient client = new WebClient())
    {
        try
        {
            xmlResponse = client.DownloadString(xmlEndPoint);
        }
        catch (Exception e)
        {
            throw new Exception("Error: " + e.InnerException);
        }
    }
    if (String.IsNullOrEmpty(xmlResponse))
        throw new Exception("Error: The XML endpoint did not respond.");
    return UserParser.ParseUser(xmlResponse);
}

就对其他用户的有用性而言,我这样做是最好的方式吗?.InnerException是否足以让其他开发人员知道出了什么问题?

感谢您抽出时间。:(


编辑:

所以按照你的建议,我写道:

using (WebClient client = new WebClient())
{
    try
    {
        xmlResponse = client.DownloadString(xmlEndPoint);
    }
    catch (Exception e)
    {
        throw new Exception("Error: Something went wrong, please check the InnerException.", e);
    }
}

这是要走的路吗?这是在保留堆栈跟踪吗?我做得对吗?


编辑2:

所以这是理想的解决方案?

//Just let it explode?
using (WebClient client = new WebClient())
{
    xmlResponse = client.DownloadString(xmlEndPoint);
}

在为其他开发人员提供的API中处理异常时的最佳C#实践

  • 不要投掷Exception。相反,抛出一个适当的派生类型。

  • 除非您有其他详细信息要提供,否则不要包装异常(来自catchthrow(
    (比如它在哪个页面上失败,或者你认为它为什么失败(

  • 包装异常时,始终将原始异常作为InnerException构造函数参数传递。这提供了对原始堆栈跟踪的访问,以及异常中的任何附加信息。

的一些指导原则

  • 不要抛出Exception-它太通用了。

  • 不要使用throw ex;重新引发异常-它会丢弃调用堆栈。

  • Do如果传递了错误的参数,则抛出ArgumentOutOfRangeException

  • 考虑您调用的代码引发的捕获和包装异常。如果有,请使用预定义的匹配异常类型,否则定义自己的异常类型。除非尝试以使调试变得困难,否则永远不要丢弃捕获的异常。

  • 考虑包括有关异常的附加上下文,以便可以跟踪故障

  • 考虑在异常消息中提及内部异常,以便提醒其他开发人员查看("下载{1}时发生{0}错误;有关详细信息,请参阅内部异常"(

更新

针对Scott在下文中对上下文的评论。

如果查找特定用户的请求失败,原始异常包装到另一个提供更多上下文的异常中会很有用:

try
{
    ...
}
catch (Exception e)
{
    // Yes, catch every exception
    var message
        = string.Format(
            "Failed to load user profile for user {0}: {1} ({2})",
                id,
                e.GetType().Name,
                "see inner exception for details");
    throw new InvalidOperationException(message, e);
}

这很有用,因为实际错误可能只发生在特定的用户id上(例如,如果该特定用户的数据在底层数据库中已损坏(。提供这种附加上下文为试图调试问题的开发人员提供了更多信息。

一些建议:

  • 如果现有的.NET异常符合您的需要,请使用它们。ArgumentOutOfRangeException在您的示例中似乎很合适
  • 否则,只需创建一个新的异常类,并包含准确描述错误所需的任何额外数据
  • 考虑代码的上层是否需要以某种方式"过滤"异常(即捕获一些异常并让其他异常继续展开(。如果是,您可能需要考虑设计自己的异常继承层次结构,这样可以很容易地按类型筛选异常
  • 在任何情况下,都要尽量比简单的Exception更具体
  • 考虑异常消息是否会一直"冒泡"到最终用户。根据具体情况,这可能是个好主意,也可能不是个好主意
  • 如果您(或者更确切地说,API的客户端(让用户看到异常消息,请考虑将其本地化
  • 记录您的例外情况。您可以在///样式的注释中使用<exception>标记来执行此操作

SLaks提供的答案涵盖了您的大部分问题/关注点。如前所述,您应该很少(如果有的话(抛出一般异常,也不应该(如果有的时候(简单地包装现有异常并抛出新异常。你这里的代码有问题

using (WebClient client = new WebClient()) 
{ 
    try 
    { 
        xmlResponse = client.DownloadString(xmlEndPoint); 
    } 
    catch (Exception e) 
    { 
        throw new Exception("Error: " + e.InnerException); 
    } 
} 

API的使用者将被要求捕获Exception,然后检查InnerException以了解出错的实际细节。相反,如果你只是像一样编写代码

using (WebClient client = new WebClient()) 
{ 
     xmlResponse = client.DownloadString(xmlEndPoint); 
} 

任何API使用者都会看到WebClient调用引发的实际异常(可能更有意义(。

尽管如此,如果API背后的想法是隐藏这样一个事实,即当用户调用FindUserById时,他们实际上正在外出并在某个地方访问web服务,并且WebClient调用引发的任何异常实际上都将是更多的"噪音"(或以其他方式使API消费者更加困惑(,那么捕获异常并引发更有意义的异常是可以接受的(确保在InnerException中提供原始异常(。然而,由于您有效地抛出了两个异常(ArgumentOutOfRangeExceptionException(,因此API消费者并没有真正得到更有意义的异常。

ArgumentOutOfRangeException是合适的(尽管如果你使用的是.NET 4,你应该真正使用代码契约的东西,在这种情况下,你的参数检查应该只是Contract.Requires<ArgumentOutOfRangeException>(id > 0, "The user ID must be greater than 0.");。一般的Exception抛出应该被删除,或者用更有意义的异常类型替换。