我自己的理想軟體結構之基本觀念只有一句話:靜態物件與動態方法全部都要一一對應。這句話說起來很簡單,但是要在系統設計時落實時勢必要多費不少時間。
我所用的作法是在2007/11的專案進行時領悟出來的,當時我必須留下系統架構的空殼給三位工作不滿兩年的工程師,讓他們逐一把Use Case的實作填進系統。如果依照以前自由堆疊的寫作風格,等拿到手裡時鐵定是無法動彈的僵化結構,因此我擬定的策略是努力把系統結構鋪到所有可能會放置程式的地方。
現今留存下來的系統是四個人花費兩個多月完成的,然而當初我費盡心力建設起來的軟體結構卻沒被他們作過任何調整──除了一個當初我自己偷懶將應分成五個對應卻合成一個的Module之外;他們都覺得一開始的系統架構已經滿足之後製作功能的全部需要。所以這個設計方式我將會一直沿用下去。
在一一對應物件與方法時,我同時兼顧了一個目的:在設計結構上進行更細部的設計、實作與重構時,如果實行的內容只有Method的垂直或水平的抽取與移動的話, 就是理想的架構。垂直的定義是:方法往父類別(Generalize)或子類別(Specialize)移動,或將相同的程式區段另抽出Method重覆使用;水平的定義是執行方法的責任往呼叫自己或自己呼叫的方向(Delegation)移動,或者是合併或拆解Method。
2008年2月18日 星期一
2008年2月17日 星期日
L01 架構設計像鋪路, 涵蓋越廣就讓後人越方便
系統的設計是把一個一個的Use Case與其中的Activitiy依照順序放置到軟體的結構裡,並利用程式的邏輯把它們串連起來以達成完整的功能。對一個Use Case來說,只要按照規則執行過所有的Activity都可以做到,但是要如何放置Activity卻也是需要設計的。
Activity不管怎麼放都能達成功能,但是能輕易地reuse或是快速配合需求的變更卻不是容易的事。我們可以回想自己寫過的程式裡,有過多少次對於介面、物件與方法的合併、拆解與抽取呢?有時候以為某個方法的作法應該不會再有變動而固定下來,卻在不久的將來發現某個問題或是變更需要將那個作法一分為多而硬生生地重新拆解內容;拆解的同時又會發現許多原本也使用這個作法的其他功能又得再想過一次,為了符合新作法的幾個變更結果又造成更多地方需要調整……。
就自己而言類似的問題時常遇到,有時剛寫好的程式在回想時就會隱隱感覺遇到某些情況時將無法應付,修改後也沒法解決全部而以功能可以正常就好來安慰自己。設計的彈性是在維持功能正常前提下同樣必須具備的,把所顧慮到的可能變更納入系統的設計是有能力者應盡的責任, 不做的話其他更沒有經驗的人是完全沒有辦法做出來的。
Activity不管怎麼放都能達成功能,但是能輕易地reuse或是快速配合需求的變更卻不是容易的事。我們可以回想自己寫過的程式裡,有過多少次對於介面、物件與方法的合併、拆解與抽取呢?有時候以為某個方法的作法應該不會再有變動而固定下來,卻在不久的將來發現某個問題或是變更需要將那個作法一分為多而硬生生地重新拆解內容;拆解的同時又會發現許多原本也使用這個作法的其他功能又得再想過一次,為了符合新作法的幾個變更結果又造成更多地方需要調整……。
就自己而言類似的問題時常遇到,有時剛寫好的程式在回想時就會隱隱感覺遇到某些情況時將無法應付,修改後也沒法解決全部而以功能可以正常就好來安慰自己。設計的彈性是在維持功能正常前提下同樣必須具備的,把所顧慮到的可能變更納入系統的設計是有能力者應盡的責任, 不做的話其他更沒有經驗的人是完全沒有辦法做出來的。
2008年2月16日 星期六
K16 令人感到麻煩的設計(9)──把固定數據提出成Constant
在程式寫作時常會有一個要求:要求把所有放置固定值的地方都另外使用常數來放置。對於寫作的人來說這是感到麻煩的地方,因為明明可以直接放置固定不變的值卻變得必須額外指定一個常數,無形中浪費了一點時間,而且有時無法快速找到定義的內容。
很多額外多做的行為都是為了一個目標──把相同意義的東西抽出到同一個地方。異中求同是在這裡是必須具備的能力,大至從Use Case裡把相同子劇本提出的sub Use Case、佈置系統架構的Module、為完成特定功能的Component、Class特性的分析與定義、決定完成指定功能的Method、直到將使用的Constant拉出到通用的變數,每一個提取動作的目的都是為了“完整的reuse”。
在遇到第一個可能會被reuse的部分時,會有人因為想少做點事而沒作提取的動作,但在接連遇到同樣的情況出現後,還是無法避免去做;而且等到後面才做時,必須得找出之前沒有提出的地方做出少許破壞程式改用共同部分的舉動,感覺上像是把之前沒做的事堆在後頭一次做完,而且可能會有漏失的風險。
所以我的作法是:該做的地方就在遇到的時候處理掉,即使它可能只出現一次。
很多額外多做的行為都是為了一個目標──把相同意義的東西抽出到同一個地方。異中求同是在這裡是必須具備的能力,大至從Use Case裡把相同子劇本提出的sub Use Case、佈置系統架構的Module、為完成特定功能的Component、Class特性的分析與定義、決定完成指定功能的Method、直到將使用的Constant拉出到通用的變數,每一個提取動作的目的都是為了“完整的reuse”。
在遇到第一個可能會被reuse的部分時,會有人因為想少做點事而沒作提取的動作,但在接連遇到同樣的情況出現後,還是無法避免去做;而且等到後面才做時,必須得找出之前沒有提出的地方做出少許破壞程式改用共同部分的舉動,感覺上像是把之前沒做的事堆在後頭一次做完,而且可能會有漏失的風險。
所以我的作法是:該做的地方就在遇到的時候處理掉,即使它可能只出現一次。
2008年2月15日 星期五
K15 令人感到麻煩的設計(8)──每個設計項目有各自的Data Model Interface
曾經接觸過一個系統,裡面的Data Model實作是一個巨大的Map,它提供了兩個最單純的存取方法:getData(String name)與setData(String name, Object value)。系統裡所有需要存取Data Model的地方只都經過這兩個方法。
這在存取的時候既方便又好學,作出來的功能也不會有什麼問題,但麻煩來自於後面的一個需要:有個專案有其中一個模組的需求,詢問是否能夠reuse我們設計好的模組。我回答不容易,因為這個Data Model沒有設計Interface所以使用的模組只能用它,而且沒有根據Module來定義它所能存取的Data,變成必須要去追所有程式碼才能找出用到哪些名稱的Data。最後當然沒人要去作這種追溯而情願重新寫一個。
這就是沒有Interface並加上多合一設計的糾結。沒有定義介面變成使用時一定要依賴該Class、沒有定義該Module專用的Interface就難以快速得知它使用的資料範圍、入口的合併造成不容易分辨執行時要存取什麼資料;Data Model像是無形的鎖鏈, 將明明分開設計的Module綁死在一起。這使得要記得Interface是一個規格,定義時以能夠只觀看Interface就能明白它提供了什麼樣的功能為目標。
這在存取的時候既方便又好學,作出來的功能也不會有什麼問題,但麻煩來自於後面的一個需要:有個專案有其中一個模組的需求,詢問是否能夠reuse我們設計好的模組。我回答不容易,因為這個Data Model沒有設計Interface所以使用的模組只能用它,而且沒有根據Module來定義它所能存取的Data,變成必須要去追所有程式碼才能找出用到哪些名稱的Data。最後當然沒人要去作這種追溯而情願重新寫一個。
這就是沒有Interface並加上多合一設計的糾結。沒有定義介面變成使用時一定要依賴該Class、沒有定義該Module專用的Interface就難以快速得知它使用的資料範圍、入口的合併造成不容易分辨執行時要存取什麼資料;Data Model像是無形的鎖鏈, 將明明分開設計的Module綁死在一起。這使得要記得Interface是一個規格,定義時以能夠只觀看Interface就能明白它提供了什麼樣的功能為目標。
2008年2月14日 星期四
K14 系統的製程(7)──Data Model Interface
需求階段的後半段要建立系統使用的Data Model,這部分可以使用CRC方法得到的定義,根據內容定義Data Model Interface與對應的Method。這個部份的設計可以使用Class Diagram忠實地呈現CRC Card裡的責任與合作。
在設計Data Model時並不刻意描述儲存的媒體是什麼,期望的是不管存放在哪裡都會在使用時變成Data Model物件。應用資料庫正規化的方法來設計將會使Data Model的佈置更為理想,養成這個習慣會讓自己的設計在更多的地方符合通用的標準。
如果熟悉的是ER Model(Entity-Relationship)的作法當然可以應用,先設計好所有Entity與Relationship後再依照結果建立Data Model Interface。設計好ER Model後再使用DFD(Data Flow Diagram)設計同樣可以完成系統功能,因為程式存在的目的都是為了處理資料。
要記得在這裡定義的Data Model Interface是配合系統動作定義的,要使用具有Domain意義的Method名稱;例如取得使用者編號是getUserId()、設定使用者名稱是setUserName()之類的,即使已經知道未來全都是用setProperty()來存放資料。因為使用getProperty()是把多個系統對Data Model的操作合併到一個方法裡做,在這種多對一的對應關係下,想進一步地在系統裡分析出多個中的一個之關聯勢必要再參照參數實際值而無法只看Method Name就有答案,這將會埋下未來難以處理的盲點。
在設計Data Model時並不刻意描述儲存的媒體是什麼,期望的是不管存放在哪裡都會在使用時變成Data Model物件。應用資料庫正規化的方法來設計將會使Data Model的佈置更為理想,養成這個習慣會讓自己的設計在更多的地方符合通用的標準。
如果熟悉的是ER Model(Entity-Relationship)的作法當然可以應用,先設計好所有Entity與Relationship後再依照結果建立Data Model Interface。設計好ER Model後再使用DFD(Data Flow Diagram)設計同樣可以完成系統功能,因為程式存在的目的都是為了處理資料。
要記得在這裡定義的Data Model Interface是配合系統動作定義的,要使用具有Domain意義的Method名稱;例如取得使用者編號是getUserId()、設定使用者名稱是setUserName()之類的,即使已經知道未來全都是用setProperty()來存放資料。因為使用getProperty()是把多個系統對Data Model的操作合併到一個方法裡做,在這種多對一的對應關係下,想進一步地在系統裡分析出多個中的一個之關聯勢必要再參照參數實際值而無法只看Method Name就有答案,這將會埋下未來難以處理的盲點。
2008年2月13日 星期三
K13 令人感到麻煩的設計(7)──入口、流程與動作
Use Case與Activity的製作其實就是最high level流程與動作的分離,藉由將Activity定義為Interface Method將之固定為完成特定動作的目的,此時的Use Case可以被限制到只是用流程串連必要Activity的規則。
當然我們可以把Use Case用一個Class甚至是一個方法來完成,在功能上所有作法得到的結果都是一樣的,但是把多個動作組合在一個方法裡所付出的代價就是它被限制住只能有組合後的結果。雖然加上參數後可以應付多種不同的需要改變內部,但是當出現要增減動作的需要時就會發現改動不僅不容易而且還要考慮很多相容的問題。這是把流程與動作合併在同樣方法的問題。
雖然重點在於流程與動作的分工,但是入口卻是一個容易被忽略的地方。即使在很多地方很明白地表示進入後就是要達成某件事,不過仍有可能會在進入後參考某些設定而改變要做的事;而有時會只需要在其他地方處理同樣的功能,倘使把流程跟入口綁在一起又會形成再加工切割的後果。我們必須記得的是:預留好事後切割的空間會比重構結構來得容易而且少很多風險。
在每一個特定功能的設計裡,我的作法都會是:使用入口(有時的確不需要存在)、流程與動作三個層次合作完成指定的功能。(D06的圖也是類似的概念)
當然我們可以把Use Case用一個Class甚至是一個方法來完成,在功能上所有作法得到的結果都是一樣的,但是把多個動作組合在一個方法裡所付出的代價就是它被限制住只能有組合後的結果。雖然加上參數後可以應付多種不同的需要改變內部,但是當出現要增減動作的需要時就會發現改動不僅不容易而且還要考慮很多相容的問題。這是把流程與動作合併在同樣方法的問題。
雖然重點在於流程與動作的分工,但是入口卻是一個容易被忽略的地方。即使在很多地方很明白地表示進入後就是要達成某件事,不過仍有可能會在進入後參考某些設定而改變要做的事;而有時會只需要在其他地方處理同樣的功能,倘使把流程跟入口綁在一起又會形成再加工切割的後果。我們必須記得的是:預留好事後切割的空間會比重構結構來得容易而且少很多風險。
在每一個特定功能的設計裡,我的作法都會是:使用入口(有時的確不需要存在)、流程與動作三個層次合作完成指定的功能。(D06的圖也是類似的概念)
2008年2月12日 星期二
K12 替代的作法(3)──定義Use Case與Activity的Interface Method
在Activity List所需要的資訊,當然會有它自己的Name、Description以及對應的Method Name、所屬的Interface。雖然在製作Activity Diagram的同時,Activity至少都會被歸屬到一個Use Case下,但我認為Activity會與多個Use Case對應,所以其間的關係並不是絕對的而不需多加記錄;但是要記錄屬於哪一個Subsystem。

在Use Case的這一頁,基本上還是記錄Use Case Name、Description與所屬的Subsystem,再接下來所要記錄的是每個Use Case的進入點、起始狀態與結束狀態;安排這幾個欄位的目的是希望簡短地記錄一些SRS裡的資訊,以便快速地參考必須的資料。如果有Client與Server多個層次時,每個層次都要定義一組欄位來記錄,因為這裡註明的是Activity Digram裡跨越swimlane動作的資訊。
此時沒有好的方法在Excel裡描述Use Case裡的執行流程,因此暫時不在此記錄而放在其他地方,這會等到後面適當的步驟時說明。
2008年2月11日 星期一
K11 系統的製程(6)──定義Use Case Interface Method
Activity Interface Method是系統功能的入口,而Use Case是使用者操作的入口,對於系統而言這就像是把系統功能再用客製化的邏輯包裝而形成更上一層的應用。在此同樣也需要定義Use Case Interface Method。
與定義Activity Interface Method類似,在這裡需要決定每一個Use Case對應的方法,而且同樣必須每個Use Case使用自己的Method Name。不過還有一點需要特別留意:在Activity Diagram裡如果使用swimlane將系統切割為Client、Server等多個層次時,必須每個層次都為這個Use Case定義方法,這是未來藉由硬體橋樑設計時對另一端的操作定義。
在這裡我們會得到Use Case與Activity的關聯,我們可以發現每個Use Case會使用至少一個Activity,每個Activity也會被至少一個Use Case所使用;這在系統裡會對應真正執行時程式的呼叫關係。此時只需定義到Interface與Method,因為暫時以功能定義作為目標,Package要等到架構設計將之佈置妥當後再安放Interface。
與定義Activity Interface Method類似,在這裡需要決定每一個Use Case對應的方法,而且同樣必須每個Use Case使用自己的Method Name。不過還有一點需要特別留意:在Activity Diagram裡如果使用swimlane將系統切割為Client、Server等多個層次時,必須每個層次都為這個Use Case定義方法,這是未來藉由硬體橋樑設計時對另一端的操作定義。
在這裡我們會得到Use Case與Activity的關聯,我們可以發現每個Use Case會使用至少一個Activity,每個Activity也會被至少一個Use Case所使用;這在系統裡會對應真正執行時程式的呼叫關係。此時只需定義到Interface與Method,因為暫時以功能定義作為目標,Package要等到架構設計將之佈置妥當後再安放Interface。
2008年2月10日 星期日
K10 系統的製程(5)──定義Activity Interface Method
在製作Use Case時努力所製作的Activity Diagram其實是為了一個重要的目的──收集全部的系統動作,這在實際的作法是收集所有Activity Diagram的Activity List。之前對Activity所做的分析與重用都是為了得到系統動作的最佳集合。
理想的Activity是每個都對應一個特定目的獨立系統動作,範圍太大會使得一個Activity裡包含了數個系統動作,範圍太小則會令多個Activity才達成一個系統動作;在定義時務必讓它們形成一對一的對應才是理想的設計。此時必須強調Activity的意義是達成Use Case的分解動作,而Activity Diagram是組合分解動作的Use Case Controller。
我認為Activity才是代表系統功能的真正入口,因此收集Activity List之後,就給每個Activity自己專有的method name作為Entry Point, 並設定其所屬的Interface。定義System Interface Method時要參考設計準則仔細思考, 務必把類似的動作封裝在一起不要造成混亂。
記得每個Activity都要一個自己的方法,而不要認為數個動作接近的Activity未來可以利用不同的參數來使用同一個方法,那時設計時才需要去思考的。唯有這樣一一對應才能在執行某方法時肯定那是為了一個特定原因而進入的,我們也才可以肯定在這方法上的變動不會影響到其他Activity。
理想的Activity是每個都對應一個特定目的獨立系統動作,範圍太大會使得一個Activity裡包含了數個系統動作,範圍太小則會令多個Activity才達成一個系統動作;在定義時務必讓它們形成一對一的對應才是理想的設計。此時必須強調Activity的意義是達成Use Case的分解動作,而Activity Diagram是組合分解動作的Use Case Controller。
我認為Activity才是代表系統功能的真正入口,因此收集Activity List之後,就給每個Activity自己專有的method name作為Entry Point, 並設定其所屬的Interface。定義System Interface Method時要參考設計準則仔細思考, 務必把類似的動作封裝在一起不要造成混亂。
記得每個Activity都要一個自己的方法,而不要認為數個動作接近的Activity未來可以利用不同的參數來使用同一個方法,那時設計時才需要去思考的。唯有這樣一一對應才能在執行某方法時肯定那是為了一個特定原因而進入的,我們也才可以肯定在這方法上的變動不會影響到其他Activity。
訂閱:
文章 (Atom)