思考方法的真正目的

走過的世界無論多遼闊,心中的思念 仍是相同的地方 - 《回家》html

就在那個山頂聽聽來天堂的聲音,就在那個村莊平息難以安靜的靈魂 - 《帶我到山頂》數據庫

在VB中,方法分爲函數和過程(Function/Sub),至關於C系語言有返回值方法和void方法。沿用單元測試的叫法,方法稱其爲單元。不少人會疑問,這個有什麼好說的呢?實際上很有一些研究價值,請看分析。編程

方法與面向對象

老外有篇文章,《When A Method Can Do Nothing》。不少人表示看不懂,若一個方法什麼也不作,要它幹嗎?其實若是細品文章中的一段話:c#

當你告訴一個對象作XX時,有時候它並不去作——發送執行XX的消息,意味着有時XX事件並不必定會發生。這要由這個對象決定。安全

若是說多態有任何意義,它至少在說對象是本身管理本身的。咱們給它發送一個消息,這是由它來決定應該去作什麼。這是面向對象的核心,也是 Alan Kay(設計了SmallTalk,得到過圖靈獎) 最初的對對象的認識之一——消息只是它們的信息傳遞。這種觀點在現在並不占主導地位。架構

在面向過程/函數語言中,天然一切都圍繞方法,每一個方法都有明確的職責,調用某個方法有明確的目的。app

到了面向對象編程,方法的業務邏輯則要服從對象的設計,方法的職責和目的要與服務於對象。這很天然,然而卻不易深入理解。框架

When A Method Can Do Nothing正是揭示了這種的改變,並且是面向對象的核心之一。若是文章標題改成 Don’t Expect A Method To Do Anything,或許更好理解。不要期望你調用的方法會作什麼,除了返回值。編程語言

脫離外部單元的依賴,外部單元,還有外部單元的外部單元,也須要單元測試,造成級聯,每一級均可以明確職責,實現隔離的測試。直至上溯到咱們項目代碼邊界,操做持久層或UI或框架代碼。函數

方法的業務邏輯不該該依賴於它調用的方法,或者說調用的其餘方法改變了什麼,方法的業務邏輯都能處理,這是真正面向對象的方法。這其實是SOA(面向服務)思想在方法級別的應用,方法間只有消息(參數)傳遞的鬆耦合。

面向對象方法很容易被擴展、測試、重構,是IoC的很好補充。通常用於API上,尤爲是UI和持久層。雖然這種方法只佔所有方法的一小部分,但對整個系統就是點晴之筆。

方法包含什麼

方法是最小粒度的模塊,全部業務處理步驟,均可以分爲四類,參見下圖:

 

一個方法存在的意義就在於最後的組裝,將前四部分編織起來,融入數以千計甚至萬計的組成一個複雜的業務邏輯網中。

目前C#等語言方法概念,仍然是沿襲面向過程語言。方法簽名只能單向規定被調用的方式,除非看源代碼,不然徹底不清楚其內部的運做方式。這就像一個黑盒子,雖然輸入輸出一目瞭然,但卻可能有許多看不見的線,與外部狀態關聯。

從低耦合角度,應該把方法儘可能設計成讓人放心的黑盒子,儘量避免對外部狀態的依賴,系統的測試性/維護性/擴展性都會大大提升。然而在整個系統中,必須有模塊要與外部狀態打交道,除非是一個理想化的模型。好的設計只是封裝得更好,限制得更少。即便一個方法看上去好像不操做全局上下文,可它一旦調用其餘單元,就可能間接改變外部狀態,致使意料以外的結果。

或許現實中真有那種建立時根本無須和外界打交道的對象(業務越複雜可能性越小)。多是我我的經驗不夠,還想象不到。

方法還與標準黑盒子有點不一樣,只須包含加工處理邏輯,並不必定要有輸出返回值或「產品」。用函數仍是過程,要根據對現實對象連繫,以及業務邏輯的抽象程度。反映了系統模塊間的連結方式,連結方式反映了業務邏輯的組織方式。何時用全局狀態,何時用函數返回值,這是模塊設計中必須不斷權衡的問題。

這裏的「產品」其實指編程語言能夠描述的對象,若是一個處理過程不出「產品」,並不表明沒有成果,好比修改全局狀態,或者後面提到的操做Database/UI,調用第三方API等等,這些事不能也沒必要描述爲「產品」對象。由於咱們的項目和代碼,是對現實世界的抽象,現實場景的一個角度和片段。只能儘量地模擬,而沒法徹底描述。

方法和業務邏輯

上面提到了方法中各類元素,只有內部計算和封裝每一個步驟的流程,是沒有反作用的,這是方法真正的業務邏輯。

業務邏輯自己就是個很是抽象的詞兒,咱們最常提到,也最習慣的業務邏輯概念,是從領域模型角度看的,對此阿彬同窗有一篇很是獨到而精僻闡述其本質。他指出,業務邏輯不是數據庫也不是UI,而是Domain(Business Logic) Layer的代碼。

對於這些業務邏輯,充分的單元測試代碼,既能說明需求,又能檢查邏輯,一舉多得,對於如今主流的敏捷開發更是不可或缺。

領域角度,相似於像數據庫三級模式中的概念模式。DBMS自己也是一個軟件系統,其內模式和外模式一樣能夠套用在通常系統上。

領域視角的系統概念層,對應領域模型的設計,代碼上直接反映在BL層。而領域的內模式,則將UI層/持久層/Service等,全部與底層和外部交互的架構層都包括其中。

從方法角度看,業務邏輯的概念能夠擴展到BL之外的層,這些架構層也有其自身的處理邏輯。但BL層邏輯仍然是核心,其餘層的業務邏輯都是服務於BL層服,只是手段而不是目的。這些層的模塊/對象/方法,都是能夠隔離,能夠模擬,能夠替代成不一樣框架實現的。

好比系統中日誌處理模塊,能夠將日誌保存到Server的Event Logger,也能夠存到數據庫,兩種方式雖然數據相同,但讀寫策略不一樣。 這些策略雖然不是標準的Business Logic,但能夠稱之爲擴展或廣義的業務邏輯。

BL層的方法和其餘層方法,結構上沒有特別之處。只有一種特殊狀況,除BL外的其餘層,都應該有一個邊界,邊界內是咱們的代碼,「外面的世界」是無限的第三方框架和服務,處於邊界的方法,是溝通咱們代碼,與底層框架/操做系統/第三方服務的橋樑。

image

應用程序的邊界方法會調用許多外部API,平臺服務的邊界方法會提供API供外部調用。

你們都知道,代碼邏輯中處理邊界的狀況要特別當心注意。對於邊界方法也是,要處理可靠性(好比網斷了)、安全性、性能方面的要求,這些一樣也能夠看做擴展的業務邏輯。採用優秀的框架,如WCF,每每能封裝並下降實現這些要求的代碼負擔。

方法的測試與擴展

通常而言,代碼越容易被測試,也很容易被修改,也越健壯。總之軟件各項標準很大程序上都是統一的。模塊/類/方法,各個粒度的代碼都適用。

過去的DB和UI框架,不少難以封裝抽象,如Asp.Net的控件綁定。現在隨着MVC、MVVM、Respostory架構的推廣,狀況已經大爲改觀,出現了許多Mock框架。將領域業務和實現業務隔離,避免常常邊界操做致使性能時間損失,更有助於推廣Unit Test。

現代每一個平臺,每一個團隊都提倡,敏捷開發更離不開單元測試,但是爲何你們仍是時常很迷茫,無從下手呢。根本緣由在於:單元測試驗證機制徹底靠手工,沒法準確反映單元(方法)間的聯繫和約束,並且隨業務邏輯變化須要維護。

舉個例子,有下面一個ER圖,也可做爲領域模型,描述學生/講師/課程間的關係。

image

假設有個BL層方法,計算學生選的課,生成一個課程表,方法會調用DAL層返回學生和課程信息。如今需求提出要課程表要加上講師信息,DAL層採用了EntityFramework,因此講師信息做爲課程的外鍵屬性,不會自動加載。很多人包括我,常常會忘掉加Include方法。這種狀況下單元測試若是不更新,增長講師Null斷言,也就沒法檢測出這種邏輯錯誤。

將來的編程語言或框架,必需要能支持檢測這些約束。還有個問題是,對於課程的講師信息,其實應該是非空外鍵屬性,只是因爲性能考慮,對現實抽象妥協而留空。這種狀況的Null很容易與真正業務上的Null混淆。

Spec#語言是C#擴展,提供了方法約束和不可空對象的支持。看下一個求平方根的方法:

int ISqrt(int x){
requires 0 <= x; //參數非負
ensures         //檢查結果偏差
    result*result <= x && x < (result+1)*(result+1);    
{
    int r = 0;
    while ((r+1)*(r+1) <= x)
        invariant r*r <= x;
    {
        r++;
    }
    return r;
}

 

多是語法有點繁瑣,Spec#沒流行起來,但這個探索應該是編程語言將來的方向。而非空對象的特性,我以爲面嚮對象語言都應該引入,這會使《WhenA Method Can Do Nothing》提到的Null-Object模式變得流行起來,將帶來更接近真實,更容易理解的業務邏輯。

理想的完美系統,每一個方法職責絕不含糊,每一個方法都清晰可測的,方法的外部依賴都應該封裝成接口或其餘形式的抽象。對一個單元測試,除了BL層那些不會產生反作用的方法,應該Mock全部外部調用。再單獨對每一個外部調用進行單元測試。由於一個API可能被多個業務邏輯調用,而只須要一次就能夠驗證其正確性。

預計單元測試將來的發展方向,應該也分紅不一樣的架構層,會和項目業務部分同樣重要的地位。

 

胡扯了很多,該結尾了。其實本文的大部分東西,都是寫以前未想到的。能思考出這些東西,仍是很開心的。本文和以前一篇框架和應用的文章,做爲下一篇隨筆的鋪墊。最後祝全部人新春快樂,馬到成功!

相關文章
相關標籤/搜索