什么“只捕捉你可以处理的例外”真的意味着什么?

我的任务是编写一个我正在研究的.NET / C#项目的exception处理策略和指南文档。 我很难接受它。 有很多关于如何/何时抛出,捕获,包装exception的信息,但我正在寻找描述在catch块内部应该进行哪些事情而不是包装和抛出exception。

try { DoSomethingNotNice(); } catch (ExceptionICanHandle ex) { //Looking for examples of what people are doing in catch blocks //other than throw or wrapping the exception, and throwing. } 

提前致谢

这意味着。 如果您期望正在运行的代码抛出exception,并且在抛出该exception时您的代码知道出了什么问题以及如何继续,那么捕获exception并处理它。

基本上,规则存在以防止反模式,如:

 try { ... } catch(Exception ex) { throw; } 

这里的捕获只会添加一个减速带来解除调用堆栈。 如果你真的不希望任何事情,你抓住的例外,你甚至不应该打扰捕获。

一个相关但更有效的案例是你不关心被抛出的exception,但你需要在所有情况下进行清理。 在这种情况下,跳过捕获; 你不需要它,只需将它作为try-finally块。

编辑:要回答post中的问题,而不仅仅是主题,您可以编写如下规则:“不要编写不执行任何操作的try-catch语句,或者只重新捕获捕获的exception。所有catch语句都应该执行一些与抛出exception有关的增值行动。“

例如,假设您尝试使用用户在登录应用程序时提供的凭据连接到SQL Server实例。 许多事情都可能出错,其中一些你不能指望,其中一些你应该。

  • 服务器没有响应 – 你可以再试一次; 也许在catch中递归调用连接方法,使用“重试计数器”来打破无限循环。
  • 用户validation失败 – 在对话框中以红色显示友好(或不那么友好,但简洁易懂)的消息。
  • 用户无权连接到指定的数据库 – 取决于您的安全设置; 在大多数办公室,你应该通过电子邮件向DBA发送电子邮件,因为这意味着他创建了登录但忘记分配适当的权限。
  • 网络不可用:您可以通过登录对话框或新对话框中的错误提醒用户,重试几次等。
  • 除以零 – WTF? 在登录期间可能导致Div by Zero的原因是什么? 你不期待这个例外,你不知道在这种情况下出了什么问题因此无法继续运行代码,所以不要抓住它。
  • 如果出现任何问题,您可能希望将消息记录到文件或共享资源以用于审计/安全目的。 如果你想继续执行,这应该发生在较低级别,如果你以后要优雅地关闭,这应该发生在更高级别。

所有这些示例都涉及首先捕获已知类型的exception并询问它以查看确切的错误,然后执行一些可以允许程序继续执行的已知操作。 目的是防止应用程序在出现错误时崩溃和刻录,以防止您知道可能出错,但知道在这种情况下如何保持程序运行。

捕获exception的基本规则:

  1. 如果您没有预料到exception,请不要抓一个。
  2. 如果您不能或不想在收到exception后继续执行代码,无论您是否知道它可能发生,请不要抓住它。
  3. 如果您希望发生exception,并知道如何在代码发生时继续执行代码(至少暂时),那么请捕获并执行您需要的任何特殊操作。
  4. 永远不会捕获exception(空捕获块); 这导致应用程序以更加不可预测的方式无声地失败。
  5. 永远不要在生产代码中留下catch-and-rethrow(只有一个重新抛出的catch块)。 它们有时在调试时很有用,因为它们允许您识别失败的特定代码段,但在生产代码中,它们只是抛出或实际处理exception的速度。

我认为这个常见建议的基本思想是避免这样的情况:

 try { SomeImportantResource = GetSomeImportantResource(); SomeOtherImportantResource = GetSomeOtherImportantResource(); } catch (Exception ex) { SomeGlobalErrorHandlingMechanism(ex); } 

我和开发人员一起工作过,当遇到bug时,他们会简单地将有问题的代码包装在try / catch块中,然后说:“我修复了这个bug。” 上述示例中的问题是,通过简单地捕获exception并且不解决导致exception的问题 ,您可能会破坏程序的可靠性。 上面, catch内容使我们不确定SomeImportantResourceSomeOtherImportantResource是否已正确初始化。 似乎程序中的其他地方可能存在需要初始化这些代码的代码,在这种情况下,我们刚刚通过“修复”错误引入了一个错误。

因此,我认为标准的智慧是只尝试处理exception,如果你能以一种不损害程序中其他任何代码的方式从中恢复的话。

或者,更好的是:不要抓住exception并做出一些微弱的尝试(或非尝试)来“处理”它; 找出导致它的原因并解决问题。 显然,这并不总是可行的,但它可能比它应该更频繁。

考虑一下像OneNote这样的应用程序是否允许您将文件存储在共享网络驱动器上,但是如果网络不可用,则它会暂时使用本地存储,直到主存储可用。

如果您的程序在与文件交互时遇到exception,那么您可以使用本地存储重试该操作。

这是一个示例,您可以使用所需的特定程序行为,并通过处理exception的方式来完成它。 通常,您应该尝试找到一种方法来实现您的目标而不使用exception处理,例如在上面的例子中,您可以在尝试操作之前始终检查文件是否可用。 这样你就可以把它编码为“if / else”而不是“try / catch”。 但是,如果您这样做,在上述情况下仍有可能有人在操作过程中失去对文件的访问权限,这样无论您是否提前检查过,您仍然可能会遇到exception可以优雅地处理。 所以你可能会将你的else块重构为一个从else和catch调用的函数,这样你就可以优雅地回退到本地存储。

我还经常包括日志记录,如果我正在记录的内容没有安全问题,并且正如您所提到的那样重新抛出,我的日志记录包含更多描述性信息和上下文信息,可能是一些本地值,这使得调试更容易。 我一直努力让日志文件如此详细,以至于无需在我的机器上重现就可以确定问题的原因。 我讨厌听到程序员做出“我无法重现它”的借口。 您不必重现它。 如果您的日志记录足够,则无需重现它。

当exception通过重新抛出一直到你的GUI层时,然后在那一点你抓住它并且不重新抛出它,而是向用户显示一条消息,表明发生了意外错误,并且通常退出应用程序。 您可能会给他们一个保存工作的机会,但可能会自动备份被覆盖的文件,因为未处理的exception是您从未编写过的,这意味着某些内容可能已损坏,您可能正在保存错误的文件,但却领先用户相信他们正在挽救他们的工作。 这最终是许多程序在发生意外情况时选择自杀的原因,从那时起谁知道程序可能处于什么状态,以及在数据库中保存某些行这样简单的事情可能会产生严重的后果和软件很多数据。

如果您在捕获以某种方式有用的exception时可以执行操作(例如执行将执行try语句中尝试的函数的代码块,但是以不同但可能效率较低的方式执行,或者只是通知用户他们的行为无法执行),那么你应该抓住它并这样做。 如果您只是记录exception以便稍后跟踪问题,那么您应该重新抛出exceptionthrow; (不要throw ex; ),以防有另一个代码块可以处理这种类型的exception。

捕获exception以将捕获的exception包装在您自己的exception中也可以接受,这可能对调用函数更有意义。

一些例子:

  1. 记录exception并继续
  2. 重试出错的地方
  3. 尝试另一种方法来做你想做的事情

这一切都取决于出了什么问题。 关键是,只是捕捉和重新投掷对任何人都没用。

如果您的代码可以优雅地处理特定类型的exception,请捕获并处理它,然后让代码继续运行。 如果没有,请让exception传播,因为它可能会被捕获到更高级别,或者它可能是一个非常错误的东西,你不应该捕获它,因为它可能会掩盖错误。

您不应该捕获无法处理的exception,但是您可以捕获您可能能够处理的exception:

 try { DoSomethingNotNice(); } catch (ExceptionIMightBeAbleToHandle ex) { if(iCanHandle(ex)) thenHandle(ex); else throw; } 

请注意,使用throw本身应该保留堆栈跟踪信息。

您可以优雅地处理的典型事情是FileNotFoundException

catch块应该拆除可能已经打开以供在try中使用的任何东西,并且由于exception被抛出没有正确关闭。 数据库连接和文件访问通常需要关闭(尽管正确使用using块可以处理这个)

一旦完成,你可以使用throw; 把exception扔到一个新的水平

或者,您可能希望将当前exception包装在与当前方法更相关的新exception中

 catch(LowLevelException ex){ throw new HighLevelException("argh bad things happened!",ex); }