18

最近我参加了 Jeffrey Richter 的关于 .NET 的培训课程。他提到了一种编码策略“死是真棒”。也就是说,即使在程序或事件循环的根部也不要写“catch (Exception ex)”。如果抛出了一些未处理的异常,就让进程死掉。

我不确定这是否正确。就个人而言,我更喜欢try {...} catch(Exception ex) {log and try to recover}在执行的顶层有一个“”来包装。实际上,如果从 asXx 抛出任何异常,ASP.NET 不会死。如果它确实因异常而死,那么一个灵丹妙药的请求可以使整个服务保持沉默。

你怎么看?

4

16 回答 16

38

我认为这取决于您正在运行什么样的应用程序以及“死亡”的后果是什么。对于许多客户端应用程序来说,死亡真是太棒了。对于服务器,通常不是那么多(吞下日志是合适的)。没有一种万能的解决方案。

于 2009-02-23T04:40:18.533 回答
22

也称为进攻性编程。

您应该查看“进攻性编程:早期崩溃,经常崩溃”。

这是与防御性编程规范截然不同的意识形态:

[防御性编程]旨在确保该软件的持续功能,尽管该软件的使用不可预见。

我个人喜欢“早期崩溃,经常崩溃”的理念。

我见过太多了:

try  
{  
    // ... Huge block of code goes here  
}  
catch(Exception ex)  
{  
   // Do nothing...  
}  

这比用力撞车要糟糕得多。如果异常处理得当,那么一点防御性编程就可以了。

于 2009-02-23T04:44:22.920 回答
8

这一切都取决于您如何处理异常。如果您只在发生真正异常的事情时抛出异常(而不是在数据库查询没有返回结果或格式错误时),那么您真的不需要捕获异常。因为您的程序不应该能够恢复,所以您可以恢复的任何东西都不是异常,而是错误。

于 2009-02-23T04:38:11.050 回答
8

听起来像古鲁斯语

这听起来像是大师们宣扬的另一条一般指导方针,它本身并不是一个坏建议。但是,该准则可以很容易地应用于不属于它的地方。我认为要记住您上面使用的关键短语是“一种编码策略”,因为这种策略在某些领域非常有用,但在其他领域可能非常有害。

死亡很棒——如果你很多紧密耦合的组件,它们的状态相互依赖,那么异常很容易成为灾难性事件。但是,您的目标之一应该是以这样一种方式进行编码,即单个故障不必关闭整个系统(注意目标)。

您如何看待以下应用程序因普通异常而死亡:

  • 医疗设备
  • 发电厂
  • 入侵检测系统

对于您在 try / catch 中捕获的异常 - 您确实应该期待它们并处理它们。对于所有其他情况,最好快速失败到预测的运行级别。那么,如果您在网络或 Web 处理程序中,为什么不直接终止当前操作呢?你真的需要整个应用程序崩溃吗?

如果您正在开发一个任务关键且具有公共界面的应用程序,这一点变得越来越重要。如果有可能导致应用程序关闭的异常情况,那么这将成为黑帽黑客意图造成拒绝服务攻击的攻击媒介。

滥用这种策略的极端案例有点太大了,无法给予太多赞扬。更好的方法是解决您的域问题。了解此策略的目的,并将适当的部分应用于您的问题。

警告:我在正常运行时间和安全性至关重要的服务器端系统上工作。

编辑:我想我对“进程死亡”的含义感到困惑——这是对整个应用程序的引用还是对正在运行的线程等的引用?

于 2009-02-23T06:43:46.877 回答
8

Karl Seguin对异常处理说了以下几点:

  1. 仅处理您实际上可以做某事的异常
  2. 对于绝大多数异常你无能为力

很好的主题介绍。

于 2009-02-25T12:10:54.127 回答
6

这是非常微软的。

MS 希望您将所有未处理的异常都扔给 WER。(Windows 错误报告,当应用程序崩溃时,您可以将错误发送给微软的对话框)

通过这种方式,您可以获得使用指标,并可以专注于导致客户悲伤的关键未处理异常。我相信这个想法是它迫使您考虑异常的来源。

但我完全同意你的看法。即使你重新抛出,你总是会在根目录下捕获一个未处理的,记录发生的事情。唯一的问题是,如果您未处理的 ex 内存不足,在这种情况下,您对 log 的调用可能会失败,因为 JIT 无法分配更多内存等。我认为 C++ 程序通过占用内存来处理这个问题,在未处理的异常上释放它,然后运行一个非常紧凑的日志例程来尝试优雅地失败。

于 2009-02-23T04:39:22.903 回答
3

我不熟悉 Richter 的材料,但我认为哲学/理由是,在绝大多数情况下,程序实际上无法从未处理和意外的异常中恢复。

然而,让程序在用户面前死掉并不是一个好主意——它会留下非常糟糕的印象。我更喜欢将未处理的异常记录到数据库中,通知开发人员并跟踪它们以确保它们得到修复。

再说一次,我正是为了这个目的而销售产品,所以我在这方面有点偏见!

于 2009-02-23T04:39:43.777 回答
3

如果你不能充分处理这个问题——如果它不是拒绝某种形式的错误输入(这包括你试图从磁盘或网络中读取的东西)你可能不能——那么你应该死。除非你 100% 确定你可以安全地继续,否则不要。

但是,我不会简单地放过一个例外。抓住它并尽可能多地收集信息。如果您的程序操作某种文档,请将其保存在一个新名称下——它可能已损坏,您不想覆盖原始文件。

于 2009-02-23T04:59:30.093 回答
2

没有什么比堆栈跟踪更快地破坏用户信心了。至少,捕获异常并记录尽可能多的信息。然后向用户提供友好的消息和说明,说明如何解决问题或将问题报告给支持人员。

这里有关于继续处于不确定状态的担忧。如果这是一个网络应用程序,那么这不是问题,除非您过度依赖会话和应用程序状态。如果这是一个 Windows 应用程序,请随时退出(在让用户有机会保存之后)。

于 2009-02-23T06:18:00.887 回答
2

由于您不知道 Exception 的每个子类是什么,因此您根本无法知道捕获它们是可以的。因此,您应该注意自己的事情:捕获您知道和关心的异常,这主要是您为自己的程序显式创建的异常,或者为您正在使用的库调用中的错误创建的异常。特别是,捕获 Exception 类只是懒惰、糟糕的编程 --- 本质上,您是在说“我不在乎问题是什么,也不要告诉我;就这样做吧。” 看看 java 程序如何处理异常,因为那里的异常处理实践通常都很好。

于 2009-02-23T09:47:44.080 回答
1

您的客户期望什么?

这一切又回到了那个。如果客户可以处理程序死亡并且他们可以重新启动它,那就这样吧。

有时,最好让一个进程终止并创建另一个进程来处理请求。

有时,最好尝试解决问题。

于 2009-02-23T04:40:03.087 回答
1

我说你应该总是有一个根异常捕获器......特别是如果你可以在异常中加入某种信息,说明出了什么问题,代码或其他东西并将其打印给用户。然后,用户可以随时询问您(或支持或其他)出了什么问题并提供一些信息......而不是“它因保护故障而崩溃”

于 2009-02-23T04:43:06.433 回答
1

根本不处理任何异常有一个优点。如果出现问题,您宁愿崩溃并让用户抱怨,而不是让程序在不确定的状态下继续运行。

例如,如果您正在编写一个实时交易系统,并且出现意外错误,您最好让它崩溃。否则,您的程序可能会继续运行并进行愚蠢的交易。用户会马上抱怨“WTF?”,但至少你不会损失数百万美元。

于 2009-02-23T06:05:42.430 回答
1

这里已经有了很棒的答案,尤其是 Simucal。我只想补充一点:

攻击性编程非常适合调试。根据我的经验,“早期失败,经常失败”可以被认为是为代码中的错误设置陷阱。当出现问题时——无论是内存泄漏、内存踩踏、意外 NULL 等——如果程序立即失败(以及相关的调试数据,如调用堆栈),通常更容易发现问题。

防御性编程非常适合生产环境。应用程序发布后,您可能不希望最终用户看到一个漂亮的小对话框,上面写着“foo() 处未处理的异常”。如果您的应用程序处于测试阶段并且您仍在收集调试信息,那可能会很棒,但如果您正在发布一个稳定的应用程序,您可能只是不想在某些事情确实失败的罕见情况下用神秘的输出打扰用户。

有时你可以同时拥有它。我经常按照以下几行编写 C 代码:

void foo(char* data) {
    ASSERT(data);
    if (!data) { return; }
    // ... work with data
}

如果此代码是在测试环境中编译的,则 assert 语句会捕获错误条件。如果它是在生产环境中编译的,则断言会被预处理器删除,并且 foo() 会静默失败。

异常比断言更具表现力和灵活性,并且有多种方法可以管理它们,以便应用程序在测试环境中及早失败,并在生产环境中记录错误时继续前进。根据项目的类型,这种设计可能对您有意义。

于 2009-02-23T09:05:56.367 回答
0

try catch 解决所有问题的问题在于,您通常最终会“处理”您不知道如何从中恢复的事情。用户会误以为事情进展顺利,但在内部你转向瑞士奶酪并最终以非常奇怪的方式打破。

另一方面,当他们也没有某种方式报告它时,将异常抛给用户并没有太大帮助

  • 当您向 MS 注册时,新的 Windows 服务会提供该功能。
  • 安装 drwatson 还可以捕获数据(转储文件)供您的用户手动发送给您。

如果您提供服务库并且它在进程中运行,如果您的服务与整个服务器混淆内存或设置等,那么当引发异常时服务器可能真的需要关闭。

根据合同,API 将倾向于提供一种方式来表示“我可以这样做”,然后提供一种方式来“执行它”,并且许多 API(如 MS 文件打开)将允许您在引发异常和返回错误代码之间进行切换。

于 2009-02-23T05:43:57.613 回答
0

您始终可以设置自定义错误处理程序并忽略捕获您不准备处理的异常。如果满足正确的条件,试图从未知中恢复可能也会产生负面影响。但是话又说回来,这确实取决于异常是什么以及您如何处理它们。

另一方面 - 我认为人们会被整个“例外是邪恶的”心态所淹没。它们是一种工具,如果使用得当,可以带来惊人的好处。但是,许多人通过使用 catch(Exception) 包装 root 来滥用它们。

于 2009-02-23T06:05:12.167 回答