如何使用try catch进行异常处理是最佳实践

在保持我的同事代码的同时,即使是自称是高级开发人员的人,我也经常看到以下代码:

try
{
  //do something
}
catch
{
  //Do nothing
}

或者有时他们会将日志记录信息写入日志文件,如下面的try catch

try
{
  //do some work
}
catch(Exception exception)
{
   WriteException2LogFile(exception);
}

我只是想知道他们所做的是否是最佳做法? 这让我感到困惑,因为我认为用户应该知道系统会发生什么。

请给我一些建议。


我的异常处理策略是:

  • 通过挂接到Application.ThreadException event来捕获所有未处理的异常 ,然后决定:

  • 对于UI应用程序:用道歉消息(winforms)将其弹出给用户
  • 对于服务或控制台应用程序:将其记录到文件(服务或控制台)
  • 然后我总是附上在try/catch 外部运行的每段代码

  • Winforms基础结构触发的所有事件(Load,Click,SelectedChanged ...)
  • 所有由第三方组件发起的事件
  • 然后我附上'try / catch'

  • 我所知道的所有操作可能无法一直工作 (IO操作,可能为零的计算......)。 在这种情况下,我会抛出一个新的ApplicationException("custom message", innerException)来跟踪真正发生的事情
  • 另外,我尽我所能正确排序异常 。 有以下例外情况:

  • 需要立即显示给用户
  • 需要一些额外的处理来将它们放在一起,以避免级联问题(即:在TreeView填充过程中,在finally节中放置.EndUpdate)
  • 用户不在意,但知道发生了什么很重要。 所以我总是记录它们:

  • 在事件日志中
  • 或者在磁盘上的.log文件中
  • 设计一些静态方法来处理应用程序顶级错误处理程序中的异常是一种很好的做法。

    我也强迫自己尝试:

  • 记住所有异常都会冒泡到顶层 。 没有必要把异常处理程序放在任何地方。
  • 可重用或深层调用的函数不需要显示或记录异常:它们要么自动冒出来,要么在异常处理程序中用一些自定义消息重新抛出。
  • 最后:

    坏:

    // DON'T DO THIS, ITS BAD
    try
    {
        ...
    }
    catch 
    {
       // only air...
    }
    

    无用:

    // DONT'T DO THIS, ITS USELESS
    try
    {
        ...
    }
    catch(Exception ex)
    {
        throw ex;
    }
    

    终于没有抓住一个尝试是完全有效的:

    try
    {
        listView1.BeginUpdate();
    
        // If an exception occurs in the following code, then the finally will be executed
        // and the exception will be thrown
        ...
    }
    finally
    {
        // I WANT THIS CODE TO RUN EVENTUALLY REGARDLESS AN EXCEPTION OCCURED OR NOT
        listView1.EndUpdate();
    }
    

    我在顶层做什么:

    // i.e When the user clicks on a button
    try
    {
        ...
    }
    catch(Exception ex)
    {
        ex.Log(); // Log exception
    
        -- OR --
    
        ex.Log().Display(); // Log exception, then show it to the user with apologies...
    }
    

    我在一些所谓的功能上做了什么:

    // Calculation module
    try
    {
        ...
    }
    catch(Exception ex)
    {
        // Add useful information to the exception
        throw new ApplicationException("Something wrong happened in the calculation module :", ex);
    }
    
    // IO module
    try
    {
        ...
    }
    catch(Exception ex)
    {
        throw new ApplicationException(string.Format("I cannot write the file {0} to {1}", fileName, directoryName), ex);
    }
    

    异常处理有很多事情要做(自定义异常),但是我试图记住的规则对于我所做的简单应用程序来说已经足够了。

    下面是一个扩展方法的例子,用于处理捕捉到的异常。 它们的实现方式可以链接在一起,并且添加自己的捕获的异常处理非常简单。

    // Usage:
    
    try
    {
        // boom
    }
    catch(Exception ex)
    {
        // Only log exception
        ex.Log();
    
        -- OR --
    
        // Only display exception
        ex.Display();
    
        -- OR --
    
        // Log, then display exception
        ex.Log().Display();
    
        -- OR --
    
        // Add some user-friendly message to an exception
        new ApplicationException("Unable to calculate !", ex).Log().Display();
    }
    
    // Extension methods
    
    internal static Exception Log(this Exception ex)
    {
        File.AppendAllText("CaughtExceptions" + DateTime.Now.ToString("yyyy-MM-dd") + ".log", DateTime.Now.ToString("HH:mm:ss") + ": " + ex.Message + "n" + ex.ToString() + "n");
        return ex;
    }
    
    internal static Exception Display(this Exception ex, string msg = null, MessageBoxImage img = MessageBoxImage.Error)
    {
        MessageBox.Show(msg ?? ex.Message, "", MessageBoxButton.OK, img);
        return ex;
    }
    

    最佳做法是异常处理不应该隐藏问题。 这意味着try-catch块应该非常少见。

    有三种情况使用try-catch是有道理的。

  • 尽可能处理已知的例外情况。 但是,如果您希望发生异常,通常先进行测试是更好的做法。 例如,解析,格式化和算术异常几乎总是由逻辑检查首先处理,而不是特定的try-catch

  • 如果您需要对异常执行某些操作(例如记录或回滚事务),则重新抛出异常。

  • 始终处理未知异常,尽可能高 - 唯一应该消费异常并且不会重新抛出异常的代码应该是UI或公共API。

  • 假设你正在连接到一个远程API,在这里你知道期望某些错误(并且在那些情况下有事情),所以这是情况1:

    try 
    {
        remoteApi.Connect()
    }
    catch(ApiConnectionSecurityException ex) 
    {
        // User's security details have expired
        return false;
    }
    
    return true;
    

    请注意,没有其他例外被捕获,因为它们不是预期的。

    现在假设你正在试图将某些东西保存到数据库中。 如果它失败了,我们必须将其回滚,所以我们有案例2:

    try
    {
        DBConnection.Save();
    }
    catch
    {
        // Roll back the DB changes so they aren't corrupted on ANY exception
        DBConnection.Rollback();
    
        // Re-throw the exception, it's critical that the user knows that it failed to save
        throw;
    }
    

    请注意,我们重新抛出异常 - 上面的代码仍然需要知道某些失败。

    最后我们有了用户界面 - 这里我们不想完全没有处理异常,但我们也不想隐藏它们。 这里我们有一个案例3的例子:

    try
    {
        // Do something
    }
    catch(Exception ex) 
    {
        // Log exception for developers
        WriteException2LogFile(ex);
    
        // Display message to users
        DisplayWarningBox("An error has occurred, please contact support!");
    }
    

    但是,大多数API或UI框架都具有通用的方法来处理案例3.例如,ASP.Net具有一个黄色错误屏幕,用于转储异常详细信息,但可以在生产环境中用更通用的消息替换。 遵循这些是最佳实践,因为它为您节省了大量代码,同时还因为错误日志记录和显示应该是配置决策而不是硬编码。

    这一切都意味着案例1(已知例外)和案例3(一次性UI处理)都具有更好的模式(避免预期的错误或处理UI的手部错误)。

    即使是案例2也可以被更好的模式取代,例如事务处理范围( using回滚块中未提交的任何事务的块)会使开发人员难以获得最佳实践模式。

    例如,假设你有一个大规模的ASP.Net应用程序。 错误记录可以通过ELMAH进行,错误显示可以是本地信息量大的YSoD,也可以是生产中的一个很好的本地化信息。 数据库连接都可以通过事务范围和using块。 你不需要一个try-catch块。

    TL; DR:最佳实践实际上是根本不使用try-catch块。


    例外是一个阻塞错误。

    首先,最好的做法应该是不要抛出任何类型的错误的例外,除非它是一个阻塞错误。

    如果错误被阻止,则抛出异常。 一旦已经抛出异常,就没有必要隐藏它,因为它是非常特殊的; 让用户了解它(您应该将整个异常重新格式化为用户界面中对用户有用的内容)。

    作为软件开发人员的工作是努力防止一些特殊情况,例如某些参数或运行时情况可能会以例外情况结束。 也就是说, 异常不能被静音,但必须避免这些异常

    例如,如果您知道某些整数输入可能带有无效格式,请使用int.TryParse而不是int.Parse 。 有很多情况下你可以做到这一点,而不是仅仅说“如果失败,只是抛出异常”。

    抛出异常很昂贵。

    毕竟,如果引发异常,而不是一旦抛出异常就写入日志,最好的方法之一就是在第一次机会的异常处理程序中捕获异常。 例如:

  • ASP.NET:Global.asax Application_Error
  • 其他: AppDomain.FirstChanceException事件
  • 我的立场是,本地尝试/捕获更适合处理可能会将异常转换为另一个异常的特殊情况,或者当您想要将它“静音”为非常非常非常特殊的情况时(库缺陷抛出一个无关的异常,你需要静音才能解决整个bug)。

    对于其他情况:

  • 尽量避免例外。
  • 如果这是不可能的:一次机会异常处理程序。
  • 或者使用PostSharp方面(AOP)。
  • 对某个评论回复@thewhiteambit ...

    @thewhiteambit说:

    例外不是致命错误,它们是例外! 有时他们甚至不是错误,但要考虑他们致命错误对于异常是什么是完全错误的理解。

    首先,一个异常如何不能成为一个错误?

  • 没有数据库连接=>异常。
  • 无效的字符串格式解析为某种类型=>异常
  • 尝试解析JSON,而输入实际上不是JSON =>异常
  • 当期望对象时参数为null =>异常
  • 某些库有bug =>会引发意外的异常
  • 有一个套接字连接,它被断开。 然后你尝试发送消息=>异常
  • ...
  • 我们可能列出1k个案例,当抛出异常时,任何可能的案例都将是错误的。

    一个例外是一个错误,因为在一天结束的时候它是一个收集诊断信息的对象 - 它有一条消息,当发生错误时它会发生。

    当没有特殊情况时,没有人会抛出异常。 异常应该阻止错误,因为一旦抛出异常,如果您不尝试使用try / catch和异常来实现控制流,那么它们意味着您的应用程序/服务将会停止进入异常情况的操作。

    另外,我建议大家检查由Martin Fowler发布的(由Jim Shore撰写的)失败模式。 这就是我一直理解如何处理异常的情况,甚至在我刚才讨论这个文档之前。

    [...]认为他们致命错误是完全错误的理解什么是例外。

    通常情况下,异常会削减一些操作流程,并将它们处理为将它们转换为人为可理解的错误。 因此,看起来像一个异常实际上是一个更好的范例来处理错误情况并对它们进行处理以避免应用程序/服务完全崩溃,并通知用户/消费者出现问题。

    有关@thewhiteambit关注的更多答案

    例如,如果数据库连接丢失,程序可能会异常地继续写入本地文件,并在数据库再次可用时将更改发送到数据库。 你可以尝试用异常的语言本地解释再次解析你的无效的字符串到数字的转换,就像你尝试使用默认的英语语言解析(“1,5”)失败,并且你再次用德语解释来尝试它,这是完全的很好,因为我们使用逗号而不是点作为分隔符。 你看到这些异常不能被阻塞,他们只需要一些异常处理。

  • 如果您的应用可能脱机工作而没有将数据保存到数据库,则不应该使用异常,因为使用try/catch实现控制流被认为是反模式。 脱机工作是一种可能的用例,因此您可以实施控制流来检查数据库是否可访问,您不必等到数据库无法访问

  • 解析的东西也是一个预期的情况( 不是EXCEPTIONAL CASE )。 如果你期望这一点, 你不会使用异常来控制流量! 。 你可以从用户那里得到一些元数据来知道他/她的文化是什么,并且你使用格式化程序来完成这个! .NET也支持这个和其他的环境,并且一个例外,因为如果你希望你的应用程序/服务的文化特定用法,那么必须避免数字格式化

  • 未处理的异常通常会变成错误,但异常本身不是codeproject.com/Articles/15921/Not-All-Exceptions-Are-Errors

    本文只是作者的观点或观点。

    由于维基百科也可以只是作曲家作者的意见,所以我不会说这是一个教条,而是检查某些段落某处的异常文章所说的编码:

    [...]使用这些例外来处理继续该程序出现的特定错误称为例外编码。 这种反模式可以快速降低软件的性能和可维护性。

    它还说:

    异常使用不正确

    通常由异常编码可能会导致软件中出现进一步问题,导致异常使用不正确。 除了针对唯一问题使用异常处理之外,不正确的异常使用甚至在引发异常之后通过执行代码进一步进行处理。 这种可怜的编程方法类似于许多软件语言中的goto方法,但仅在检测到软件中存在问题后才出现。

    老实说,我认为软件不能开发不重视用例。 如果你知道...

  • 你的数据库可以离线...
  • 有些文件可以被锁定...
  • 某些格式可能不受支持...
  • 某些域验证可能会失败...
  • 您的应用应该可以在离线模式下运行
  • 无论用例如何...
  • ... 你不会为此使用例外 。 您可以使用常规控制流来支持这些用例。

    如果一些意外的用例没有被覆盖,你的代码将会很快失败,因为它会抛出一个异常 。 对,因为例外是一种例外情况。

    另一方面,最后,有时你会涉及抛出预期异常的例外情况 ,但是你不会抛出它们来实现控制流。 你这样做是因为你想通知上层你不支持某些用例,或者你的代码无法使用某些给定的参数或环境数据/属性。

    链接地址: http://www.djcxy.com/p/21131.html

    上一篇: How using try catch for exception handling is best practice

    下一篇: try/catch + using, right syntax