异常处理(exception handling)

异常处理(exception handling)的成本与处理原则

1、为了在运行期处理异常,程序必须做大量额外的工作。比如,即使抛出异常,也必须保证离开作用域的栈上对象执行析构方法。因此,必须记录try语句的进入点和离开点,记录catch语句能够处理的异常等。这就意味着,程序目标码变大,执行速度慢。

2、即使从未使用任何异常处理,还是必须要付出最低代价,付出一些空间,放置某些数据结构,付出一些时间,保持数据结构的正确性。

3、即使自己的程序没有使用throw,try,catch语句,使用的其他程序库可能有异常处理,因此也要付出代价。

4、对于try语句,没有异常抛出的情况下,代码膨胀5%-10%,速度也下降这个数。

5、如果抛出异常,影响很大,速度可能会比正常情况下慢3个数量级。但是,抛出异常是罕见的,因此可以接受。这也就意味着,在相对正常的情况下,不要抛出异常。

6、考虑到异常对效率的影响,因此,在非用不可的情况下,才使用try语句。在确实是个异常的情况下,才抛出异常。

处理策略handling strategy

在钩住(hooking)Application.ThreadException事件后,捕获所有未处理异常后

  • 对于UI应用(如winforms),应弹出向用户致歉的包含异常信息的窗口(winforms)。

  • 对于Service或Console应用,记录日志到文件中(service 或 console)。

Then I always enclose every piece of code that is run externally in try/catch :

  • All events fired by the Winforms infrastructure (Load, Click, SelectedChanged...)

  • All events fired by third party components

Then I enclose in 'try/catch'

  • All the operations that I know might not work all the time (IO operations, calculations with a potential zero division...). In such a case, I throw a new ApplicationException("custom message", innerException) to keep track of what really happened

Additionally, I try my best to sort exceptions correctly. There are exceptions which:

  • need to be shown to the user immediately

  • require some extra processing to put things together when they happen to avoid cascading problems (ie: put .EndUpdate in the finally section during a TreeView fill)

  • the user does not care, but it is important to know what happened. So I always log them:

    • In the event log

    • or in a .log file on the disk

It is a good practice to design some static methods to handle exceptions in the application top level error handlers.

I also force myself to try to:

  • Remember ALL exceptions are bubbled up to the top level. It is not necessary to put exception handlers everywhere.

  • Reusable or deep called functions does not need to display or log exceptions : they are either bubbled up automatically or rethrown with some custom messages in my exception handlers.

实例

糟糕用法:

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

无效用法,不如不处理异常:

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

Having a try finally without a catch is perfectly valid:

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 NOTlistView1.EndUpdate();
}

What I do at the top level:

// 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...
}

What I do in some called functions:

// Calculation module
try{...}
catch(Exception ex)
{
// Add useful information to the exceptionthrow 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);}

There is a lot to do with exception handling (Custom Exceptions) but thoses rules I try to keep in mind are enough for the simple applications I do.

Here is an example of extensions methods to handle caught exceptions a comfortable way. They are implemented a way they can be chained together, and it is very easy to add your own caught exception processing.

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

参见