结构化异常处理不好吗?处理异常的正确方法是什么?
编辑:使用 C# 在 .NET 中进行异常处理。
我通常有一组特定的异常类(DivideByZeroException、ArrayTypeMismatchException)并且没有通用的“catch (Exception ex)”。
这背后的想法是,我预计会发生某些类型的异常,并在它们发生时定义特定的操作,并且意外的异常会在界面(Windows 或 Web)上出现。这是一个好习惯吗?
结构化异常处理不好吗?处理异常的正确方法是什么?
编辑:使用 C# 在 .NET 中进行异常处理。
我通常有一组特定的异常类(DivideByZeroException、ArrayTypeMismatchException)并且没有通用的“catch (Exception ex)”。
这背后的想法是,我预计会发生某些类型的异常,并在它们发生时定义特定的操作,并且意外的异常会在界面(Windows 或 Web)上出现。这是一个好习惯吗?
我不确定“结构化异常处理”是什么意思。
在异常处理中可以做的最糟糕的事情是“吞下”异常或静默处理。
不要这样做:
try {
...
}
catch (Exception e) {
//TODO: handle this later
}
这通常是因为懒得编译代码。如果您不知道如何在特定级别处理异常,请让方法抛出异常并至少在顶部有一个 catch all 处理程序。以某种方式提供反馈(通过 GUI、给支持人员的页面/电子邮件、日志文件),以便最终解决问题。默默地捕捉异常几乎总是会导致稍后发生更大的问题并且难以追踪。
Catch 语句 + 堆栈跟踪。永远不要在不打印堆栈跟踪的情况下捕获异常,当发生错误并且您的日志文件为空或模糊时,您或其他人将不得不再次签出该代码并将堆栈跟踪放置在 Catch 块中。
这是一个复杂的话题……有这方面的书籍……但是……有两种主要的异常处理类型……内联,其中处理潜在错误的代码与方法或例程的代码内联将“通常”执行和结构化异常处理,其中代码位于其他地方,并且基础架构旨在在发生意外事件(错误)时自动切换到该异常处理代码......两者都有优点和缺点。“内联”方法倾向于生成更混乱(带有错误代码)并且更难阅读和维护的代码。但它更容易预先生成,因为它不需要任何前期分析,当使用内联错误处理时,您经常会看到返回布尔或数字“错误”代码的方法,向调用者指示方法或例程是否成功。这消除了例程“返回”有意义的业务值或对象的“功能”语法(因为按照惯例,每个函数都必须返回错误代码)当使用结构化异常处理时,这个问题没有实际意义。
otoh,结构化异常处理通常更难做好,因为它需要预先分析例程或方法可能产生的错误,以及如果确实发生了每个错误,该方法可以或应该做什么。
可以肯定的一件事,不要将这两种方法混合在一个组件中......
我的建议:
不要捕获异常,除非:
在尽可能高的级别捕获异常意味着您可以获得最大的调用堆栈,这在您浏览日志并尝试查看最初是什么初始操作触发了导致异常的事件序列时非常有用.
我不是 Windows 程序员,但在我看来,使用结构化异常处理来处理软件异常等硬件异常意味着:
因此,IMO 要问的问题是:
如果答案是“是”、“是”、“否”,则需要结构化异常处理。否则,您可能能够避免它,在这种情况下,您可能想要。编写异常安全的代码很棘手,因此您可以提供的异常保证越强越好。可能用 SEH 除以零的代码不提供 nothrow 保证,当可能进行一些重新设计以使调用者不给它 duff 数据时,它可以这样做。但是如果一个函数已经因为其他原因不得不抛出异常,那么也可能为了硬件陷阱而抛出它们可能不会让事情变得更糟。
一种值得注意的特殊情况是内存分配。不确定 .NET 是否这样做,但在 linux 上分配只有在没有足够的虚拟地址空间进行分配时才会失败。物理内存在首次使用时提交,如果不够,则会导致硬件异常。由于内存分配应该在失败时抛出 std::bad_alloc,而实现未能实现标准的这一要求,它可能在某些情况下,将硬件异常转换为软件是正确的做法。然而,硬件异常可能发生在意想不到的地方,(包括你认为没有抛出的例程中),所以可能仍然无法优雅地处理,这就是为什么 linux 核心转储而不是抛出的原因。在实践中,任何完全初始化的东西都会使其构造函数崩溃,这通常足够接近软件异常有用的分配。