本文鏈接: https://www.cnblogs.com/hubaijia/p/about-exceptions-2.html
系列文章:
本文目錄
與Exception相關的文件結構
在上一篇我們提到了 集中化管理 Exceptions,這樣所有的Exception的產生都在同一文件中,便於我們后期的Review、重構,不會看到漫天飛舞的throw new Exceptions(...)
,也盡量避免了同一種類的Exception擁有着不同的Message,記錄着不同的參數信息。
此外在上一篇中我們還提出了細化Exception的方式,即按照 類型、ErrorCode、Cause 以此細化。
舉例來說,項目中會形成如下的文件結構:(實例代碼請訪問 Github)
綠框里的就是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等錯誤.
- 如果項目開啟上面兩項功能,需要添加相應的Attribute。比如,上面代碼的
Exception的可見性
舉例來說,就是 DalException
要不要對Service可見,還是只對Repo可見?
其實可見或者不可見都沒有明顯的錯誤,但總的來說,
- 隱藏並包裝下層的Exception,對調用者更友好些。
- 比如在WebApplication項目中,我們就可以只面對來自Service的Exception,不用去知道Dal或者Repo的細節。
- 而來自IdentityService的異常,只有IdentityException一種(不算ArgumentNull那些基本Exception)
- 隱藏並包裝下層的Exception,可以在每一層加入更多有用的信息
- 如果是調用第三方類庫,盡量隱藏和包裝成自己的異常類型。比如你在Dal中調用MySqlConnector類庫,那么將
MySqlException
放到DalException的innerException中去。讓程序的其他部分不需要面對一個可能更換掉的外來者。
結語
以上寫的都比較具體,所以如果與大家的實際使用不同,請多多交流,共同進步,我也會補充到文章里來。
下一篇,我會講講關於 捕捉Exception的幾點實際經驗,比如全局異常處理以及異步編程中的異常。
謝謝閱讀。