首页 文章

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

提问于
浏览
183

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

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

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

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

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

请给我一些建议 .

14 回答

  • 0

    最佳做法是在发生错误时抛出异常 . 因为发生了错误,所以不应该隐藏它 .

    但在现实生活中,当你想要隐藏它时,你可以有几种情况

    • 您依赖第三方组件,并且您希望在出现错误时继续该程序 .

    • 您有一个商业案例,如果出现错误,您需要继续

  • 0

    您应该考虑这些例外设计指南

    • 异常投掷

    • 使用标准异常类型

    • 例外和表现

    https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/exceptions

  • 0

    对我来说,处理异常可以看作是业务规则 . 显然,第一种方法是不可接受的 . 第二个是更好的一个,如果上下文这样说,它可能是100%正确的方式 . 现在,例如,您正在开发Outlook Addin . 如果你添加了抛出未处理的异常,那么outlook用户现在可能知道它,因为一个插件失败后,outlook将不会自行销毁 . 你很难弄清楚出了什么问题 . 因此,在这种情况下,对我来说,第二种方法是正确的 . 除了记录异常之外,您可能决定向用户显示错误消息 - 我将其视为业务规则 .

  • 5

    我的异常处理策略是:

    • 要通过挂钩到 Application.ThreadException event 来捕获 all unhandled exceptions ,然后决定:

    • 对于UI应用程序:使用道歉消息将其弹出给用户(winforms)

    • 对于服务或控制台应用程序:将其记录到文件(服务或控制台)

    然后我总是在 try/catch 中附上 every piece of code that is run externally

    • Winforms基础架构触发的所有事件(Load,Click,SelectedChanged ...)

    • 由第三方组件触发的所有事件

    然后我附上'try / catch'

    • 所有 operations that I know might not work all the time (IO操作,具有潜在零分割的计算......) . 在这种情况下,我抛出一个新的 ApplicationException("custom message", innerException) 来跟踪真实情况

    另外,我尽力 sort exceptions correctly . 有例外情况:

    • 需要立即显示给用户

    • 需要一些额外的处理才能将事情放在一起,以避免出现级联问题(例如:在 TreeView 填充期间将.EndUpdate放在 finally 部分中)

    • 用户并不在意,但重要的是要知道发生了什么 . 所以我总是记录它们:

    • 在事件日志中

    • 或磁盘上的.log文件

    在应用程序顶级错误处理程序中 design some static methods to handle exceptions 是一个很好的做法 .

    我也强迫自己尝试:

    • 记住 ALL exceptions are bubbled up to the top level . 没有必要在任何地方放置异常处理程序 .

    • 可重用或深度调用的函数不需要显示或记录异常:它们要么自动冒泡,要么在我的异常处理程序中使用一些自定义消息重新抛出 .

    最后:

    坏:

    // 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;
    }
    
  • 0

    最佳实践是异常处理永远不应该隐藏问题 . 这意味着 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;
    }
    

    请注意,我们重新抛出异常 - 更高的代码仍需要知道某些内容已失败 .

    最后我们有了UI - 这里我们不希望有完全未处理的异常,但我们也不想隐藏它们 . 这里我们有一个案例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可以被更好的模式替换,例如transaction scopes(回滚块中未提交的任何事务的块)会使开发人员更难以获得错误的最佳实践模式 .

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

    TL; DR:最佳做法实际上是根本不使用 try-catch 块 .

  • 5

    阻塞错误是一个例外 .

    首先,最好的做法应该是阻止错误 .

    如果错误被阻止,则抛出异常 . 一旦抛出异常,就会出现异常情况 . 让用户知道它(您应该将整个异常重新格式化为UI中对用户有用的东西) .

    您作为软件开发人员的工作是努力防止某些参数或运行时情况可能以异常结束的特殊情况 . 那就是 exceptions mustn't be muted, but these must be avoided .

    例如,如果您知道某些整数输入可能带有无效格式,请使用 int.TryParse 而不是 int.Parse . 在很多情况下你可以做到这一点,而不是只说"if it fails, simply throw an exception" .

    抛出异常是昂贵的 .

    毕竟,如果抛出异常,而不是在抛出异常后将其写入日志,最佳实践之一是在第一次机会异常处理程序中捕获它 . 例如:

    • ASP.NET: Global.asax Application_Error

    • 其他: AppDomain.FirstChanceException event .

    我的立场是,本地尝试/捕获更适合处理特殊情况,您可以将异常转换为另一个异常,或者当您想要将其“静音”为一个非常非常非常非常特殊的情况时(库错误)抛出一个你需要静音的无关异常,以便解决整个bug) .

    对于其他案例:

    • 尽量避免例外 .

    • 如果这不可能:第一次机会异常处理程序 .

    • 或使用PostSharp方面(AOP) .

    在某些评论中回复@thewhiteambit

    @thewhiteambit说:

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

    首先,异常怎么可能不是一个错误?

    • 没有数据库连接=>异常 .

    • 要解析为某种类型=>异常的无效字符串格式

    • 尝试解析JSON,而输入实际上不是JSON => exception

    • 参数 null ,而对象是预期的=>异常

    • 某些库有bug =>引发意外异常

    • 有一个套接字连接,它会断开连接 . 然后你尝试发送一个消息=>异常

    • ......

    我们可能会列出抛出异常的1k个案例,毕竟,任何可能的情况都是错误的 .

    异常是一个错误,因为在一天结束时它是一个收集诊断信息的对象 - 它有一条消息,它会在出现问题时发生 .

    在没有例外的情况下,没有人会抛出异常 . 异常应该是阻止错误,因为一旦他们尝试使用try / catch和exception来实现控制流,他们就意味着你的应用程序/服务将停止进入异常情况的操作 .

    另外,我建议大家检查一下快速失败的范例published by Martin Fowler (and written by Jim Shore) . 这就是我总是理解如何处理异常的方法,甚至在我不久之前得到这个文档之前 .

    [...]考虑他们致命错误完全错误地理解异常是什么 .

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

    关于@thewhiteambit关注的更多答案

    例如,如果数据库连接丢失,程序可以异常地继续写入本地文件,并在数据库再次可用后将更改发送到数据库 . 您可以尝试使用无效的String-To-Number转换来解析Exception的语言本地解释,就像你尝试默认英语语言到Parse(“1,5”)失败一样,你再次尝试德语解释,这完全没问题,因为我们使用逗号而不是点作为分隔符 . 您看到这些异常甚至不能被阻止,它们只需要一些异常处理 .

    • 如果您的应用程序可能脱机工作而不将数据持久保存到数据库,则不应使用异常,因为使用_1226118实现控制流被视为反模式 . Offline work is a possible use case, so you implement control flow to check if database is accessible or not, you don't wait until it's unreachable .

    • 解析事物也是预期的情况( not EXCEPTIONAL CASE ) . 如果你期待这个, you don't use exceptions to do control flow! . 您可以从用户那里获得一些元数据,以了解他/她的文化,并为此使用格式化程序! .NET supports this and other environments too, and an exception because number formatting must be avoided if you expect a culture-specific usage of your application/service .

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

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

    既然维基百科也可以只是关于作者的意见,我也不会这样做,但是检查一下Coding by exception篇文章中的某个段落:

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

    它也在某处说:

    异常使用不正确

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

    老实说,我认为软件无法开发不认真对待用例 . 如果你知道......

    • 您的数据库可以离线...

    • 某些文件可以锁定...

    • 可能不支持某些格式...

    • 某些域验证可能会失败...

    • 您的应用应该在离线模式下工作...

    • 无论用例如何......

    ...... you won't use exceptions for that . 您可以使用常规控制流来查看这些用例 .

    如果未涵盖某些意外用例,则代码将快速失败,因为 it'll throw an exception . 是的,因为异常是一种例外情况 .

    另一方面,最后,有时您会覆盖抛出 expected exceptions 的特殊情况,但您不支持某些用例,或者您的代码无法使用某些给定的参数或环境数据/属性 .

  • 273

    您唯一一次应该担心用户关于代码中发生的事情,如果他们可以或需要做些什么来避免这个问题 . 如果他们可以更改表单上的数据,请按下按钮或更改应用程序设置以避免该问题,然后让他们知道 . 但是,用户无法避免的警告或错误只会让他们对您的产品失去信心 .

    例外和日志适合您,开发人员,而不是您的最终用户 . 了解捕获每个异常时要做的正确事情远比仅仅应用一些黄金法则或依赖于应用程序范围的安全网要好得多 .

    无意识编码是唯一的错误编码 . 事实上,你觉得在这些情况下可以做得更好,这表明你投入了良好的编码,但是避免在这些情况下尝试标记一些通用规则并理解首先抛出某些东西的原因以及什么你可以做到从中恢复 .

  • 54

    我知道这是一个老问题,但是这里没有人提到MSDN文章,并且实际上是为我清理了文档,MSDN上有一个very good document,当满足以下条件时你应该捕获异常:

    您非常了解可能抛出异常的原因,并且可以实现特定的恢复,例如在捕获FileNotFoundException对象时提示用户输入新的文件名 . 您可以创建并抛出一个新的,更具体的异常 .

    int GetInt(int[] array, int index)
    {
        try
        {
            return array[index];
        }
        catch(System.IndexOutOfRangeException e)
        {
            throw new System.ArgumentOutOfRangeException(
                "Parameter index is out of range.");
        }
    }
    

    您希望在传递异常之前部分处理异常以进行其他处理 . 在以下示例中,catch块用于在重新抛出异常之前向错误日志添加条目 .

    try
    {
        // Try to access a resource.
    }
    catch (System.UnauthorizedAccessException e)
    {
        // Call a custom error logging procedure.
        LogError(e);
        // Re-throw the error.
        throw;     
    }
    

    我建议阅读整个“Exceptions and Exception Handling”部分以及Best Practices for Exceptions .

  • 32

    更好的方法是第二个(指定异常类型的方法) . 该这样做的好处是,您知道代码中可能会发生此类异常 . 您正在处理此类异常,您可以继续 . 如果出现任何其他异常,那么这意味着有些错误可以帮助您找到代码中的错误 . 应用程序最终会崩溃,但你会发现你错过了一些错误(bug)需要修复 .

  • 1

    第二种方法是好方法 .

    如果您不想通过显示与它们无关的运行时异常(即错误)来显示错误并使应用程序用户感到困惑,那么只需记录错误,技术团队就可以查找问题并解决问题 .

    try
    {
      //do some work
    }
    catch(Exception exception)
    {
       WriteException2LogFile(exception);//it will write the or log the error in a text file
    }
    

    我建议您为整个应用程序选择第二种方法 .

  • 1

    留下空白挡块是最糟糕的事情 . 如果出现错误,最好的处理方法是:

    • 将其登录到文件\数据库等 .

    • 尝试动态修复它(也许尝试其他方式进行该操作)

    • 如果我们无法解决此问题,请通知用户存在一些错误,当然会中止操作

  • 0

    没有任何参数的 catch 只是 eating 例外并没有用 . 如果发生致命错误怎么办?如果你没有参数使用catch,就无法知道发生了什么 .

    一个catch语句应该捕获更多的 specific 例外 FileNotFoundException 然后在 end 你应该捕获 Exception ,这将捕获任何其他异常并记录它们 .

  • 0

    有时您需要处理对用户不说任何内容的异常 .

    我的方式是:

    • 在应用程序级别(即在global.asax中)捕获关键异常的未处理异常(应用程序无用) . 这些事情我没有 grab 这个地方 . 只需在应用程序级别上记录它们,让系统完成其工作 .

    • Catch "on place"并向用户显示一些有用的信息(输入错误的数字,无法解析) .

    • 赶上地方,对"I will check for update info on the background, but the service is not running"等边缘问题什么都不做 .

    它绝对不一定是最佳实践 . ;-)

  • 0

    使用例外,我尝试以下方法:

    首先,我捕获特殊类型的异常,例如除零,IO操作等,并根据它编写代码 . 例如,除以我可以提醒用户的值的证明(例如,在中间计算中的简单计算器(不是参数)到达零除零)或者静默处理该异常,记录它并继续处理 .

    然后我尝试捕获剩余的异常并记录它们 . 如果可能,允许执行代码,否则警告用户发生错误并要求他们邮寄错误报告 .

    在代码中,这样的事情:

    try{
        //Some code here
    }
    catch(DivideByZeroException dz){
        AlerUserDivideByZerohappened();
    }
    catch(Exception e){
        treatGeneralException(e);
    }
    finally{
        //if a IO operation here i close the hanging handlers for example
    }
    

相关问题