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

时间:2013-02-20 06:32:38

标签: c# .net exception exception-handling try-catch

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

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

或有时他们将日志记录信息写入日志文件,如try catch阻止

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

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

请给我一些建议。

15 个答案:

答案 0 :(得分:291)

我的异常处理策略是:

  • 要通过挂钩Application.ThreadException event来捕获所有未处理的例外,然后决定:

    • 对于UI应用程序:使用道歉消息(winforms)将其弹出给用户
    • 对于服务或控制台应用程序:将其记录到文件(服务或控制台)

然后我总是在try/catch中附上外部运行的每一段代码

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

然后我附上'try / catch'

  • 知道的所有操作可能无法一直 (IO操作,具有潜在零分割的计算......)。在这种情况下,我会抛出一个新的ApplicationException("custom message", innerException)来跟踪实际发生的事情

此外,我尽力正确排序异常。有例外:

  • 需要立即向用户显示
  • 需要一些额外的处理,以便在碰巧出现级联问题时将事情放在一起(例如:在finally填充期间将{.EndUpdate放入TreeView部分)
  • 用户不在乎,但重要的是要知道发生了什么。所以我总是记录它们:

    • 在事件日志中
    • 或磁盘上的.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;
}

答案 1 :(得分:58)

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

在3种情况下使用try-catch是有道理的。

  1. 始终尽可能低地处理已知的异常。但是,如果您期望出现异常,通常最好先测试一下。例如,解析,格式化和算术异常几乎总是首先由逻辑检查更好地处理,而不是特定的try-catch

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

  3. 始终处理 unknown 异常,因为只有 代码应该消耗异常而不是重新抛出它应该是用户界面或公共API。

  4. 假设您正在连接到远程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(回滚块中未提交的任何事务的using块)使开发人员更难以获得错误的最佳实践模式。

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

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

答案 2 :(得分:32)

异常是阻止错误

首先,最佳做法是不要为任何类型的错误抛出异常,除非它是阻止错误

如果错误是阻止,则抛出异常。一旦异常被抛出,就没有必要隐藏它,因为它是例外;让用户知道它(您应该将整个异常重新格式化为UI中对用户有用的东西)。

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

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

抛出异常是很昂贵的。

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

  • ASP.NET: Global.asax Application_Error
  • 其他: AppDomain.FirstChanceException事件

我的立场是,本地尝试/捕获更适合处理特殊情况,您可以将异常翻译成另一个,或者当您想要静音时#34;它是一个非常,非常,非常非常特殊的情况(库错误抛出一个无关的异常,你需要静音才能解决整个bug)。

对于其他案例:

  • 尽量避免例外。
  • 如果这不可能:第一次机会异常处理程序。
  • 或使用PostSharp方面(AOP)。

在某些评论中回复@thewhiteambit ......

@thewhiteambit说:

  

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

首先,异常如何甚至不是错误?

  • 没有数据库连接=>异常。
  • 要解析为某种类型的无效字符串格式=>例外
  • 尝试解析JSON,而输入实际上并不是JSON =>例外
  • 期望对象时的参数null =>例外
  • 有些库有bug =>抛出意外的异常
  • 有一个套接字连接,它会断开连接。然后你尝试发送消息=>例外
  • ...

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

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

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

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

  

[...]考虑他们致命错误完全错误地理解了什么例外。

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

关于@thewhiteambit的更多答案

  

例如,如果数据库连接丢失,程序就可以   异常继续写入本地文件并发送   一旦再次可用,就对数据库进行更改。你的无效   可以尝试使用字符串转换为数字转换   异常的语言本地解释,就像你尝试默认一样   Parse(" 1,5")的英语语言失败,你用德语尝试   再次解释这是完全正常的,因为我们使用逗号   而不是点作为分隔符。你看这些例外情况一定不行   阻止,他们只需要一些异常处理。

  1. 如果你的应用可能在没有将数据保存到数据库的情况下脱机工作,则不应该使用例外,因为使用try/catch实现控制流被认为是反图案。 脱机工作是一种可能的用例,因此您实施控制流程以检查数据库是否可访问,您不要等到它无法访问

  2. 解析事物也是预期的情况(不是例外情况)。如果您希望如此,您不会使用例外来执行控制流程!。您可以从用户那里获得一些元数据,以了解他/她的文化,并为此使用格式化程序! .NET也支持此环境和其他环境,并且例外,因为如果您希望使用特定于文化的应用程序/服务,则必须避免使用数字格式。

  3.   

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

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

    既然维基百科也可以只是关于作者的观点,我也不会说它是的教条,但要检查Coding by exception文章所说的内容在某段中的某处:

      

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

    它也在某处说:

    错误的异常使用

      

    通常按例外编码可能会导致软件中出现进一步的问题   使用不正确的异常。除了使用异常   处理一个独特的问题,不正确的异常使用需要这个   甚至在引发异常之后执行代码。这个   糟糕的编程方法类似于许多软件中的goto方法   语言,但仅在检测到软件中的问题后才会出现。

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

    • 您的数据库可以离线...
    • 某些文件可以锁定...
    • 可能不支持某些格式...
    • 某些域验证可能会失败......
    • 您的应用应该可以在离线模式下工作...
    • 用例 ......

    ... 您不会使用的例外。您将使用常规控制流支持这些用例。

    如果未涵盖某些意外用例,您的代码将快速失败,因为它会抛出异常。是的,因为异常是特殊情况

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

答案 3 :(得分:5)

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

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

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

答案 4 :(得分:5)

我知道这是一个老问题,但是这里没有人提到过MSDN文章,实际上是为我清理了文档,MSDN对此有一个very good document,你应该在下面的时候捕获异常条件是真的:

  
      
  • 您很好地理解了为什么可能抛出异常,并且您可以实现特定的恢复,例如在捕获FileNotFoundException对象时提示用户输入新的文件名。

    < / LI>   
  • 您可以创建并抛出一个新的,更具体的例外。

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

我建议阅读整个&#34; Exceptions and Exception Handling&#34;部分以及Best Practices for Exceptions

答案 5 :(得分:1)

第二种方法很好。

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

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

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

答案 6 :(得分:1)

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

答案 7 :(得分:1)

使用例外,我尝试以下操作:

首先,我捕获特殊类型的异常,例如除零,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
}

答案 8 :(得分:0)

最佳做法是在发生错误时抛出异常。因为发生了错误而且不应该隐藏它。

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

  1. 您依赖第三方组件,并且您希望在出现错误时继续执行该程序。
  2. 如果出现错误,您需要继续处理商业案例

答案 9 :(得分:0)

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

  1. 将其登录到文件\数据库等..
  2. 尝试动态修复它(也许尝试其他方式进行该操作)
  3. 如果我们无法解决此问题,请通知用户存在某些错误,当然会中止操作

答案 10 :(得分:0)

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

我的方式是:

  • 在应用程序级别(即在global.asax中)捕获关键异常的未处理异常(应用程序无用)。这些事情我没有抓住这个地方。只需在应用程序级别上记录它们,让系统完成它的工作。
  • 抓住“就地”并向用户显示一些有用的信息(输入错误的号码,无法解析)。
  • 抓住地方,对边缘问题不采取任何措施,例如“我会检查后台的更新信息,但服务没有运行”。

绝对不一定是最佳做法。 ; - )

答案 11 :(得分:0)

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

答案 12 :(得分:0)

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

catch语句应该捕获更多特定的异常,例如FileNotFoundException,然后在结尾,你应该捕获Exception,这将抓住任何其他异常并记录它们。

答案 13 :(得分:0)

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

  • 异常投掷
  • 使用标准异常类型
  • 例外与表现

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

答案 14 :(得分:0)

我可以告诉你一些事情

代码段1不能接受,因为它忽略了异常。 (它吞没了,好像什么也没发生一样。)

因此,请勿添加不执行任何操作或仅将其重新抛出的catch块。

捕获块应添加一些值。例如,向最终用户输出消息或记录错误。

对于常规流程序逻辑,请勿使用异常。例如:

例如输入验证。 <-这不是有效的特殊情况,您应该编写方法IsValid(myInput);来检查输入项是否有效。

设计代码以避免异常。例如:

int Parse(string input);

如果我们将无法解析的值传递给int,则此方法将引发并异常,相反,我们可能会编写如下内容:

bool TryParse(string input,out int result); <-此方法将返回布尔值,指示解析是否成功。

也许这有点超出这个问题的范围,但是我希望这可以帮助您在关于try {} catch(){}和异常时做出正确的决定。

相关问题