軟體設計是怎樣煉成的(7)——細節決定成敗(詳細設計)

張傳波(Fireball)發表於2014-03-04

摘要:

當我們需要考慮類、類的內部細節、類之間的關係時,這時我們已經開始做詳細設計了。詳細設計不一定是一份文件,也不一定是Word文件,詳細設計也不一定叫“詳細設計”,有時候“編碼就是設計”也是未嘗不可的。對於MIS型別系統來說,架構設計和資料庫設計做好的前提下,詳細設計的難度其實是比較小的了,但MIS系統會有一些特殊的需求點,我們需要識別出來並想清楚應對辦法。如果你做的軟體是高技術含量的非MIS系統,情況將會更加複雜。

 

大綱:

1.什麼是優秀的設計?
2.優秀的設計能節省專案工作量
3.優秀設計從分析需求開始
4.軟體系統不是木桶型的
5.軟體設計的“大道理”
6.規劃系統骨架——架構設計
7.打造系統的底蘊——資料庫設計
8.細節決定成敗——詳細設計
9.使用者感覺好才是真的好——使用者體驗設計
10.持續提升設計水平

 

本文章是系列文章之一,如果你還沒有看過之前的文章,建議先看完前面的文章再看本篇,這樣效果更好。

 

8.細節決定成敗——詳細設計

 
 
8.1 什麼是詳細設計?
 
當我們需要考慮類、類的內部細節、類之間的關係時,這時我們已經開始做詳細設計了。詳細設計不一定是一份文件,也不一定是Word文件,詳細設計也不一定叫“詳細設計”,有時候“編碼就是設計”也是未嘗不可的。下面是我的一些最佳實踐:
 
實踐一:模組設計
我早期的一些專案會寫一份詳細設計文件,但後來的專案我會將詳細設計文件分拆為N份模組設計文件了,這樣做的兩大好處是:
1)一份詳細設計文件太大,不利於閱讀,不利於指導編碼工作,分拆後就好多了;
2)N個模組設計的任務可以分派給不同的軟體設計師(或程式設計師)來負責。
 
實踐二:程式碼就是設計
有時候我會“偷懶”,我覺得沒有必要再寫什麼設計文件,直接在開發工具中定義好類,寫好類的公開介面,寫好註釋等等,這時我其實就是在做詳細設計的工作,我將程式碼框架寫好後,才寫具體的實現程式碼。這種工作模式其實就是將詳細設計與編碼實現融合在一起了,效果和效率更好!當然不是說不再需要寫Word文件格式的詳細設計了,對應比較複雜的詳細設計,一般還是需要通過另外的文件來描述一下比較好。
但可能會有一種比較詳見的“特殊”情況:你可能會遇到開發人員死活寫不出Word文件格式的詳細設計,你和他溝通多次後,他還是寫不出有質量的Word文件格式的詳細設計,這時你不如讓他直接寫程式碼,先寫個框架看看,然後你通過評審程式碼來修正他的設計。
 
實踐三:Demo就是設計
設計邏輯複雜時,可能需要文件來應對,但文件畢竟是紙上談兵,可能最切實的辦法是做一個 Demo 實現你的演算法和設計思路。只要 Demo 是 Work 的,就可以將這個 Demo 的程式碼重用到實際的專案中。
舉一個例子:曾經某專案中需要寫程式碼解決判斷一個點是否在多邊形內,演算法有點麻煩,光寫文件說演算法沒有實質的價值,於是我用了半天時間寫了實現的程式碼和測試的程式碼,將這個Demo提交給專案組。
 
實踐四:“無”詳細設計
無詳細設計的意思不是真的不考慮詳細設計了,而是對於這種情況我們已經駕輕就熟了,幾乎是閉著眼睛都會做了,所以我們就“無”詳細設計直接編碼了。
 
 
8.2 詳細設計的基礎
 
我曾經評審過一份設計文件,該文件內容詳細、思路清晰,清楚描述了某些技術環節的實現辦法,而且實現辦法都是可行和有效的,這個文件可以算是一份比較好的詳細設計文件了。但可惜的是,整個專案只有這樣的一份設計文件,從全域性來看這個文件只解決了區域性問題,缺失了一些核心內容:
1)沒有架構設計的的內容;
2)沒有資料庫設計的內容;
3)系統的需求很複雜,大部分的需求沒有對應的設計考慮。
 
前面的文章曾提到,我做過的專案一般至少會有一份概要設計文件,詳細設計文件不一定是必須的。詳細設計固然重要,但針對整個系統的全面考慮更加重要,詳細設計之前應該具備以下條件:
1)應針對全部需求(包括功能性和非功能性的需求),系統需要有整體上的考慮,也就是前文提到的架構設計。
詳細設計需要考慮類、類的內部細節、類之間介面等,這些是需要符合系統的總體架構和分層架構的。
2)應有資料庫設計。
如果沒有資料庫設計,建築在資料庫之上的程式碼是很難寫的。當然如果你是用“由中間到上下”的設計方法的話(什麼是“由中間到上下”?請參考前面的文章),資料庫設計沒有,只要有中間層的建模的話,表現層和邏輯層的程式碼還是可以寫的,但資料庫操作層的程式碼還是依賴於資料庫設計的。
3)部分情況下,還應該有部分或全部的使用者體驗設計(使用者體驗設計下一篇會分享)。
使用者體驗設計主要考慮的是軟體的表現層,最能充分體驗“由頂而下”的設計思路,將會直接影響具體的程式碼實現。
 
一般情況下我們應該在架構設計和資料庫設計的基礎上進行詳細設計,否則很可能會讓我們僅僅關注了區域性的問題,而沒有抓住其他更加重要的問題和全域性的問題。如果沒有架構設計和資料庫設計,直接詳細設計是不是一定不可行呢?有以下的一些特殊情況(不限於此噢):
1)如果果你的情況是在原有系統上升級改造,系統原有的架構和資料庫設計基本不變,那麼直接進行詳細設計是合適的做法;
2)有時候有些區域性問題雖然很“區域性”但又相當特殊或重要,哪怕沒有來得及完成架構設計和資料庫設計,也可以先進行詳細設計的。
後文我們先從正常思路介紹詳細設計,也就是先有架構設計和資料庫設計再有詳細設計,然後再分享一些上面第2)點的情況。
 
 
8.3 詳細設計是架構設計的延續
 
前文的架構設計提到我們要對系統進行兩個層次的拆解,分別是:
第一層拆解:思考系統需要開發什麼軟體和資料庫等;
第二層拆解:考慮元件(Component)、程式碼包、某個分層等等,可能是“物理分拆”也可能是“邏輯分拆”。
不太記得或者看不懂的朋友,請先看看前面的文章啦。
而詳細設計其實就是:
第三層拆解:進一步細化出類、類對外介面、類的內部細節等。
 
通常我會用UML的順序圖(Sequence Diagram)來表達“第三層拆解”,請看一個簡單一點的圖,瞭解一下順序圖。
 
圖8.1 詳細設計-順序圖1
 
我們通過這個圖瞭解兩個事情:
1)順序圖的基本語法;
2)順序圖如何表達詳細設計。
 
這個圖表示的是使用者在某個查詢頁面輸入查詢內容、點選查詢按鈕等這些使用者互動及背後的程式設計。通常順序圖最左邊畫的是使用者,僅次之是軟體的表現層的某個頁面(介面),使用者與表現層的之間的互動,會導致表現層後面的類的一系列動作。這個圖還算比較簡單,請看下面這個我在N年前完成的某專案中的其中一個順序圖:
 
圖8.2 詳細設計-順序圖2
 
架構設計需要考慮全部需求後設計出來,也就是說”全部需“求驅動架構設計,當然某些特殊的需求點需要特別關照;資料庫設計主要是業務概念模型驅動的,業務建模及進一步提煉可以幫助我們設計出更有彈性的設計。那詳細設計是不是仍然需要”需求驅動“呢?這是必須的!
我們可能用用例(UseCase)、使用者故事(User Story)或者是功能點等方法表示需求,不管怎樣的表示辦法,最終都會拆解為一條條比較細的需求。每一條需求具體如何實現呢?順序圖就是表達這個實現方法的好工具!圖8.1 和 圖8.2 分別說明的都是某個需求點的實現方法,圖8.1 是查詢用例的詳細設計, 圖8.2 是修改材料裝置資訊的詳細設計。一個系統的詳細設計,就可以用類似圖8.1和圖8.2的圖逐一表示出來。
 
我們通過下圖再充分理解一下需求如何驅動詳細設計:
圖8.3 詳細設計-順序圖3
 
上圖紅色框框部分的內容是需求,使用者和系統介面之間的互動設計是對需求的進一步細化;藍色框框部分是在需求驅動下的程式實現,此圖實現部分比較簡單,大部分的程式實現邏輯都會比上圖複雜,會涉及到邏輯層、資料操作層還有一些共用模組之間的呼叫等等。詳細設計除了要需求驅動,同時也要需要符合架構設計,程式碼也需要基於已有的資料庫設計,換句話說就是詳細設計是需求、架構設計及資料庫設計三者同時驅動的。
 
本小節所列舉的詳細設計的例子都是MIS型別系統的例子,基本上圍繞資料庫的增刪改查進行,設計難度其實並不是很大。前面已經提到,如果對於已經很熟悉的情況,你沒有必要再用順序圖來畫一次了,直接可以”無”詳細設計;但如果你的團隊成員還不是很熟悉資料庫的增刪改查,或者實現邏輯比較複雜,這樣就很有必要進行詳細設計了。
 
本小節的例子比較“常規”,老鳥可能覺得沒啥難度,下小節的難度將會增加。
 
 
8.4 詳細設計是解決區域性問題的良方
 
前面提到,有些區域性問題雖然很“區域性”但又相當特殊或重要,哪怕沒有來得及完成架構設計和資料庫設計,也是需要先進行詳細設計的。
舉三個例子:
 
案例1:針對網路負載平衡的特殊考慮
某客戶的Web伺服器採用網路負載平衡,有兩臺Web伺服器,這與我們慣常的一臺Web伺服器場景很不一樣。我們打算使用公司的框架來開發這個系統,但這個框架的其中一個地方很可能會出問題。框架使用了靜態變數用來記錄資料庫中ID的最大值,當增加一條記錄時就 ID_Max = ID_Max + 1,將新的 ID_Max 作為新增加記錄的ID。這樣在兩臺Web服務的場景下,就會有兩個靜態的 ID_Max,兩邊都很可能會出現不準確的情況,導致資料插入到資料庫中時出錯。本身這個修改並不算複雜,但我們需要同時考慮相容框架,因為這個框架是同時支援 SQLServer 和 Oracle 資料庫的,我們的首席設計師很厲害,在框架層面解決了這個問題,不僅可以繼續保持框架的相容性,還擴大了框架的適應面。
 
案例2:點是否在多邊形內的求解
這個幾何問題是由業務問題轉化而已來的,這是一個某移動通訊公司的系統,先簡單介紹一下業務。
我們的手機是通過基站進行通訊的,如果附近沒有基站,就會出現手機沒有訊號的情況。我們所居住的城市當中,一般會有上百成千的基站,保證我們通訊暢通。基站與基站之間形成的通訊網路,會劃分為以基站為中心的多個“多邊形”,形成一個好像蜂窩的樣子,這就是我們經常聽說的蜂窩網路。但有可能會出現某個地方打電話有問題的情況,這個出問題的地“點”位於哪個“多邊形”呢,系統需要通過點的座標找到這個點在哪個多邊形範圍內,進一步定位到是哪個基站可能出問題。 
上述就是原始的業務背景,我們將這個需求演化為一個幾何問題,當時我參考了“放射線”的演算法,直接通過“Demo法”來完成這個設計。前文的”實踐3:Demo就是設計“中提到的例子,就是這個例子了!
 
案例3:讓程式支援 Undo 和 Redo
如果要求你的系統支援 Undo 和 Redo,不知道你會如何考慮呢?支援 Undo 和 Redo 是非常酷的,但難度是相當之高的,你會先完成架構設計和資料庫設計才考慮嗎?23設計模式中的命令模式可以幫助我們,但命令模式僅僅是給出瞭解決問題的框架而已,你還需要演化為更實際的內容,否則又犯了”放之四海而皆準“的毛病了。命令模式很有難度,但很有意思和很有用,我們這裡不詳解命令模式,大家可以參考我的設計模式方面的文章。
這裡舉這個例子是想說明:某些需求看上去好像是僅僅很小的一個點的要求,有可能影響面很大,你可能需要先針對這個點去思考詳細的實現辦法,然後才能幫助你想清楚架構設計及資料庫設計。
 
小結一下詳細設計解決區域性問題的兩種特殊情況:框架未確定之前的技術預研,案例1、3屬於這個情況;框架確定與否都不影響的區域性問題求解,案例2屬於這個情況。一般認為詳細設計是概要設計之後的,大部分情況確實如此,但通常我們進行概要設計之前還很可能需要針對某些需求點進行技術預研,這些技術預研是需要用詳細設計的程度來進行。
 
需要補充說明的是:某些技術難點的設計,通常僅僅靠順序圖是搞不定的,甚至不能用順序圖,我還會用到類圖、物件圖、活動圖和狀態機圖等等,類圖用到的機會最大,你看看23設計模式,設計思路基本上都是用類圖來表達的。UML圖僅僅是一種工具,前文提到的“實踐二:程式碼就是設計”和“實踐三:Demo就是設計”,對於難度高的詳細設計是經常需要用到這兩招的。
 
 
8.5 需要從詳細設計中提煉出需要全域性考慮的內容
 
前文提到”詳細設計是架構設計的延續”,其實還需要補充的是“詳細設計需要持續完善架構設計”。詳細設計過程中,我們會發現很多共性的內容,需要提煉為整個程式需要遵循的設計規範。下面是一些例子:
1)使用者體驗設計;(下一篇再詳細介紹)
2)輸入合法性判定;
3)批量資料的傳輸約定;
4)實體類的生命週期;
5)邏輯類的生命週期;
6)併發衝突的處理原則,包括判定辦法、提示辦法;
7)連線開啟、關閉原則;
8)採用事務的原則;
9)異常處理機制;
10)日誌記錄機制;
……
 
 
8.6 詳細設計小結
 
對於MIS型別系統來說,架構設計和資料庫設計做好的前提下,詳細設計的難度其實是比較小的了,你可以用順序圖來完成設計,注意做到需求驅動詳細設計,注意要滿足架構設計和資料庫設計。不過不少MIS會有一些特殊的需求點,我們需要識別出來並想清楚應對辦法,某些特殊的需求點還需要進行前期的技術預研。
如果你做的不是MIS型別的系統,而是設計難度很高的軟體,比方說技術含量很高的CAD軟體、人工智慧很牛逼的某些家用遊戲,要解決這些軟體的詳細設計,你需要設計模式、工程繪圖、數學、物理、人工智慧等很多知識支撐才能搞得掂了!
 
 

本文是系列文章的其中一篇,要做軟體設計師一點都不簡單啊,請留意後續文章!

 

如果本文對你有幫助,麻煩點一下“推薦”啦,謝謝!

 

 

作者:張傳波

創新工場創業課堂(敏捷課程)講師

軟體研發管理資深顧問

CMMI首席專家

《火球——UML大戰需求分析》作者

軟體知識原創基地創辦人

相關文章