[C#] 自訂例外狀況,怎麼樣才訂的好?

  • 95
  • 0

之前只有稍微紀錄一下例外狀況需要怎麼訂
最近有感而發
想分享和記錄自己對於 Exception 的理解與使用

最近在測試一些前端畫面的 Error Handler
看到一支 API 的錯誤訊息竟然是回覆這個

沒測試不知道,一測試讓我整個人嚇歪
竟然直接把我們專案中的名稱全都印出來給前端
深入理解後,發現有很多人其實不太了解自訂 Exception 要注意的事以及 Exception Error Handle 可以怎麼做

在工作中,常見到的 Exception

在目前的工作中,很常使用不同的自訂 Exception 來丟出例外錯誤
然後我們會再透過 Middleware 針對不同的 Exception 做相對應的例外處理

我有遇過依照功能訂出特定的 Exception
像是使用者登入失敗,是因為沒有註冊過,可以丟出 UserNotFoundException

也有遇過依照專案名稱訂專用的 Exception
比如說專案名稱叫做 Xinyi,那就會訂一個 XinyiException 針對這個專案的例外做處理

所以我們有可能會這樣子在建立我們的自訂 Exception:

public class XinyiException : Exception
{
    public ErrorCode ErrorCode { get; set; }
    public string ErrorMessage { get; set; }

    public XinyiException(ErrorCode errorCode, string errorMessage)
    {
        ErrorCode = errorCode;
        ErrorMessage = errorMessage;
    }
}

在實際使用上會遇到問題?

我們預期所有開發人員會使用我們自定義的 ErrorMessage 來印相關的 log
但事情往往不如預期,尤其是在系統越來越龐大
甚至是公司開發人員越來越多,就會開始遇到問題了

首先會遇到的是有很像的 property 可以使用
Exception 原本的 Message 和 自訂的 ErrorMessage
這個問題看似很小,但只要有開發人員拿錯,又把這個直接傳給前端,那就會讓 end user 看到驚喜的錯誤訊息

或許這些問題可以從 Middleware 解決、把 Exception 都封裝起來等等方式
但如果能讓開發人員都有良好的認知與理解,並且知道不要掉進這種地雷,我認為是更好的方法

範例:你以為的錯誤訊息不是你以為的

 [Test]
 public void XinyiExceptionTest()
 {
     var exception = new XinyiException(ErrorCode.LoginFail, "Login service fail.");
     
     Assert.That(exception.ErrorMessage, Is.EqualTo("Login service fail."));
     Assert.That(exception.Message, Is.EqualTo("Login service fail."));
 }

為什麼會拿到丟出 Exception 的 class name?
因為 Exception 底層的 Message 是這樣寫的:

public virtual string Message => _message ?? SR.Format(SR.Exception_WasThrown, GetClassName());

想要讓 Exception 的錯誤訊息不要被誤拿,我們可以這樣做

1. 繼承 Exception(String),它會接受字串訊息

public XinyiException(ErrorCode errorCode, string errorMessage) : base(errorMessage)
{
    ErrorCode = errorCode;
    ErrorMessage = errorMessage;
}

base 裡面,幫你把 message 塞進去

public Exception(string? message): this()
{
    _message = message;
}

2. 繼承 Exception(String, Exception),它會接受字串訊息和內部例外狀況

像是這樣:

public XinyiException(ErrorCode errorCode, string errorMessage, Exception innerException) : base(errorMessage, innerException)
{
    ErrorCode = errorCode;
    ErrorMessage = errorMessage;
}

Exception 底層的程式碼裡就有給你「建議」

Creates a new Exception. All derived classes should provide this constructor.

什麼是 Stack trace (堆疊追蹤)?

簡單來說就是會幫你把 exception append 起來
當 application 發生問題時,可以一層一層追蹤問題發生的地方在哪一行
不會因為中間被某個 try catch 攔住時,原本發生問題的 Exception就不見了

結論

根據 MSDN 建議:建立您自己的例外狀況時,請以文字 "Exception" 作為使用者定義例外狀況類別名稱的結尾,並實作三種常見的建構函式

大家在自訂 Exception 的時候,要記得實作這些 constructor 唷!

這次先分享到這邊,以上的範例可以到 ExceptionExample 查看


上一篇與 Exception 相關的文章:

[C#] 自訂例外處理 (Implementing Custom Exceptions)

參考:

如何建立使用者定義的例外狀況