一堂如何提高程式碼質量的培訓課(三)

醉面韋陀發表於2010-01-11

3 )職責驅動設計和領域驅動設計

前 面我提到,當我們嘗試寫一些複雜功能的時候,我們把功能分解成一個個相對獨立的函式。但是,應當將這些函式分配到哪個類中呢?也就是系統中的所有類都應當 擁有哪些函式呢?或者說應當表現出哪些行為呢?答案就在這裡:以職責為中心,根據職責分配行為。我們在分析系統時,首先是根據客戶需求進行用例分析,然後 根據用例繪製領域模式和分析模型,整個系統最主要的類就形成了。通過以上分析形成的類,往往和現實世界的物件是對應的。正因為如此,軟體世界的這些類也具 有了與現實世界的物件相對應的職責,以及在這些職責範圍內的行為。

職責驅動設計( Responsibility Drive Design RDD )是 Craig Larman 在他的經典著作《 UML 和 模式應用》中提出的。職責驅動設計的核心思想,就是我們在對一個系統進行分析設計的時候,應當以職責為中心,根據職責分配行為。這種思想首先要求我們設計 的所有軟體世界的物件,應當與現實世界儘量保持一致,他稱之為“低表示差異”。有了低表示差異,一方面提高了程式碼的可讀性,另一方面,當業務發生變更的時 候,也可以根據實際情況快速應對變更。

Craig Larman 在提出職責驅動設計理論的同時,還提出了 GRASP 設計模式,來豐富這個理論。在 GRASP 設計模式中,我認為,低耦合、高內聚、資訊專家模式最有用(以後再詳細講述)。

Craig Larman 提出的職責驅動設計數年之後,另一位大師提出了領域驅動設計。領域驅動設計( Domain Drive Design DDD )是 Eric Evans 在 他的同名著作《領域驅動設計》中提出的。在之前的設計理論中,領域模型是從用例模型到分析模型之間的一種中間模型,也就是從需求分析到軟體開發之間的一種 中間模型。這麼一箇中間模型,既不是需求階段的重要產物,在開發階段也不以它作為標準進行開發,僅僅是作為參考,甚至給人感覺有一些多餘。但是, Evans 在 領域驅動設計中,將它放到了一個無比重要的位置。按照領域驅動設計的理論,在需求分析階段,需求分析人員使用領域模型與客戶進行溝通;在設計開發階段,開 發人員使用領域模型指導設計開發;在執行維護和二次開發階段,維護和二次開發人員使用領域模型理解和熟悉系統,並指導他們進行維護和二次開發。總之,在整 個軟體開發的生命週期中,領域模型都成為了最核心的內容。

領 域驅動設計繼承了職責驅動設計。在領域驅動設計中強調的,依然是低表示差異,以及職責的分配。但是,如何做到低表示差異呢?如何完成職責分配呢?領域驅動 設計給了我們完美的答案,那就是建立領域模型。領域驅動設計改變了我們的設計方式。在需求分析階段,用例模型已不再是這個階段的核心,而是建立領域模型。 在開發和二次開發階段,開發人員也不再是一埋頭地猛扎程式序堆裡開始程式設計,而是首先細緻地進行領域模型分析。領域驅動設計強調持續精化,使領域模型不再是 一旦完成分析就扔在一邊不再理會的圖紙,而是在不斷理解業務的基礎上不斷修改和精化領域模型,進而驅動我們程式碼的精化。領域驅動設計強調的不再是一次軟體 開發過程中我們要做的工作,它看得更加長遠,它強調的是一套軟體在相當長一段時間內持續升級的過程中我們應當做的工作。我認為,領域驅動設計是提高程式碼質 量的最高等級。當時,使用領域驅動設計進行軟體開發是一場相當巨大的改革,它顛覆了我們過去的所有開發模式,我們必須腳踏實地地一步一步去實踐和改變。

 

職責驅動設計

隨 著軟體業的不斷髮展,隨著軟體需求的不斷擴大,軟體所管理的範圍也在不斷拓寬。過去一個軟體僅僅管理一臺電腦的一個小小的功能,而現在被擴充套件到了一個企 業、一個行業、一個產業鏈。過去我們開發一套軟體,只有少量的二次開發,當它使用到一定時候我們就拋棄掉重新又開發一套。現在,隨著使用者對軟體依賴程度的 不斷加大,我們很難說拋棄一套軟體重新開發了,更多的是在一套軟體中持續改進,使這套軟體的生命週期持續數年以及數個版本。正是因為軟體業面臨著如此巨大 的壓力,我們的程式碼質量,我們開發的軟體擁有的可變更性和持續改進的能力,成為軟體制勝的關鍵因素,令我們不能不反思。

 

程式碼質量評價的關鍵指標:低耦合,高內聚

耦合就是對某元素與其它元素之間的連線、感知和依賴的量度。耦合包括:

1 .元素 B 是元素 A 的屬性,或者元素 A 引用了元素 B 的例項(這包括元素 A 呼叫的某個方法,其引數中包含元素 B )。

2 .元素 A 呼叫了元素 B 的方法。

3 .元素 A 直接或間接成為元素 B 的子類。

4 .元素 A 是介面 B 的實現。

如果一個元素過於依賴其它元素,一旦它所依賴的元素不存在,或者發生變更,則該元素將不能再正常執行,或者不得不相應地進行變更。因此,耦合將大大影響程式碼的通用性和可變更性。

內 聚,更為專業的說法叫功能內聚,是對軟體系統中元素職責相關性和集中度的度量。如果元素具有高度相關的職責,除了這些職責內的任務,沒有其它過多的工作, 那麼該元素就具有高內聚性,反之則為低內聚性。內聚就像一個專橫的管理者,它只做自己職責範圍內的事,而將其它與它相關的事情,分配給別人去做。

高質量的程式碼要求我們的程式碼保持低耦合、高內聚。但是,這個要求是如此的抽象與模糊,如何才能做到這些呢?軟體大師們告訴我們了許多方法,其中之一就是 Craig Larman 的職責驅動設計。

職責驅動設計 Responsibility Drive Design RDD Craig Larman 在他的經典著作《 UML 和模式應用》中提出的。要理解職責驅動設計,我們首先要理解 低表示差異

 

低表示差異

我 們開發的應用軟體實際上是對現實世界的模擬,因此,軟體世界與現實世界存在著必然的聯絡。當我們在進行需求分析的時候,需求分析員實際上是從客戶那裡在了 解現實世界事物的規則、工作的流程。如果我們在軟體分析和設計的過程中,將軟體世界與現實世界緊密地聯絡到一起,我們的軟體將更加本色地還原事物最本質的 規律。這樣的設計,就稱之為“低表示差異”。

 



 

採用“低表示差異”進行軟體設計,現實世界有什麼事物,就對映為軟體世界的各種物件(類);現實世界的事物擁有什麼樣的職責,在軟體世界裡的物件就擁有什麼樣的職責;在現實世界中的事物,因為它的職責而產生的行為,在軟體世界中就反映為物件所擁有的函式。

低表示差異,使分析設計者對軟體的分析和設計更加簡單,思路更加清晰;使程式碼更加可讀,閱讀者更加易於理解;更重要的是,當需求發生變更,或者業務產生擴充套件時,設計者只需要遵循事物本來的面貌去思考和修改軟體,使軟體更加易於變更和擴充套件。

 

 

角色、職責、協作

理解了“低表示差異”,現在我們來看看我們應當如何運用職責驅動設計進行分析和設計。首先,我們通過與客戶的溝通和對業務需求的瞭解,從中提取出現實世界中的關鍵事物以及相互之間的關係。這個過程我們通常通過建立領域模型來完成。領域模型建立起來以後,通過諸如 Rational Rose 這 樣的設計軟體的正向工程,生成了我們在軟體系統中最初始的軟體類。這些軟體類,由於每個都扮演著現實世界中的一個具體的角色,因而賦予了各自的職責。前面 我已經提到,如果你的系統採用職責驅動設計的思想進行設計開發,作為一個好的習慣,你應當在每一個軟體類的註釋首行,清楚地描述該軟體類的職責。

當 我們完成了系統中軟體類的制訂,分配好了各自的職責,我們就應該開始根據軟體需求,編寫各個軟體類的功能。在前面我給大家提出了一個建議,就是不要在一個 函式中編寫大段的程式碼。編寫大段的程式碼,通常會降低程式碼的內聚度,因為這些程式碼中將包含不是該軟體類應當完成的工作。作為一個有經驗的開發人員,在編寫一 個功能時,首先應當對功能進行分解。一段稍微複雜的功能,通常都可以被分解成一個個相對獨立的步驟。步驟與步驟之間存在著互動,那就是資料的輸入輸出。通 過以上的分解,每一個步驟將形成一個獨立的函式,並且使用一個可以表明這個步驟意圖的釋義函式名。接下來,我們應當考慮的,就是應當將這些函式交給誰。它 們有可能交給原軟體類,也有可能交給其它軟體類,其分配的原則是什麼呢?答案是否清楚,那就是職責。每個軟體類代表現實世界的一個事物,或者說一個角色。 在現實世界中這個任務應當由誰來完成,那麼在軟體世界中,這個函式就應當分配給相應的那個軟體類。

通 過以上步驟的分解,一個功能就分配給了多個軟體類,相互協作地完成這個功能。這樣的分析和設計,其程式碼一定是高內聚的和高可讀性的。同時,當需求發生變更 的時候,設計者通過對現實世界的理解,可以非常輕鬆地找到那個需要修改的軟體類,而不會影響其它類,因而也就變得易維護、易變更和低耦合了。

說了這麼多,舉一個例項也許更能幫助理解。拿一個員工工資系統來說吧。當人力資源在發放一個月工資的時候,以及離職的員工肯定不能再發放工資了。在系統設計的期初,開發人員商量好,在員工資訊中設定一個“離職標誌”欄位。編寫工資發放的開發人員通過查詢,將“離職標誌”為 false 的 員工查詢出來,併為他們計算和發放工資。但是,隨著這個系統的不斷使用,編寫員工管理的開發人員發現,“離職標誌”欄位已經不能滿足客戶的需求,因而將 “離職標誌”欄位廢棄,並增加了一個“離職時間”欄位來管理離職的員工。然而,編寫工資發放的開發人員並不知道這樣的變更,依然使用著“離職標誌”欄位。 顯然,這樣的結果就是,軟體系統開始對離職員工發放工資了。仔細分析這個問題的原因,我們不難發現,確認員工是否離職,並不是“發放工資”軟體類應當完成 的工作,而應當是“員工管理”軟體類應當完成的。如果將“獲取非離職員工”的任務交給“員工管理”軟體類,而“發放工資”軟體類僅僅只是去呼叫,那麼離職 功能由“離職標誌”欄位改為了“離職時間”欄位,其實就與“發放工資”軟體類毫無關係。而作為“員工管理”的開發人員,一旦發生這樣的變更,他當然知道去 修改自己相應的“獲取非離職員工”函式,這樣就不會發生以上問題。通過這樣一個例項,也許你能夠理解“職責驅動設計”的精要與作用了吧。

 

職責分配與資訊專家

通過以上對職責驅動設計的講述,我們不難發現,職責驅動設計的精要就是職責分配。但是,在紛繁複雜的軟體設計中,如何進行職責分配常常令我們迷惑。幸運的是, Larman 大師清楚地認識到了這一點。在他的著作中,資訊專家模式為我們提供了幫助。

資訊專家模式(又稱為專家模式)告訴我們,在分析設計中,應當將職責分配給軟體系統中的這樣一個軟體類,它擁有實現這個職責所必須的資訊。我們稱這個軟體類,叫“資訊專家”。用更加簡短的話說,就是將職責分配給資訊專家。

為 什麼我們要將職責分配給資訊專家呢?我們用上面的例子來說明吧。當“發放工資”軟體類需要獲取非離職員工時,“員工管理”軟體類就是“獲取非離職員工”任 務的資訊專家,因為它掌握著所有員工的資訊。假設我們不將“獲取非離職員工”的任務交給“員工管理”軟體類,而是另一個軟體類 X ,那麼,為了獲取員工資訊,軟體類 X 不得不訪問“員工管理”軟體類,從而使“發放工資”與 X 耦合, X 又與“員工管理”耦合。這樣的設計,不如直接將“獲取非離職員工”的任務交給“員工管理”軟體類,使得“發放工資”僅僅與“員工管理”耦合,從而有效地降低了系統的整體耦合度。

 

總之,採用“職責驅動設計”的思路,為我們提高軟體開發質量、可讀性、可維護性,以及保持軟體的持續發展,提供了一個廣闊的空間。

相關文章