【问题标题】:Catching specific vs. generic exceptions in c#在 c# 中捕获特定异常与通用异常
【发布时间】:2009-09-11 14:38:44
【问题描述】:

这个问题来自针对我创建的对象运行的代码分析。分析说我应该捕获一个比基本异常更具体的异常类型。

您是否发现自己只使用捕获通用异常或尝试捕获特定异常并使用多个捕获块默认为通用异常?

有问题的代码块之一如下:

internal static bool ClearFlags(string connectionString, Guid ID)
{
    bool returnValue = false;
    SqlConnection dbEngine = new SqlConnection(connectionString);
    SqlCommand dbCmd = new SqlCommand("ClearFlags", dbEngine);
    SqlDataAdapter dataAdapter = new SqlDataAdapter(dbCmd);

    dbCmd.CommandType = CommandType.StoredProcedure;
    try
    {
        dbCmd.Parameters.AddWithValue("@ID", ID.ToString());

        dbEngine.Open();
        dbCmd.ExecuteNonQuery();
        dbEngine.Close();

        returnValue = true;
    }
    catch (Exception ex)
    { ErrorHandler(ex); }

    return returnValue;
}

感谢您的建议

编辑:这是来自代码分析的警告

警告 351 CA1031:Microsoft.Design:修改“ClearFlags(string, Guid)”以捕获比“Exception”更具体的异常或重新引发异常

【问题讨论】:

    标签: c# exception-handling coding-style


    【解决方案1】:

    您几乎不应该捕获顶级异常。

    在大多数情况下,您应该捕获并处理可能的最具体的异常,并且只有当您可以用它做一些有用的事情时。

    对此的异常(哈哈)是,如果您正在捕获记录并重新抛出异常,那么有时可以捕获顶级异常,记录并重新抛出它。

    您几乎不应该捕获顶级异常并吞下它。这是因为如果你捕捉到一个顶级异常,你并不真正知道你在处理什么;绝对任何事情都可能导致它,所以你几乎肯定无法做任何事情来正确处理每一个失败案例。可能有一些失败,您可能只想默默地处理和吞下,但是通过吞下顶级异常,您还将吞下一大堆真正应该向上抛出以便您的代码处理更高层的异常。在您的代码示例中,您可能想要做的是处理 SQLException 并记录+吞下它;然后对于异常,记录并重新抛出它。这涵盖了你自己。您仍在记录所有异常类型,但您只是吞下了相当可预测的 SQLException,这表明您的 SQL/数据库存在问题。

    一个常见的做法是只处理你在那个时候可以实际解决的异常,如果你不能在那个时候在代码中解决它,那么你允许它向上冒泡。如果你不能在下一个级别解决它,让它继续向上。如果它到达顶部未处理,则向用户显示礼貌的道歉(也许尝试快速自动保存)并关闭应用程序。通常认为允许应用程序在未处理的异常后继续运行会更糟糕,因为您无法预测应用程序的状态,因为发生了一些异常情况。最好关闭并重新启动应用程序以恢复到预期状态。

    【讨论】:

    • @Simon:谢谢!我很感激你的回应!你能否再给我一点关于为什么你不应该捕捉和吞下顶级异常的信息。我现在拥有代码的方式是,“ErrorHandler”方法从异常中收集尽可能多的信息并将其存储在事件日志中(堆栈跟踪、内部异常、消息等)。从您的角度来看,我不确定捕获/处理异常的后果。
    • 捕获异常、记录异常然后重新抛出异常有什么意义?
    • @Kyralessa:降低 DAL 中的代码堆栈,您想要记录的事情可能会出错。因此,您捕获并记录顶级异常,但您不应该吞下它,因为您没有t 真的知道出了什么问题(而且你可能会吞下像 OutOfMemEx 这样严重的东西)。所以你应该重新抛出。如果你想处理它,你应该捕获你知道如何处理的特定异常类型。如果您只能捕获“异常”,那么您应该重新抛出(或包装并重新抛出)以允许更高的调用者适当地处理它。 @Scott:我已经扩展了我的答案。
    • 当然不能吞下去。我的意思是,如果你只是要重新抛出它,为什么要记录它?您应该能够使用异常层次结构来捕获您期望进入 DAL 的任何异常;但记录其他人似乎毫无意义,因为他们会被追得更远(毕竟,你正在重新抛出它们),如果你正确地重新抛出它们,它们将有一个完整的调用堆栈来指出事情出错的地方。跨度>
    • (3) 我还认为,如果您尝试调试错误处理或需要某种形式的详细跟踪检测,那么在每个级别进行日志记录都会很有用。我想这取决于具体的要求。是的,在基本客户端应用程序中,您可能不会在任何级别记录和重新抛出,但在分层服务器应用程序中,层之间的区别可能很重要,尤其是在跟踪错误的执行路径时。 (哇,对于大量帖子感到抱歉)。
    【解决方案2】:

    看看 Krzysztof Cwalina 的这篇文章,我发现这篇文章对理解何时捕获或忽略异常非常有帮助:

    How to Design Exception Hierarchies

    它描述的关于设计异常层次结构的所有原则也适用于决定何时捕获、抛出或忽略异常。他将异常分为三组:

    • 使用错误,如DivideByZeroException,表示代码错误;您不应该处理这些,因为可以通过更改代码来避免它们。
    • 逻辑错误,例如FileNotFoundException,您需要处理这些错误,因为您不能保证它们不会发生。 (即使您检查文件是否存在,它仍然可能在您读取文件之前的那一瞬间被删除。)
    • 系统故障,例如OutOfMemoryException,您无法避免或处理。

    【讨论】:

      【解决方案3】:

      是的,

      你应该从最具体的异常中捕捉到最少的异常,这样你才能以适当的方式处理事情。

      例如,如果您正在发出网络请求,则应首先捕获 TimeOuts 和 404 等信息,然后您可以通知最终用户他们应该重试(超时)和/或检查他们输入的 URL。

      然后你可以捕捉不那么普遍的东西,以防更古怪的事情出错,然后在发生荒谬的事情时立即回退到只捕捉异常。

      【讨论】:

        【解决方案4】:

        作为最佳实践,您应该避免捕获异常并使用标志作为返回值。

        相反,您应该为预期的异常设计自定义异常并直接捕获这些异常。其他任何事情都应该作为意外异常冒泡。

        在上面的示例中,您可能希望重新抛出一个更具体的业务异常。

        【讨论】:

          【解决方案5】:

          我同意,一般而言,您应该只捕获预期的异常并了解如何处理。一些我经常不这样做的情况:

          1. 如上所述,如果我正在捕获某种有用的信息以记录然后重新抛出。

          2. 如果我正在执行异步操作,例如在工作线程中处理排队的消息或作业,并且我想捕获异常以便在不同的上下文中重新抛出。我还经常在这里使用一个丑陋的 hack 来欺骗 CLR 附加堆栈跟踪信息,以便在新上下文中重新抛出时不会丢失。

          3. 如果我正在处理一个孤立的任务或操作,我可以通过关闭任务来处理异常,而无需关闭整个应用程序。我经常希望这里有一个真正致命的异常(如 OutOfMemoryException)的顶级异常,因为我一直忽略这些。处理此问题的正确方法是在其自己的 AppDomain 中运行隔离任务,但我还没有可用的计划时间在项目中实施此操作。

          【讨论】:

            【解决方案6】:

            您应该阅读一般论文或谷歌“结构化异常处理”并更好地了解该主题的全部内容,但一般来说,捕获每个异常被认为是不好的做法,因为您不知道异常是什么(内存故障、内存不足错误、磁盘故障等)。

            对于许多未知/意外的异常,您不应该允许应用程序继续运行。通常,您“捕获”并仅处理玩具已确定的那些异常,这是对您正在为其编写 catch 子句的方法进行分析的结果,该方法实际上可以创建,并且您可以做一些事情。您应该捕获所有异常(捕获异常 x)的唯一时间是执行诸如记录它之类的操作,在这种情况下,您应该立即重新抛出相同的异常(无论它是什么),以便它可以将堆栈冒泡到一些一般的“未处理异常” Handler”,它可以向用户显示适当的消息,然后导致应用程序终止。

            【讨论】:

              【解决方案7】:

              我同意代码分析工具。我对规则的例外是我在我的事件处理程序中捕获了一般异常,并且用户可以选择报告错误或忽略它。

              在您提供的示例中,我认为代码分析是正确的。如果您不能在那里处理特定的异常,那么您根本不应该捕获任何东西并让它冒泡到最高级别。这样,当您尝试解决问题时,您将更容易重新创建问题。

              您可以通过将连接字符串和 ID 值添加到异常的 Data 属性来改进您的示例,并确保它也被记录。这样你就可以给自己一个重现错误的机会。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-11-11
                • 2011-07-02
                • 1970-01-01
                • 2018-06-28
                相关资源
                最近更新 更多