[WinForm] 利用全域例外捕捉和方法自訂特性,提升Error可讀性

結對開發時常常會看到例外處理寫的不好,來看看這一次的案例...

結對開發時常常看到這樣的寫法,捕捉到了例外之後,自訂了一個錯誤訊息並拋出例外

public decimal Add(decimal firstNumber, decimal secondNumber)
{
    try
    {
        //DO...
        return firstNumber + secondNumber;
    }
    catch (Exception e)
    {
        throw new Exception("加法錯計算錯誤");
    }
}

 

這有甚麼問題??

例外堆疊被破壞了,log 到的例外內容會是錯的,你可能會將整個方法都包在一個 try/catch 裡面,裡面做了很多的事,可能會跟資料庫溝通或是邏輯的演算,當這樣寫時,用戶端(高層模組)拿到的例外行號會是錯的,這時候用戶端就要開始猜,到底是哪一行引起的例外

為什麼他要這樣做?

要把錯誤訊息變成自訂中文描述

 

如果你真的需要捕捉例外,可以這樣寫

public decimal Add(decimal firstNumber, decimal secondNumber)
{
    try
    {
        //DO...
        return firstNumber + secondNumber;
    }
    catch (Exception e)
    {
        throw new Exception("加法錯誤", e);
    }
}

 

這樣也可以

public decimal Add(decimal firstNumber, decimal secondNumber)
{
    try
    {
        //DO...
        return firstNumber + secondNumber;
    }
    catch (Exception e)
    {
        throw;
    }
}

 

這兩種方法的例外推疊都會被保留下來,你會知道到底是哪一個行號出了問題

 

只是為了自訂例外訊息,每一個 Method 都要寫 catch,何不利用全域例外捕捉?

 

以 WinForm 為例,用下面的寫法就可以全域捕捉例外

Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
Application.ThreadException += Application_ThreadException;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

 

internal static class Program
{
    private static readonly ILogger s_logger = LogManager.GetCurrentClassLogger();
 
    private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
    {
       //catch exception
    }
 
    private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        //catch exception
    }
 
    /// <summary>
    ///     The main entry point for the application.
    /// </summary>
    [STAThread]
    private static void Main()
    {
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
        Application.ThreadException                += Application_ThreadException;
        AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
 
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}

 

自訂一個 Attribute,使用範圍在 Method、Property,這得看你的需求

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property,AllowMultiple = false)]
public class ErrorDescriptionAttribute : Attribute
{
    public string Description { get; set; }
 
    public ErrorDescriptionAttribute(string description)
    {
        this.Description = description;
    }
}

 

掛載方法

[ErrorDescription("加法錯誤")]
public decimal Add(decimal firstNumber, decimal secondNumber)
{
    throw new Exception("Fail");
}

 

用擴充方法把 ErrorDesriptionAttribute 找出來

public static class ExceptionExtend
{
    public static ErrorDescriptionAttribute GetCurrentErrorDescription(this Exception source)
    {
        var stack      = new StackTrace(source, true);
        var frame      = stack.GetFrame(0);
        var methodBase = frame.GetMethod();
        var attribute = methodBase.GetCustomAttributes(typeof(ErrorDescriptionAttribute), true)
                                  .Select(p => (ErrorDescriptionAttribute) p)
                                  .FirstOrDefault();
        return attribute;
    }
}

 

調用端就用 exception.GetCurrentErrorDescription() 能取得發生例外方法的 ErrorDescriptionAttribute,錯誤訊息一方面跟 User 作互動,也寫到了 NLog 裡面通知開發人員

internal static class Program
{
    private static readonly ILogger s_logger = LogManager.GetCurrentClassLogger();
 
    private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
    {
        var exception = e.Exception;
        Show(exception);
    }
 
    private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        var exception = (Exception) e.ExceptionObject;
        Show(exception);
    }
 
    ....
 
    private static void Show(Exception exception)
    {
        var errorDescription = exception.GetCurrentErrorDescription();
        var errorMsg         = $"{errorDescription.Description},{exception.Message}";
        s_logger.Error(exception, errorMsg);
        MessageBox.Show(errorMsg, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

 

初期開發的時候,我會例外全域例外捕捉的技巧進行,當全域例外捕捉的方法無法滿足的時候,比如說彈跳視窗已經沒有辦法滿足跟用戶的互動,才會在 UI Layer 的事件裡面寫 try/catch

全域例外捕捉也降低了開發人員處理例外的 effort

 

專案位置

https://github.com/yaochangyu/sample.dotblog/tree/master/Exception/Lab.ExceptionStack
 

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo