4

我正在用 C# 编写一个通用的离散事件系统模拟库。我将在此基础上编写另一个库,以实现特定类型的离散事件模拟。这是代码的完整版本。

static class Engine
{
    [ThreadStatic] internal static uint time;
    //...

    public static void Run(OnException onException = OnException.PauseAndRethrow,
                           IList<Type> exceptionsToPass = null)
    {
        //...
        while (!stop)
        {
            Event e = currentTimeEventQueue.PopEvent();
            if (e == null) break;
            try {
                e.Activate();
            }
            catch (EngineException ex)
            {
                // An engine method that shouldn't have been called
                // was called, and Activate didn't handle that

                // handle this situation...
            }
            catch (/* fatal exception type*/ ex)
            {
                throw;
            }
            catch (Exception ex)
            {
                // code to decides whether to dismiss exception
                // pause, stop, reset based on parameters to this method
            }
        }
    }
}

问题是:我是否应该专门捕获已知不可恢复的异常类型(我不应该尝试以任何方式处理)。那些例外是什么(我能想到OutOfMemoryExceptionStackOverflowException)。是否有致命例外列表?我记得其中一些是无法捕获的。所以我对可以捕获的致命异常列表感兴趣。我只想重新扔掉它们,而不做任何事情。另一方面,我想处理任何其他类型的异常。或者也许我需要另一个角度。


编辑:好的,我在写这个问题时做了很大的疏忽。Activate()是抽象的。我正在编写一个通用的离散事件系统模拟库。该引擎正在使用完全未知的Event. 所以它调用了一个绝对未知的方法Activate(),它可能会抛出任何类型的异常。我可以忽略这个问题,但我想将过程控制权交给调用者。从方法的参数可以看出Run(),调用者决定引擎在调用到异常时会做什么Activate()(它可以指示引擎忽略并继续,或暂停并重新抛出,或...)。这就是为什么我试图将致命异常与所有其他异常区分开来。如果调用者已指示引擎忽略来自Activate()捕获和忽略致命异常是不明智的。(有龙:))

4

3 回答 3

3

我是否应该专门捕获已知不可恢复的异常类型

不,你不应该。如果它们不可恢复,则不应尝试从它们中恢复。

关于异常的规则是 - 捕获并处理您知道如何从中恢复的异常。让任何其他冒泡 - 如果这意味着应用程序崩溃,这可能是最好的。

以下是代码异味,不应编码:

catch (/* fatal exception type*/ ex)
{
    throw;
}
于 2013-01-07T18:23:46.880 回答
2

没有 throwable 是真的“无法捕获”;任何可以在 .NET 中抛出的东西都派生自 Exception,因此可以被捕获。但是,很多东西不应该被抓住。因此你的问题;如何区分?

我遵循的一般规则是“捕获您期望并知道如何处理的异常”。这需要你首先知道你的代码抛出什么。MSDN 文档通常非常擅长说明各种框架方法在什么情况下会抛出什么;除非您正在开发一个供其他编码人员使用的中间库(或者您的管理/领导对适当的文档非常感兴趣),否则您自己的代码库可能没有那么完善的文档记录。

一旦你知道代码可以抛出什么,确定你应该捕获什么(如果有的话)。异常捕获,又名“Pokemon 处理”(Gotta catch 'em all)通常是一件坏事,因为有正当的理由让您的应用程序死气沉沉并让用户重新启动它。示例包括 StackOverflowExceptions、OutOfMemoryExceptions 和各种 Win32Exceptions,详细说明了为您的程序提供请求的资源的一些内部故障。你通常无法以任何有意义的方式从这些中恢复过来。

然而,大多数错误并不那么严重。当找不到文件,或者网络连接被拒绝或意外关闭,或者当你尝试对一个类型做某事而不检查 null 时抛出异常(当检查 null 失败时会发生什么?通常你可以'不要继续,必须抛出你自己的异常)。这些是您应该期待、捕捉并至少以某种可以理解的方式与最终用户交流的东西。

在许多情况下,try/throw/catch 对于预期但不是日常情况是有用的工具;如果您在等待结果时收到超时异常,而通常获取结果没有问题,那么甚至不要告诉用户有问题;再试一次。如果您可以(应该?)在无法评估计算时插入一些默认值(除以零,可能产生非真实结果但代码无法处理它们的数学等),然后这样做。

但是,我可以想到一种情况,您必须捕获并重新抛出,那就是在涉及数据库事务的情况下。如果您正在执行一个大型“工作单元”,其中一个数据库事务处理多个持久性操作,那么如果出现任何问题,您应该回滚数据库事务。在这种情况下,操作应该被包围在一个 try-catch(Exception) 块中,该块捕获异常、回滚事务并重新抛出。您可以正常处理的任何异常都应使用嵌套的 try-catch 进行处理,或者应在可能以这种方式失败的操作之前检查条件。

于 2013-01-07T18:37:34.213 回答
1

您的问题是一个合理的问题,但遗憾的是,通常没有真正好的答案。C++ 中异常范式的一个主要弱点是它在异常对象的类型中封装了太多,不幸的是,它一直延续到其他语言和框架中。在 C++ 中,这是可以理解的,因为 Bjarne Stroustrup 希望避免将任何非原始类型“硬编码”到语言中;鉴于这种限制,C++ 设计可能是最好的。尽管如此,这样的设计施加了一些严重的限制。

最大的问题是正在考虑捕获和处理异常,然后吞下或重新抛出异常的代码可能对许多事情感兴趣:

  1. 发生了什么阻止代码按预期运行
  2. 针对这种情况应该采取什么行动
  3. 采取各种措施后,是否应将条件视为“已解决”
  4. 如果人们对系统状态的任何方面的假设进行调整,超出方法失败所暗示的范围(例如,如果文件“foo/bar”无法读取,则失败的性质可能会影响是否尝试读取“foo”的决定/boz”)。

在 Java 和 .net 中,期望应该使用异常类型来指示上面的 #1,并且没有标准的异常方法来回答 #2 和 #3,即使它们在许多情况下远比“什么”重要得多发生了”。如果尝试加载文档但出现问题,99% 的时间系统将处于与加载尝试之前相同的状态,因此该异常,无论其类型如何,基本上意味着“文件不是可读且无法加载”。此外,在剩下的 1% 的时间里,除了加载文件失败之外发生了“坏”的事情,异常类型完全有可能与 99% 中抛出的异常类型相同。

如果没有一些新的异常处理功能,最好的办法可能是,在可能的范围内,具有指示状态损坏的异常使系统状态的损坏部分无效,以便将来对这些部分的所有操作同样会引发异常。这种行为意味着代码无法处理的异常最终会导致系统崩溃(因为代码将继续尝试使用已损坏的部分状态),同时允许代码从它应该恢复的事物中恢复(例如,如果由于正在构建的对象中的损坏而发生异常,并且如果异常导致代码放弃正在构建的对象,则这种放弃足以“处理”异常,因此可以并且应该继续执行)。

于 2013-01-22T19:39:46.207 回答