异常处理(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.
实例
糟糕用法:
try
{...}
catch
{// only air...}
无效用法,不如不处理异常:
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;
}