[.net] 關於Exception的幾點思考和在項目中的使用(二)


本文鏈接: https://www.cnblogs.com/hubaijia/p/about-exceptions-2.html

系列文章:

本文目錄



與Exception相關的文件結構


在上一篇我們提到了 集中化管理 Exceptions,這樣所有的Exception的產生都在同一文件中,便於我們后期的Review、重構,不會看到漫天飛舞的throw new Exceptions(...),也盡量避免了同一種類的Exception擁有着不同的Message,記錄着不同的參數信息。

此外在上一篇中我們還提出了細化Exception的方式,即按照 類型、ErrorCode、Cause 以此細化。

舉例來說,項目中會形成如下的文件結構:(實例代碼請訪問 Github)


cddo1s.png

綠框里的就是Exception相關的代碼文件。其中XXX_ErrorCodes, XXX_ExceptionFactory等類型應該標記為internal,即只是類庫內部可見。

在上述文件結構中,我簡要的模擬了一個項目的分層結構,不一定都是這樣,但大致如下:

  • Common 是項目公共類庫,放着各種輔助代碼、框架代碼等等,代表着你項目的基礎部分;
  • Dal 放着連接各種Store(MySql、Redis等等)的代碼,即放DataAccessObject的;
  • Repo 屏蔽各種細節,直接為各種Service提供服務;
  • Services 表示你的各項業務模塊,當然你的項目結構中有不同的叫法和含義,大家各自知道就行;
  • Application 具體的應用,提供Endpoint,開放Service的各項功能,面向調用者。

為什么要詳細描述一個項目分層結構,因為一會兒我們需要解決一個問題,就是Exception的可見性。

先賣個關子,一會兒再說,在此之前,先解決一個小問題。

ExceptionFactory 和 Ensure

將Exception集中化管理,除了我們用的ExceptionFactory,還有一種寫法我們經常見到,那就是Ensure,或者EnsureXXX。

參閱.net core的源代碼,我們會看到非常多的Ensure類,源代碼連接 https://source.dot.net/#q=Ensure。

我們經常把 復雜 或者 重復 出現的判斷寫到Ensure中,Ensure的中文意思“確保”正是這個含義。

示例代碼如下:


// Ensure 寫法
internal static class Ensure
{
    public static void NickNameNotExisted(string newNickName)
    {
        bool alreadyExisted = true;

        // Some Check

        if(alreadyExisted)
        {
            throw IdentityExceptionFactory.NickNameExisted();
        }
    }

    public static T NotNull<T>([NotNull]T? obj, string paramName) where T : class
    {
        if (obj == null)
        {
            throw new ArgumentNullException(paramName);
        }
        return obj;
    }
}

我們觀察到,Ensure類中的Exception也是由ExceptionFactory生成的(當然那些基礎Exception類型,比如ArgumentNullException就直接new 了)。


Ensure類是對判斷(if語句等)的抽象,而ExceptionFactory只是Exception的生成器。


當然,當我們不直接在現場拋出異常,而是調用Ensure類方法拋出異常,會稍微有一些不同:

  • Stack Trace不同,第一行永遠是Ensure的方法
    • 沒有什么大不了,因為Stack Trace會詳細記錄后續的調用;
  • 代碼邏輯上不如直接寫 throw 那么直白
    • 明確使用Ensure這一稱呼,寫到代碼規則里,團隊內統一,一看到Ensure就知道可能要拋出異常。
  • 注意CodeAnalysis 和 NRT(null reference types)
    • 如果項目開啟上面兩項功能,需要添加相應的Attribute。比如,上面代碼的NotNull方法,否則在Ensure后,CodeAnalysis還會提醒你CS8602等錯誤.

Exception的可見性


舉例來說,就是 DalException 要不要對Service可見,還是只對Repo可見?


其實可見或者不可見都沒有明顯的錯誤,但總的來說,

  • 隱藏並包裝下層的Exception,對調用者更友好些。
    • 比如在WebApplication項目中,我們就可以只面對來自Service的Exception,不用去知道Dal或者Repo的細節。
    • 而來自IdentityService的異常,只有IdentityException一種(不算ArgumentNull那些基本Exception)
  • 隱藏並包裝下層的Exception,可以在每一層加入更多有用的信息
  • 如果是調用第三方類庫,盡量隱藏和包裝成自己的異常類型。比如你在Dal中調用MySqlConnector類庫,那么將MySqlException放到DalException的innerException中去。讓程序的其他部分不需要面對一個可能更換掉的外來者。

結語


以上寫的都比較具體,所以如果與大家的實際使用不同,請多多交流,共同進步,我也會補充到文章里來。


下一篇,我會講講關於 捕捉Exception的幾點實際經驗,比如全局異常處理以及異步編程中的異常。


謝謝閱讀。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM