是否有从工具包API中抛出异常的最佳实践或行业标准?
面向方法的用户是否应该抓住某些Exception
中的CustomException
,以便用户只需担心CustomException
出现在API中?
或者只是让那些泡沫破灭的惯例?
我们关注的是能够记录我们的API方法可能引发的所有可能的异常。 (例如,如果我们的API方法调用引发4或5个异常的Stream.Write()
,除了其他被调用方法可能抛出的其他异常之外,我们还必须记录所有这些异常。)
我们正在考虑做这样的事情:
public void customerFacingApiMethod(){
try {
//api functionality goes here
} catch (Exception e) {
throw new CustomException(e);
}
}
答案 0 :(得分:13)
在我看来,API只抛出一个异常类型是个坏主意。不同异常的好处之一是您可以选择捕获不同类型的异常并以不同方式处理它们。将异常包装到单个异常类型中将删除该工具。
在适当的地方使用框架提供的异常类型,并在适当的情况下为特定情况创建自己的自定义异常类型。最重要的是,确保为每种方法记录他们可能抛出的异常。
答案 1 :(得分:3)
在抛出异常时,请确保面向用户的异常都与用户在您的工具包方面确实出错的内容相关。这可能意味着将不同的文件系统异常捕获或合并到一个异常中,但是您不应该只捕获异常并抛出一个新异常 - 这实际上并没有告诉工具包用户他们做错了什么。
答案 2 :(得分:3)
您应该遵循与.NET Framework相同的概念。您的用户已经使用该框架,因此他们知道它是如何工作的并且期望某些行为。
ArgumentException
派生的异常(请视情况使用ArgumentNullException
,ArgumentOutOfRangeException
等。)IOException
派生的异常。等等...
在您的文档中,清楚地说明公共API的前提条件,并说明失败时将抛出的异常(如MSDN)。
答案 3 :(得分:1)
我不喜欢它。如果将所有异常包装到CustomException中,则很难捕获特定的异常,而无需在InnerExcpetion的对象内部进行搜索(如果您的API也使用其他执行相同操作的API,则可能会有多个级别)。如果有一个OutOfMemoryException,我想知道这一点,而不必去搜索它。
答案 4 :(得分:1)
不要对所有人使用相同的例外。
您需要不同的异常类型才能将一个案例与另一个案例分开。对于任何你让它流动起来直到一般异常处理程序块捕获它的东西看似k,但你阻止任何其他代码恢复 - 对特定情况进行操作的额外动作。
如果那些异常适合可由调用代码处理的特定方案,则可以包装一组异常。不要忘记,一些框架异常已经很好地传达了这种情况。
答案 5 :(得分:1)
Fredrik Mork所说的是非常真实的。我还想补充一点,你可以轻松地记录xml注释(和GhostDoc)引发的异常。
因此API用户可以在文档中查找以查看可以抛出的异常。但是,有一个缺点。
如果你的API中有一个深度的callstack,特别是如果圈复杂度变高(即可能的分支数量),你无法恢复所有可能抛出的异常(可能是运行时?)
所以我建议只有在有可能无法恢复的情况下抛出,并且只抛出API的上层,即深度不超过2。并捕获在callstack中更深入抛出的所有异常,并将它们作为InnerException或类似的东西返回。
答案 6 :(得分:1)
要考虑两种类型的异常(忽略StackOverflowException等,无论如何都无法做任何事情):
通常有意义的是,IO类型的异常会在未修改的情况下向上传递调用堆栈,因为无论如何都没有任何关于它的事情。如果您的API在GUI级别运行(这将是非常不寻常的),那么例外情况可能是您可能会重试的错误,或者询问用户该做什么。
请务必记录您的API方法可以抛出此类型的哪些异常。
在第二种类型(总是可以防止的异常)中,这些异常可能由您的API在错误的输入上生成,但绝不应该从较低级别向上传递调用堆栈。应该验证您调用的任何内容的输入,以便不会发生这些错误,并且如果由于某种原因它们可能发生,那么应该包含这些异常。否则,使用API的代码必须处理错误抽象级别的异常。
再一次,请务必记录哪些输入不会产生异常,以及如果这些规则被破坏会产生什么样的异常。
在所有情况下,抛出对API用户有意义的异常,而不是编程API本身的人。
答案 7 :(得分:1)
在一般情况下捕获System.Exception的问题在于,通过这样做,你实际上是在说“我不知道为什么会失败,这没关系,因为我知道无论如何我都可以继续! “
这很少,如果有的话,是真的。
在单个自定义异常类型中包含所有异常意味着您的消费者将只捕获该类型 - 这就是捕获System.Exception的道德等价物。虽然你可以通过这样做满足一些策略/ FXCop规则,但你仍然只是捕获System.Exception。
对于你的例外政策,我首先要问的是:“如果我不知道这个API实际上与之交谈了什么,我会期待什么样的例外?”请记住,方法是对对象做某事的请求,而异常是对象让你知道它不可能的方式,为什么。
答案 8 :(得分:0)
如果您创建一个在整个API中使用的异常类型,则其含义对于遇到该异常类型的客户端应用程序将是模糊的。相反,请考虑为您的API创建异常类层次结构。
为此,首先定义一个抽象基本异常类型:
CustomException
...然后从中派生出更具体的异常类型。例如:
CustomFooException
CustomBarException
CustomFizException
CustomFuzException
永远不会直接从您的API中抛出抽象基类型CustomException
。但是,如果API的客户端不关心从您的API抛出哪个特定的派生异常,则它的客户端可以捕获基本异常类型CustomException
。
有关开发异常类层次结构的更多信息,请参阅this answer以及此问题的其他答案:Why create custom exceptions in .NET?
另请参阅this answer,其中介绍了使异常层次结构的基类抽象化的想法。