本文鏈接: https://www.cnblogs.com/hubaijia/p/about-exceptions-1.html
系列文章:
關於exception的基本語法和作用,這里不再贅述,下面記錄一下我在項目中關於Exception的一些思考。
一,基礎文檔
基礎文檔是官方或者大神關於這一問題的講解,和闡述。本文建立在這些文檔基礎上,不再贅述,可以自行參考和記錄。
- 微軟的Exception的文檔
- 微軟的C#編程指南中的Exceptions and Exception Handling
- 微軟的Exception最佳做法
二,使用Exception,而不是Error Code
在初始設計項目的時候,有時候我們為了明確錯誤類型,定義如下結構:
enum ErrCode
{
Ok,
ArgumentErr,
OtherErr1,
OtherErr2
}
class SomeClass
{
public ErrCode DealSomething()
{
//.....
return ErrCode.Ok;
}
}
class CallerClass
{
public void CallSome(SomeClass some)
{
ErrCode err = some.DealSomething();
if(err == ErrCode.Ok)
{
//....
}
else
{
//.....
}
}
}
使用ErrCode並不是說完全不可以,比如在Web Api調用中返回ErrCode就是不錯的選擇。
1,吃掉異常
但是在其他情況,使用ErrCode會讓使用者痛苦不堪,因為每次調用都要小心謹慎的處理和判斷ErrCode,否則就特別容易吃掉異常,
對比如下程序片段:
使用ErrCode | 使用Exception |
---|---|
class SomeClass { public ErrCode DealSomething() { //..... return ErrCode.SomeErr; } } class CallerClass { public void CallSome(SomeClass some) { some.DealSomething(); giveMoney(); } } |
class SomeClass { public void DealSomething() { //..... if(somthingWrong) { throw new SomeException(...); } } } class CallerClass { public void CallSome(SomeClass some) { some.DealSomething(); giveMoney(); } } |
左側使用ErrCode因為不小心沒有接受處理ErrCode,導致后續代碼繼續執行;
而使用Exception,即使沒有處理,也不會執行后續代碼,並且拋出等待上層使用者處理。
此外,Exception還有其他眾多好處,比如Stack Trace信息,跨process等等。
所以Exception的本質就是現成的錯誤處理(error-handling)機制,就不要再去使用自定義的ErrCode了。
二,Exception在項目中的使用
既然明確使用Exception作為我們的錯誤處理方案。那么具體怎么使用呢?
1,使用細化的Exception
首先要避免以下寫法:
class Dal
{
public void Add(Entity entity)
{
//....
if(somthingWrong)
{
throw new Exception("add wrong");
}
//...
}
}
這種寫法的問題在於,調用者無法明確Exception的原因,類型,僅僅依靠文檔或者message去理解,當項目越積越多的時候,就是漫天相同的Exception亂飛,日志里充滿了各種神奇的message的時候。
我們可以采取多維度細化Exception:類型、錯誤種類,具體原因。
使用具體類型化的Exception,比如ConnectionException
, TimeoutException
, 即為每種錯誤定義一個Exception類型,
這種方法想必大家都很清楚,但是如果一個模塊里有十幾種錯誤,而你又有十幾個模塊呢?
對於這種情況有兩種處理方法。
-
按內部外部划分
如果一個異常需要模塊外部調用者接收和處理的,那么就定義一個具體的Exception類型,比如ConnectionException,TimeoutException;
如果一個異常是模塊內存自處理的,那么只用同一個InnerException(或者其他名字)加上其他信息來區分。(是不是又聞到了ErrCode的味道,沒錯)。
這種方法經常在很多開源類庫中看到。
-
一個模塊定義一種Exception類型,Exception與ErrCode相結合
剛說過ErrCode不能用,這里又提到,大家別誤會,仔細往下看。
比如如下代碼:
class ErrCode { public int Code { get; set; } public string? Name { get; set; } public string? Message { get; set; } } class DalException : Exception { public ErrCode Code { get; } public DalException(ErrCode errCode, Exception? innerException = null):base(errCode.Message, innerException) { Code = errCode; } }
ErrCode已經不簡簡單單是一個enum了,而是有更大的用處。
這樣每一個模塊都擁有自己Exception類型,每一種錯誤類型又能得到有效的分類。
這種方法經常在公司項目的業務模塊、基礎框架模塊里用到。
2,使用Exception.Data
上面的代碼段,大家是否有點驚訝,哥們兒,你把Message直接干到ErrCode里面固定起來,是不是有點激進啊。
其實這也是在實際開發中,總結出來的經驗。盡量不要放任程序員們零散的去寫各種Exception的Message。
但是我們可以用Exception.Data來實現更多場景。
比如下面代碼段:
static class ExceptionFactory
{
public static DalException OnMigrateError(int oldVersion, int newVersion, string sql, string cause)
{
DalException ex = new DalException(ErrCodes.MigrationErr);
ex.Data["OldVersion"] = oldVersion;
ex.Data["NewVersion"] = newVersion;
ex.Data["Cause"] = cause;
}
}
class SomeCls
{
void Migration()
{
//.....
throw ExceptionFactory.OnMigrationErr(oldVersion,)
}
}
這樣為每種錯誤固定了具體需要記錄的信息,其中cause就是讓程序員記錄當下的原因。當然了,你可以重寫OnMigrateError來記錄另外一種場景。
把異常Exception具體需要的信息,記錄在Data這個Dictionary中, 而不是寫到Message中的好處,是有助於今后進一步的處理,比如結構化日志。
當然這帶來了一個需要關注的問題,就是對Exception.Data的顯式處理,因為Exception.ToString()方法,並不打印Data。
在記錄日志方面,如果你使用 Serilog, 那么推薦你使用 Serilog.Exceptions, 他對Exception.Data十分友好。
至此,我們在細化Exception上,以此遞進的使用了 類型---> ErrorCode ---> 具體cause
3,集中化管理
漫天飛的Exception,不明所以的處理方案,都需要用規則來解決,但是規則需要時刻牢記,所以最靠譜的還是用代碼結構來解決。
首先建立ErrCodes
靜態類,然后建立ExceptionFactory
類,這樣業務代碼里拋出異常都具有throw ExceptionFactory.XXException()
這樣的形式。
//所有的錯誤代碼
internal static class ErrCodes
{
public static ErrCode MigrationErr { get; } = new ErrCode(1, nameof(MigrationErr), "Error happens in Migration.");
//........ other errors
}
//所有的Exception都由此生成
internal static class ExceptionFactory
{
public static DalException OnMigrateError(int oldVersion, int newVersion, string sql, string cause)
{
DalException ex = new DalException(ErrCodes.MigrationErr);
ex.Data["OldVersion"] = oldVersion;
ex.Data["NewVersion"] = newVersion;
ex.Data["Cause"] = cause;
}
//.....其他場景
}
這樣,code review時,只要看到有程序員在代碼中自己 new XXException
,那么就督促他在ExceptionFactory
里尋找合適的異常場景或者自己添加。
隨着項目積累,即使異常、錯誤種類眾多,大家也只需在ErrCodes
和ExcepionFactory
兩個類中,總結歸納,重構。
4,參考代碼
具體的代碼,上傳在 Github, 歡迎探討和指正。
三,預告
在下一篇,我將要探討下另外的一些實際項目問題。