以下的代码可以被认为是一个好的编程实践吗?如果不能,为什么?
try
{
// code that can cause various exceptions...
}
catch (Exception e)
{
throw new MyCustomException("Custom error message", e);
}
try
{
GetDataFromNetwork("htt[://www.foo.com"); // FormatException?
GetDataFromNetwork(uriArray[0]); // ArrayIndexOutOfBounds?
GetDataFromNetwork(null); // ArgumentNull?
}
catch(Exception e)
{
throw new WeClearlyKnowBetterException(
"Hey, there's something wrong with your network!", e);
}
try
{
ImportDataFromDisk("C:\ThisFileDoesNotExist.bar"); // FileNotFound?
ImportDataFromDisk("C:\BobsPrivateFiles\Foo.bar"); // UnauthorizedAccess?
ImportDataFromDisk("C:\NotInYourFormat.baz"); // InvalidOperation?
ImportDataFromDisk("C:\EncryptedWithWrongKey.bar"); // CryptographicException?
}
catch(Exception e)
{
throw new NotHelpfulException(
"Couldn't load data!", e); // So how do I *fix* it?
}
这并不意味着你永远不应该这样做!
Exception
是您可以捕获的最具体的异常,因为您想以相同的方式处理所有异常-例如:Log(e); Environment.FailFast();
catch(InvalidOperationException e)
并包括我们正在处理的文件的路径。FooModuleException
与BarModuleException
区分开来,而不管实际问题是什么-例如:如果有一些异步操作可能会妨碍您对堆栈跟踪进行有用的询问。这都是相当一般的东西。更具体地说,针对您的情况:您为什么觉得需要自定义异常类型?如果我们知道了这一点,或许可以给您提供更好的量身定制的建议。
- 不要捕获致命的异常; 无论如何,你都不能对它们做任何事情,尝试捕获会使情况更糟。
- 修复代码,使其永远不会触发一个愚蠢的异常-一个“索引超出范围”的异常在生产代码中永远不应该发生。
- 尽可能避免出现令人烦恼的异常,通过调用那些在非异常情况下抛出的“Try”版本的令人烦恼方法来实现。 如果无法避免调用令人烦恼的方法,请捕获其令人烦恼的异常。
- 始终处理指示意外外部条件的异常; 通常,并不值得或实际去预测每一个可能的失败。只需尝试操作并准备好处理异常。
ConfigurationException("无法初始化交易员模块", e)
的异常。高级别客户端(例如某些服务)将处理配置错误,而不是处理低级别的IndexOutOfRangeException
,FileNotFoundException
(以及基本的Exception
)。 - Sergey BerezovskiyModuleInitializationException
)——您应该调查日志以找到问题的源头——是编程错误还是缺少文件。这就是问题的调查部分,这就是为什么原始异常被分配给内部异常的原因。考虑一下你的胃——当它抛出异常时——只是MyStomachHurtsException
,而不是具体的东西 :) - Sergey BerezovskiyIndexOutOfRangeException
异常,但他们可能能够自行调查 MyCustomException
+ FileNotFound
异常,而不必打扰我。在有大量客户的情况下,这对我和我的客户都是一个巨大的时间节省者。 - Sergey Kalinichenko完全没问题。你不必分别捕获每个异常类型。如果你想用特定的方式处理,你 可以 捕获特定类型的异常。如果你想以相同的方式处理所有异常-捕获基础的Exception
并按照你的方式处理。否则,你将在每个捕获块中有重复的代码。
因此,我会说您的示例代码可能没问题。这确实取决于“自定义错误消息”(以及可能的异常自定义属性 - 确保它们是可序列化的)。它必须添加价值或含义以帮助诊断问题。例如,对我来说,这看起来相当不错(可能需要改进):
string filePath = ... ;
try
{
CreateTheFile(filePath);
DoThisToTheFile(filePath);
DoThatToTheFile(filePath);
...
}
catch (Exception e)
{
throw new FileProcessException("I wasn't able to complete operation XYZ with the file at '" + filePath + "'.", e);
}
这个不行:
string filePath = ... ;
try
{
CreateTheFile(filePath);
DoThisToTheFile(filePath);
DoThatToTheFile(filePath);
}
catch (Exception e)
{
throw new Exception("I wasn't able to do what I needed to do.", e);
}
catch (System.IO.IOException e)
和 throw new MyFileIOException("...")
以及 catch (Exception e)
和 throw new MyFileException("Something not related to file IO went wrong.")
的方式?换句话说,在一个通用的自定义异常之外,你可以添加一种或多种特定的自定义异常。或者,也可以在一个通用的自定义异常中结合 Exception.Data 使用。 - DavidRRcatch(Exception)
似乎没什么用,因为你最终会从最高级别的异常处理程序中获取到那些信息。 - Simon Mouriercatch
块,并从每个块中抛出不同的自定义异常。最后一个catch
块将捕获Exception
并抛出最通用的自定义异常(该异常将包装Exception
)。关于您的示例,可能是FileProcessException
。如果特别需要报告IO相关的异常,则使用FileProcessIOException
。 FileProcessIOException
将派生自FileProcessException
。 - DavidRRException
可以捕获任何东西;我并不建议复制它。我真正建议的是可能性,即创建一个自定义异常类层次结构,以传达特定于业务的上下文。基类FileProcessException
将表示与处理某个文件相关的所有异常。而派生类FileProcessIOException
将表示更具体的错误情况。客户端可以选择捕获其中任何一个。 - DavidRR前言: 我认为此模式仅在特定条件下并且完全理解其优缺点时才可使用。
说明 2: 有时我们希望从库成员中获得结果或告诉我们不可能,并且我们不关心所有细节,只需将详细信息发送给库开发人员。
从说明 1 和说明 2 得出的结论: 当实现一个库方法时,我们可以包装一个一般的异常并抛出一个自定义异常,其中包括源异常作为其InnerException。在这种情况下,使用此成员的开发人员将需要捕获一个异常,而我们仍将获得调试信息。
public string GetConfig()
{
try
{
var assembly = Assembly.GetExecutingAssembly();
var resourceName = "MyCompany.MyProduct.MyFile.cfg";
// ArgumentNullException
// ArgumentException
// FileLoadException
// FileNotFoundException
// BadImageFormatException
// NotImplementedException
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
// ArgumentException
// ArgumentNullException
using (StreamReader reader = new StreamReader(stream))
{
// OutOfMemoryException
// IOException
string result = reader.ReadToEnd();
}
return result;
// TODO: Read config parameter from DB 'Configuration'
}
catch (Exception ex)
{
throw new ConfigException("Unable to get configuration", ex);
}
}
catch (Exception ex)
{
ex.Data.Add(paramName);
throw;
}
补充: 我会按以下方式编辑您的模式:
try
{
// code that can cause various exceptions...
}
catch (Exception e)
{
if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException)
{
throw;
}
throw new MyCustomException("Custom error message", e);
}
CustomException.InnerException
可以访问包装的异常。换句话说,InnerException
是否提供了堆栈跟踪呢? - DavidRRtry {
something();
}
catch (Exception ex) {}
try:
something()
except:
pass
[HandleProcessCorruptedStateExceptions]
部分到函数中,那么像OutOfMemoryException
这样的系统关键异常将无法捕获。要了解何时应该处理它,请阅读此SO帖子以获取详细答案。我认为这个问题非常具有投机性,通常取决于特定的情况,但我做了一些研究并将分享它。首先,我想表达我对Lain Galloway代码的看法:
try {
GetDataFromNetwork("htt[://www.foo.com"); // FormatException?
GetDataFromNetwork(uriArray[0]); // ArrayIndexOutOfBounds?
GetDataFromNetwork(null); // ArgumentNull?
}
catch(Exception e)
{
throw new WeClearlyKnowBetterException(
"Hey, there's something wrong with your network!", e);
}
try {
GetDataFromNetwork();
} catch (FormatException ex) {
// here you should wrap exception and add custom message, which will specify occuring problem
}
当我为特定应用程序创建自定义异常时,我会从Exception扩展MyGeneralException,并且每个更具体的异常都将扩展MyGeneralException。因此,在您包装成自定义异常时,您应该在方法中添加throws MyGeneralException。
我使用了一条规则,这条规则是我从比我更有经验的开发人员那里学来的,即在第一次可能抛出某些外部异常的地方,应该将其包装成自定义异常,因为您不希望依赖于其他模块的异常。
然后,如果您在任何地方使用该方法,您只需要在方法签名throws中放置MyGeneralException,它将通过应用程序的层级向上冒泡。它应该被捕获并在最高级别进行处理,大多数情况下,异常消息用于由某些处理程序创建响应,或者可以手动处理。
主要在设计异常处理期间,应该考虑到,如果您的库将使用第三方开发人员,则他们不会对处理许多异常感兴趣。
catch (Exception e) { new MyCustomException("Custom error message", e); }
哎呀! - Eric Lippert