我习惯于在每种方法中都有 try/catch 块。这样做的原因是我可以在违规点捕获每个异常并记录它。从我的阅读和与他人的交谈中,我明白这不是一个流行的观点。一个人应该只抓住准备处理的东西。但是,如果我没有抓住违规点,那么就可能永远不会记录该违规行为并了解它。注意:当我接住但不处理时,我仍然扔。这允许我让异常传播到可以处理它的东西,但仍然让我在违规点记录它。
那么......如何避免在每种方法中尝试/捕获,但仍然在错误发生时记录错误?
我习惯于在每种方法中都有 try/catch 块。这样做的原因是我可以在违规点捕获每个异常并记录它。从我的阅读和与他人的交谈中,我明白这不是一个流行的观点。一个人应该只抓住准备处理的东西。但是,如果我没有抓住违规点,那么就可能永远不会记录该违规行为并了解它。注意:当我接住但不处理时,我仍然扔。这允许我让异常传播到可以处理它的东西,但仍然让我在违规点记录它。
那么......如何避免在每种方法中尝试/捕获,但仍然在错误发生时记录错误?
不,不要抓住一切。异常在堆栈上向上传播。您所要做的就是确保异常在到达堆栈顶部之前被捕获。
这意味着,例如,您应该用 try/catch 块包围事件处理程序的代码。事件处理程序可能是“栈顶”。对于 ThreadStart 处理程序或来自异步方法的回调也是如此。
您还希望在层边界上捕获异常,但在这种情况下,您可能只想将异常包装在特定于层的异常中。
对于 ASP.NET,您可以决定允许 ASP.NET Health Monitoring 为您记录异常。
但是您当然不需要在每个方法中都捕获异常。这是一个主要的反模式。我会大声反对您使用这种异常处理检查代码。
您可以在堆栈跟踪中看到所有内容 - 无需尝试/捕获每个方法。
坚持几条规则:
好的,在阅读了所有答案后说您应该在顶层进行一次尝试/捕获,我将权衡另一种观点。
我不会在每种方法中都尝试/捕获,远非如此。但是我会在我预计会失败的代码部分(例如打开文件)周围使用 try/catch,并且我想在异常中添加额外的信息(在链的更高位置记录)。
堆栈跟踪说和“权限被拒绝”的消息可能足以让您作为程序员找出问题所在,但我的目标是为用户提供有意义的信息,例如“无法打开文件'C: \lockedfile.txt'。权限被拒绝。”。
如:
private void DoSomethingWithFile(string filename)
{
// Note: try/catch doesn't need to surround the whole method...
if (File.Exists(filename))
{
try
{
// do something involving the file
}
catch (Exception ex)
{
throw new ApplicationException(string.Format("Cannot do something with file '{0}'.", filename), ex);
}
}
}
我还想提一下,即使是那些说“只有一次 try/catch”的人可能仍然会在他们的代码中使用 try/finally,因为这是保证正确清理等的唯一方法。
我绝对不会在每种方法周围都使用 try catch 包装器(奇怪的是,我刚开始时就这样做了,但那是在我学会更好的方法之前)。
1)为了防止程序崩溃和用户丢失他们的信息,我这样做
runProgram:
try
{
container.ShowDialog();
}
catch (Exception ex)
{
ExceptionManager.Publish(ex);
if (MessageBox.Show("A fatal error has occurred. Please save work and restart program. Would you like to try to continue?", "Fatal Error", MessageBoxButtons.YesNo) == DialogResult.Yes)
goto runProgram;
container.Close();
}
容器是我的应用程序开始的地方,所以这基本上在我的整个应用程序周围放置了一个包装器,这样就不会导致无法恢复的崩溃。这是我不介意使用 goto 的极少数情况之一(它是少量代码并且仍然非常易读)
2) 我只在我认为可能出错的方法中捕获异常(例如超时)。
3) 为了便于阅读,如果您有一个 try catch 块,其中在 try 部分中有一堆代码,在 catch 部分中有一堆代码,最好将该代码提取到一个命名良好的方法中。
public void delete(Page page)
{
try
{
deletePageAndAllReferences(page)
}
catch (Exception e)
{
logError(e);
}
}
要在发生时执行此操作,您仍然需要 try/catch。但是您不一定需要在任何地方捕获异常。它们向上传播调用堆栈,当它们被捕获时,您会得到堆栈跟踪。因此,如果出现问题,您可以随时根据需要添加更多尝试/捕获。
考虑查看许多可用的日志框架之一。
我认为您不需要在违规时抓住所有内容。您可以冒泡您的异常,然后使用 StackTrace 找出违规点实际发生的位置。
另外,如果你需要一个 try catch 块,我听说的最好的方法是将它隔离在一个方法中,以免用巨大的 try catch 块使代码混乱。另外,在 try 语句中尽量少做语句。
当然,重申一下,将异常冒泡到顶部并记录堆栈跟踪是比在整个代码中嵌套 try-catch-log-throw 块更好的方法。
我会考虑使用ELMAH进行异常处理,这几乎是“让异常发生”的概念。ELMAH 将负责记录它们,您甚至可以将其设置为在某个项目的异常达到或超过特定阈值时向您发送电子邮件。在我的部门,我们尽可能远离 try/catch 块。如果应用程序出现问题,我们想立即知道问题是什么,以便我们可以修复它,而不是抑制异常并在代码中处理它。
如果发生异常,则意味着某些事情不正确。这个想法是让你的应用程序只做它应该做的事情。如果它正在做不同的事情并导致异常,您的响应应该是解决它发生的原因,而不是让它发生并在代码中处理它。这只是我/我们的理念,并不适合所有人。但是我们都被一个应用程序出于某种原因“吃掉”异常而被烧毁了太多次,没有人知道出了什么问题。
并且永远永远不会捕获一般异常。始终,始终,始终捕获最具体的异常,以便如果抛出异常,但它不是您期望的类型,您会再次知道,因为应用程序将崩溃。如果您只是捕获(异常 e),那么无论抛出哪种类型的异常,您的 catch 块现在都将负责响应可能抛出的每种类型的异常。如果没有,那么你就会遇到整个“吃”的例外情况,其中出现了问题,但你永远不知道,直到可能为时已晚。
如何避免在每种方法中尝试/捕获,但仍然在错误发生时记录错误?
这取决于托管环境。Asp.Net、WinForms 和 WPF 都有不同的方法来捕获未处理的异常。但是,一旦全局处理程序传递了一个异常实例,您就可以确定异常的抛出点,因为每个异常都包含一个堆栈跟踪。
实际上,避免细粒度的尝试/捕获。允许异常在堆栈中向上遍历并被捕获到尽可能高的级别。如果您有特定的关注点,那么如果您担心异常级联,则将日志记录立即捕获 - 尽管您仍然可以通过深入研究内部异常来解决这些问题。
异常处理不应该是事后的想法。确保你始终如一地这样做。我见过很多人从每个方法的开头到结尾都放了一个广泛的 try/catch 并捕获一般异常。人们认为这有助于他们获得更多信息,而事实上并非如此。在某些情况下,多即是少,因为少即是多。我永远不会厌倦“应该使用异常来记录异常行为”的公理。如果可以,请恢复,并尝试减少总体异常的数量。当您尝试解决问题并在出现问题时看到数百个相同的 NullReferenceException 或类似异常时,没有什么比这更令人沮丧的了。
异常的实现使得它们没有成本,除非抛出。
这对我来说意味着性能影响并不是一个强烈反对的论据。异常情况通常是……异常。