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


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

系列文章:

關於exception的基本語法和作用,這里不再贅述,下面記錄一下我在項目中關於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里尋找合適的異常場景或者自己添加。

隨着項目積累,即使異常、錯誤種類眾多,大家也只需在ErrCodesExcepionFactory兩個類中,總結歸納,重構。

4,參考代碼

具體的代碼,上傳在 Github, 歡迎探討和指正。

三,預告

在下一篇,我將要探討下另外的一些實際項目問題。


免責聲明!

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



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