結對開發時常常會看到例外處理寫的不好,來看看這一次的案例...
結對開發時常常看到這樣的寫法,捕捉到了例外之後,自訂了一個錯誤訊息並拋出例外
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