在为其他开发人员提供的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);
}
-
不要投掷
Exception
。相反,抛出一个适当的派生类型。 -
除非您有其他详细信息要提供,否则不要包装异常(来自
catch
的throw
(
(比如它在哪个页面上失败,或者你认为它为什么失败( -
包装异常时,始终将原始异常作为
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
中提供原始异常(。然而,由于您有效地抛出了两个异常(ArgumentOutOfRangeException
和Exception
(,因此API消费者并没有真正得到更有意义的异常。
ArgumentOutOfRangeException
是合适的(尽管如果你使用的是.NET 4,你应该真正使用代码契约的东西,在这种情况下,你的参数检查应该只是Contract.Requires<ArgumentOutOfRangeException>(id > 0, "The user ID must be greater than 0.");
。一般的Exception
抛出应该被删除,或者用更有意义的异常类型替换。