2008年4月30日 星期三

O10 我的設計準則(4)──靈活替換

一個以“滿足客戶既定需求”為目標的系統,只是勉強及格而已。因為無論是需求的改變或是因為要修正之前沒有被測出的錯誤,都會造成在固定結構下的變動而使得問題因為失衡而接二連三地發生。因此,彈性的設計雖然對實現功能沒有直接影響,卻能夠避免未來的巨大風險。

彈性設計的首要,在於切割出可以置換的單位。一個只能置換整個元件的設計,極可能在只有流程變動略大時就需要重新放上一個內容幾近相同的另一個元件;因此除了儘量應用參數化的設計外,我把元件的內部切割為六種特性放置屬於該部分的程式碼,每一個部分都可以根據實際的需要來改變實作的類別。

另一個要求是使用介面來定義切割出來的單位。要能靈活置換必須應用OO裡的多形特性,利用介面定義好切割單位的規格並在上層的程式使用,再用動態變換的設計(例如Design Patterns)更換實際工作的物件以應付不同的需要。

靈活替換還有一個重要的目標是使用較少的程式碼來達成較複雜的動作。像是Class、Constructor、Method等Java物件,以及Method所能涵蓋的getter、setter的資料存取方法,活用這些映射物件將有助於快速的開發與動態的改變。

2008年4月29日 星期二

O09 我的設計準則(3)──絕對重用

設計程式的人都知道,同樣目的的重覆程式碼應該抽取到同一方法裡作為重用。重用的好處主要是讓同樣目的的程式碼只有一份,在使用時有固定的用法且在修改時只需要修改一個地方就能改變全部。

在理想的設計裡應該抽取相同層級內相同目的的物件,不管是類別、方法或是常數,而不要包含不同層次的東西。在抽取方法的的時候,有時會有幾乎相同但有少許差異的地方,這時可把差異的部分用參數區別並在程式中判斷,如此就可以使用相同的程式碼。

對系統而言,特定方面的功能都集中在一個元件;對元件而言,六種類型的類別各自掌管屬於自己的行為;對方法而言,同樣的動作都抽取到同一個方法;對資料而言,使用的常數全部要定義在固定的介面裡。每一個可以抽取的地方都應該確實地實行,否則同樣目的的程式碼在系統裡到處都存在,會變得難以有固定的用法與切實的維護。

在相同使用層級的視界裡,努力把聚合力高的的共同部分抽取重用,是現在堅持的準則。檢視方法定義的所在位置是否正確與不應該存在兩份以上的相同目的程式碼或物件,是review設計的主要方向。

2008年4月28日 星期一

O08 我的設計準則(2)──動靜分離

撰寫程式時如果沒有適當切割與分類,會造成不同層次的動作都集中到一個類別,為了避免層次不清的混雜,所以希望可以遵循分層負責的準則切分出每一層該做的事。

在元件裡的動作雖然具有明確的目標,但是為了達成元件方法的目標,還是必須用正確的順序執行應該執行的小動作,所以這裡會依照動態的執行流程與靜態的固定動作加以分類,將不同的類型的東西封裝在不同的地方;也就是Flow與Action。

有許多人(包括我)奉行MVC的設計,但是在本質上來說MVC的組成會進一步劃分為MVFA。製作流程圖時是依照Use Case的流程串連各個負責實作的Activity,我是進一步把這個觀念應用在程式內部,以強制將流程與動作分離用來解決流程時常異動的問題。我的目標是在實作Flow的部分時,可以藉由週邊的Interface快速地知道可以使用的物件與動作全部有哪些,在幾乎不同多作學習慣情況下快速改變執行的邏輯。

另外不管是在系統上或是元件內,動作的實作是Activity所描述的,而且特定功能的動作會封裝在同一個Component。將流程與動作分離時,如果系統的精髓在流程,則把流程部分掌握在自己手中,將動作部分外包給其他公司開發;反之如果Component裡有設備或邏輯的機密,則將流程部分拿去外包而將動作部分留下來,如此將有助於商業機密的保護。

2008年4月27日 星期日

O07 我的設計準則(1)──分層對應

功能的完成是經由正確順序地執行所有必要動作所達到的,分析出所有的必要動作是製作功能的前提。收集大量的系統動作後,依據其執行的特性與目的加以群組化形成元件,這便是用於組成系統的軟體單位。

對開發系統而言,先準備好最底層的元件,像是Log、Message、Setting等基礎必用的元件,然後定義與Client to Server、DB存取元件,再來是定義基本的MVC元件;到這裡是所有系統都可以完全重用的基礎。再向上則是與Domain有關的MVC、Business Component、產生元件的Factory與系統Root Component。

往內來看,元件的處理邏輯有Impl、Flow、Action,處理的異常有Exception,處理時的變動參考Properties,處理的資料來源則是Model。這是元件內的六個組成單位,每個單位再封裝與自己負責功能的相關程式,依此向下完成每一個部分。

以Compnent為界線,向上組合成客戶想要的系統,向下再依MVC原則切分內部,每一個靜態物件或是動作類型都安排一個元件負責處理。二者的中間以Component Interface作為介面清楚地隔成兩個不同思維的層面,但是用相同的設計概念貫穿全部的Component,讓所有人快速瞭解整個系統是如何被架構起來的。

2008年4月26日 星期六

O06 做事的方法(11)──交付工作並非學禪

在民間故事裡,常會看到古代高僧做出一件事,讓大家根據自己的想法來推測有什麼含義,同時以檢視每個人的答案看誰的答案最接近高僧想的,然後把主持的位置交棒給他。用這個方式來確認誰的想法比較貼近出題者的想法是很不錯的,因為思維相近的人推想出來的邏輯都會差不多。

可是看一下其他的人提出的答案,同樣的動作在每個人的眼裡所解讀出來的結果都大不相同呢。如果主管交待事情時沒有描述出完整的想法,只單純地告知要做什麼,不僅接收命令的人會有“為什麼要做這個”的疑惑,消息讓其他的人聽到時還會造成每個人各自解讀主管想法再傳出去的結果,使得謠言到處流傳。

平時做事時,也有不少與主管想法不同的時候。管理階層想的是如何達成目標,實作階層想的是怎麼順利做事。有次幾個人為了怎麼順利執行進度而計畫了半天,最後跟主管確認時才發現公司政策上決定不能那樣做而要用另一種比較麻煩的方式。因此為了不浪費私下計畫的時間,所以養成先去詢問主管是否有政策考量的習慣;無法使用邏輯推理出來的結果,到底不是像我這樣的人可以理解的。

開發系統不應該也弄成這樣。設計者把想法直接做成程式,再讓一堆維護的人根據程式去猜想他原來為什麼這樣做,還有做了會有什麼好處或壞處,這是非常令人無力的事。如果自己不喜歡做維護的工作,又何苦把辛苦接手你開發系統的人搞得這麼累呢?

2008年4月25日 星期五

O05 說話為什麼會夾雜著英文單字?

以前在國內時,時常會疑惑為什麼有很多人在中文語句裡夾雜著英文單字,明明那些單字都有標準的中文名稱;那個時候都會要求自己說話時儘量使用中文單字。

在2007/11起與大陸的團隊一起進行開發,在討論與溝通的同時發現我說的話有時候他們無法明瞭,後來才發現是因為英文單字在大陸與台灣翻譯的用詞不同造成的。與其把話完全說完後才發現沒法溝通而重說一遍,倒不如直接把專有名詞全部置換在句子裡表達還比較快速;發現了這個道理之後,對他們表達的溝通內容就完全改變。

想像有本巨大的百科全書,裡面收集世界上所有的名詞、動詞、形容詞……等等,對一種語言來說,當你知道越多物件在該語言的名稱就有越多可使用的候選單字;在越多狀況下能夠用該語言表達自己的意義並且聽懂別人的表達就是越熟練的證明。

從根本來看,對每一種物品、動作、形容……如果可以輕易答出它在該語言下的念法,就更可以輕易地把自己的想法用該種語言的表達。而在溝通的過程中,由於每種語言對同一物件的形容不同,如果擔心產生誤會的話就使用大家都統一的英文來描述它。

註:倘使把字詞的本體想像為該物件的Interface,那麼各個語言下該物件的名稱就很像是該物件的實作。字典是收集指定字數內所有字詞的集合,各種字典內都具有該語言下所有字詞的名稱;把字詞名稱視為Object,字典就像是提供所有物件Interface實作的Factory。再往上看,我們在不同語言的需求下會取用指定語言的字典(Factory),這種選取的動作正是Abstract Factory的精神。

2008年4月24日 星期四

O04 讓參與者明白系統的一切

在進入實作之前,如果實作的人與設計的人不同,那麼就必須有移轉的動作;簡單地說,就是要把設計者腦子裡的東西“複製”到實作者的腦子裡。

移轉的動作常見的有兩種:一種是設計者把記得的部分跟大家說一遍,一種是循序漸進地將完成功能所需要的資訊製作文件。口述的方式的問題在於設計者想到什麼說什麼,除了細節難以即刻描述之外還有遺漏的問題;使用文件的話,一旦寫下就可以永遠保存,裡面記載的內容可以讓所有接觸的人看到全部的內容。

移轉最大的問題都在於設計者提供了些什麼內容?實作者所能知道的範圍一開始都被侷限在設計者提供的部分。雖然我們明白設計的人描述設計越詳細越好,不過由於把心裡的想法記錄出來需要時間,而且這對產能沒有任何幫助,因此許多設計者都把時間放在功能的實現而忽略掉文件內容的重要性。

適當的圖文描述有助於實作者直接吸收表達的內容,在描述內容有所缺漏的時候,實作的人必須再確認設計者的想法,或者自己去推想他原先想表達什麼。如果團隊裡想找出具有天份的人時,這的確是個不錯的方式,不過在正常的開發系統時,這樣地讓一群實作者繞一大圈才能懂設計的目標,真的是很浪費資源的作法。(節省設計者的時間,浪費實作者與維護者的時間)

因此,我強烈堅持應努力讓實作者快速明白系統的設計,以穩定而不易改變的元件與系統結構套用在所有地方,讓實作的人適當統一的寫作風格,也藉此機會減少設計者額外花時間製作文件。以這種方式,可以在一天的基本說明之後只依賴內部的註解進行系統的維護。

2008年4月23日 星期三

O03 測試先行──保證設計能有正確的結果

設計與撰寫的目的,最基本的目的是為了保證功能需求可以被正確的執行。無論系統內部設計得再爛,只要把執行功能需要的動作都依正確順序放入並通過測試,在使用者的角度來看都可以被稱為及格的系統。

由於系統有這個最基本的要求,因此“功能必須正常執行”成為首要的目標。在元件設計完後,我通常都會對它的initial、dispose與幾個重要的執行方法進行測試,不過我的目的在於檢查Method內執行的動作是否正確且沒有缺漏。設計時比較像紙上談兵,聽起來似乎都很有道理,但是實現起來就會發現有所缺漏或是邏輯相互抵觸,這些都必須依賴測試來發現。

外在測試單位是API與Component Interface Method,內部測試則是以Flow Method內部為主要,在執行時驗證Flow內使用的Action與結果是否符合完成功能的需要。從第一個實作的元件開始(應該是從底層開始實作),就應該在實作結束時實行Unit Test來保證功能正確而且不會被修改。順利的話,通過Unit Test後都可以預期使用是完全正確且沒有問題的;這會讓設計更上層元件的人專心在其內部而毌須擔心底層元件的問題。

如果有餘力的話,在設計與實作時應該要經常檢驗Component Interface的定義是否具有高內聚與低耦合的特性。雖然“功能必須正常執行”是最基本的要求,但是我們應該把眼光放遠,將目標放在“每一個元件都能被單獨重用”,這樣才能更加速自己系統與其他系統的開發速度。

2008年4月22日 星期二

O02 絕對不要直接寫程式來作設計

人腦是沒法放置太多資料的,除了極少數過目不忘再加上記憶持久的人,細節的部分到最後不是忘掉就是亂掉。在想要在程式裡執行一個動作的時候,如果遇上記憶沒法串連的情況發生, 放置的動作就有可能不在它應在的位置或是使用到不正確的方式。

程式的設計應該是嚴謹的,每種不同功能的相關動作必須要有一致的風格與寫法,而且必須在整個系統內予以貫徹。每一個動作的使用錯誤都會在切割於這個關聯上時造成必須改動的影響,這是因為在這個切割面上所露出方法的使用是不平整的。一個設計良好的系統,會在Use Case、Tier與Layer的三度空間中儘可能地放置整齊的切割面。

動作責任歸屬與使用方式的錯誤,因為功能還是達成所以可以通過所有的功能測試,但如果到系統開發後期才發現從某個切割面上必須調整,那可不是好玩的事;因為在那之上又堆疊了許多設計,任何下層的方法變動都會連帶造成使用它的方法必須修改。

在一次UI的設計上,欄位希望在輸滿指定長度時將游標移到下一欄位,但是開發的人寫成輸滿長度後要再多打一個字才會移動游標,後來為了改回正確的行為模式修改了很久,因為牽涉到得先檢查最後一字元是否合法以及移動游標時間點的衝突,而多花了不少時間。

維持各個可能切割面露出方法的正確性與一致性,這是直接寫程式作設計時無法滿足的需求;因此我強烈建議在每一個可能切割的地方先行檢驗其Interface與結構。

2008年4月21日 星期一

O01 設計常會出錯,而且實作時才發現有錯

不管哪個方法論都有這個問題,因為思考時所想的與實作時要做的有時會有差距,理論上設計經驗越多,差距應會越來越小,但是並沒有辦法保證所有的方法設計都做出完美的設計,出錯的部分通常是應該做的動作少做、動作順序錯誤或是執行結果的判斷錯誤。

主管時常問我:有沒有可能人在台灣帶領大陸那邊的人一起設計?我的回答都是:在設計階段是不可能的。人的想法都會有疏漏,定義固定的作業流程與不斷的檢查就是希望及早發現設計的疏失並解決,討論與檢查的行為在設計者之間有大量的資訊交流,拉開設計者的距離後在彼此之間的資訊交流相對地就變為比較困難。

主管還會問我:是否可以在台灣設計然後拿到大陸開發?我的回答也是:以現在的作法是不可能的。這是因為現在的設計者還無法寫出可以完整描述設計的文件,雖然現在公司已經導入CMMI,也規定出設計產出應有的章節,但是無法表現出設計精神的產出是難以順利開發的;更何況設計的人還沒有寫文件的習慣。

如何讓實作的人快速明白系統內部的設計概念是很重要的工作,因為讓實作人員依照設計用正確的順序呼叫正確的方法才可以保證系統的形成會符合設計;同時也因為設計的錯誤可能在任何地方發生,藉由設計概念的傳達也能夠讓實作人員協助發現設計時疏漏的問題。

2008年4月20日 星期日

N20 輔助的文件(5)──Component結構的所有檔案

與只擁有Interface、Controller、Model與Exception的簡潔型Component比較起來,自己所使用的Component結構實在算是個龐然大物,而且兩者間想達成的功能幾乎是一樣的。從上一篇的附圖來看,在開始設計方法前得先準備好一大堆Interface與Abstract Class,也難怪其他人根本不想改變原來的作法。

Component與Component之間在功能上是各自獨立的,但是以行為模式來說,所有Component的Implementation、Flow、Action、Model與Properties等等的特性其實是共通的。這個意思是針對Component來說,事實上也需要安置一個Abstract Component來放置所有Component裡各個零件共同的行為模式,同時還要加上管理物件生成與消滅的Interface與Method。這樣的需要就形成了這樣的Component結構:

把Component做成這麼大,如果得在佈置結構時花費過多時間,反而會降低產能,為了解決這個問題只好想辦法讓Component結構“自動產生”所有的物件。Component結構圖是既定的產出,輸入的變數可以侷限為Package Name、Component與output path,因此我在一開始就指定一個人專門製作這個工具。

如果三兩下就可以把這麼一堆的Component結構佈置進而開始設計,開發團隊的接受度與產能就可以相對提升不少。

2008年4月19日 星期六

N19 唯有先為他人著想,自己方能受惠

看別人寫的程式困難點其實在於找不到入口與看不懂處理邏輯,尤其在Component的實作只是直接從Interface後把所有的東西堆積在一個Class裡面時。

Component的設計雛型在D06時已經形成,MVC的設計方法將之修改為MF(Flow)A(Action)C並將之各自定義在專用的Interface裡。以類似SOA的觀念把Flow拉出來,對於設計者而言就變為只要挑選Action裡的方法組合為處理流程,再加上Properties改由外部傳入而不使用內部設定檔、有狀況時只拋出Exception而不在內部作任何處理,讓我的Component具有更高的reuse特性。

這樣的Component想法在2008/04時對開發團隊說明,參加的人也認同我的作法可以解決一些目前還難以解決的設計問題。我特別說明這個作法在設計時因為要把原來直接撰寫的程式拆解到不同Class的方法裡,需要直接撰寫大約五倍的設計時間,但是適當切割的作法可以讓修改除錯的時間減少一半以上,且能應付大多數的需求改變。

設計的人在定義系統時多做點,可以讓未來使用方便而且容易維護,自然可以節省未來親自處理的時間。但是習慣快速寫作的人是否能夠接受這樣的作法,尤其是多花自己時間的作法呢?如果內心的“設計”僵化到難以改變,也許人就已經定型為那個樣子了。

這張圖是我最後定型的Component內部結構。

2008年4月18日 星期五

N18 做人的方法(10)──收穫來自投入的部分

每個人一天都有24小時,扣除生活與工作必須花費的時間外,是自己可以運用的。雖然在生活中遇到的事件都會讓人學到些什麼,不過將空閒的時間投入什麼方面,在那個方面的就多少有更多的收穫,但是相對地沒有投入的部分成長會較為緩慢。

如果一個人把時間投入在讀書,與別人就沒有較多的互動,如果生活與工作上沒有辦法學習到與人相處的方式,那就有不懂做人道理的可能;把時間投入在遊戲或電影,要是內容沒有值得學習的地方,就只能得到休閒的效果而不會得到看書時能有的收獲;常與朋友一同聚會,倘使交談的內容沒有值得學習的地方,那同樣會讓學習止步。

學習的效率也是一個影響很大的因素,同樣的投入時間在不同的人身上會有不同的進度,有的人隨便看點書很快就能吸收,但是有些人投入很多的時間卻進度有限,不過只要有重覆的練習還是有很大機會學會。“一分耕耘,一分收獲”描述的是只要投入時間就會在該方面有所進步,“專家是訓練有素的狗”雖然很不好聽,卻也是描述在該領域的專家投入了更多時間反覆練習過。

如果希望自己學到什麼(目的),就要決定達成學習目的的過程(步驟),然後按部就班地投入資源,努力學習以期產生效果(影響)。我們應該依現有的資源來計畫好自己的人生可以有什麼樣的成果,依此決定更多有機會達成的學習目的,藉此慢慢構築出個人的生涯規劃並努力去實踐。

2008年4月17日 星期四

N17 API字典與一般字典的異同

所有開放的API都應該撰寫詳細的Java Doc給使用者參考,包括Package、Class、Method、Data,只要是使用者看得到、用得到的東西都應該要有說明。文件的製作就以Java Doc直接產生並加以收集保存,我會稱之為API字典。

不管是哪一種字典,目的都是收納該領域應該有的項目,使用者想了解某個項目的使用說明時,可以根據字典的分類原則從上到下找出需要的項目。不過若是想要表達某個意義,一般的字典卻無法讓使用者條列出符合該意義的所有項目供使用者挑選,使用者只能依印象或請教別人來找出達到某個特定目的的項目,再瀏覽解說看是否符合需要。

API字典的目的,一方面可以讓使用者明白某個Class的功能與有什麼樣的常數與方法,另一方面是明白指出系統內開放使用的API範圍。雖然安排的原則與一般字典相同,但是我們可以搜尋API資料夾的內容來找出擁有搜尋關鍵字的所有檔案,藉此縮小尋找的範圍以希望精確地找出需要的項目。

看到一個API後找出它的使用說明,因為某個目的找出所有可能的API作篩選,這是API字典應該提供給使用者的功能。

2008年4月16日 星期三

N16 設計的製程(10)──API的定義與分類

執行方法後立即傳回結果,而且在執行前後不需要額外的設定且不留下執行狀態者,我都會將之歸類為API。在這樣的定義下雖然可以把方法宣告為static但是我仍建議使用Singleton的設計,以確保將來出現變化時還擁有改變的彈性。

API可說是對於資料的常用動作,像對於String、Array、File、Collection甚至是XML Dom我都定義了Class來放置對應的API方法。雖然許多JDK的物件都已經提供基本的操作,但是還是有一些數個操作組合形成的動作,或是加強原先操作的功能與應用性等等的需要,這些製作的產出都要對應操作的物件類型放到各自的API Class。

除此之外,對於自己定義的Data Model也可以應用這樣的設計:Data Model本身只提供最基本的getter與setter,有組合性的需要時再於Data Model的上一層放置一個Data Model API來操作。把對資料的動作分成基本與組合兩層放置,這個觀念與Controller裡的Flow與Action是一致的。

2008年4月15日 星期二

N15 如何學好地理與歷史

地理與歷史雖然被分為兩門功課,但是兩者之間的關聯是非常密切的。地理描述了地殼的特質,包括了地形、礦產、種植、畜牧、城市等等;歷史描述的是人類在地球上生活時發生的重大事件,包括產生的原因、事件的內容與日後的影響。

從系統的角度來看,地形、氣候與礦產是位於最下方的靜態層次,城市、畜牧與種植是人類決定在此處生活後才有的中間層產出,最上層則是種族與戰爭、遷徙等歷史事件的發生。人類受地形影響決定在哪裡建造城市、在哪裡種植與畜牧,但人類的歷史同時也改變這些;人類可以改變小部分的地形,地形變化在人類歷史中雖很少發生,卻會改變該地大部分的人類(例如龐貝城)。

這兩門功課在求學階段裡都是硬背硬記度過的,主要是因為課目被切分為本國地理、世界地理、本國歷史與世界歷史,每位老師都很認真地教他們負責的科目而忽略了說明彼此間的影響,連帶地讓我沒法去融會貫通世界的平衡。

前陣子在觀看介紹圓明園的DVD時,裡面帶到建造圓明園時與乾隆死後的幾十年間,歐洲各國發生了什麼樣的事情(工業革命、法國大革命等),相較之下才更明白在某段時間裡各國的演變與之後發生事件的關聯。相信唯有明白在每塊地形上的人類在同一個時間點時各發生了些什麼事,才能對接下來的世界局勢走向有更清楚的掌控。

註:高中時選讀的是自然組,國中與高一的地理、歷史都是在課本上硬畫重點強記而及格的,因此以上僅純屬個人推測的結論。如有人看後嘗試不同的讀法而導致死當的話,本人概不負責!

2008年4月14日 星期一

N14 設計的製程(9)──Component的說明文件

對要使用的人來說,只需要說明詳盡的Component Interface註解就足以知道它是否適用;真正要使用時需要的是Component Interface Method的Java Doc加上簡單的使用範例。所以讓使用者明白Component的功能與用法是撰寫文件的方向。

功能與用途寫在Class的註解,常數與用法則寫在public Data與Method前,所有說明都用Java Doc的寫法以便產生給使用者觀看的文件。除了單一Component的內容之外,還要準備所有Component的分類清單,讓使用者快速依功能需要找出所有可以被應用的Component再從中挑選最適合的。

給維護人員看的是設計的想法,依之前提到的原則與順序,提供描繪Class與Interface的Package Diagram,同時在裡面標示所有Data Model、Action、Properties、Exception等靜態的方法,再以實作Component Interface Method的Controller說明來串連,就可以快速明白所有的方法合作的概念。

Component文件主要在於使用上的描述,讓使用者只要看文件就會運用的話,可以省掉教學的時間。由於理論上Component會在許多系統內使用,通常在初期時就會經過需求確認與較多測試,維護方面的要求相對的就會比較低一些。

2008年4月13日 星期日

N13 佈置容易看懂的軟體結構減少文件製作

似乎除了設計者之外的所有人都贊同文件的重要性,認為留下適當的文件是該做的事。但是真正在做事的人一邊寫程式還得一邊更新文件,這樣子相當於處理兩倍的工作量,每一個地方都這麼做的話肯定會累死的;但是要是只更新一邊就會造成內容不同步:正確的是程式,錯誤的文件就沒人要看,等於白做。

厲害的程式設計者通常不寫文件,就是因為他們覺得文件寫了很久卻沒有參考價值,反而拖累他們的開發進度。可是問題是他們寫的東西雖然功能正常,想要瞭解裡面做的事時,除了在沒有文字描述的輔佐下難以看懂之外,還得面臨詢問時得到“我忘記了”的回答。

大陸某大公司說他們採用敏捷開發,其大意是先定義好系統的模組,並且完整定義出每個模組的介面,結構經過討論確定後,每個模組的內容再由負責的人直接在裡面寫程式實作。發現這與我使用的開發方法很接近,只是我連模組裡的軟體結構都有佈置的準則。

每個獨立區域(包括System、Package、Module、Component)都應該要至少有一張靜態結構的Class Diagram,還要有完整的Interface與Interface Method清單來描述這個區域“應該”具有的功能;另外“如何”履行這些應該具有的功能,就努力將之收集在Controller的方法裡,以標明原因與目的的詳細註解加以描述,同時還是要記錄流程內使用同一Package內的動作Method。

動態的流程使用註解說明每一步驟的使用,靜態的動作記錄詳細的操作方式與使用效果,如此一來可以省卻變動性質最大的流程描述文件,同時保持記錄全部動作的必要資訊。

2008年4月12日 星期六

N12 備齊資料的記錄才能應付各類的分析

相信有許多人都喜歡看棒球,不懂門道的人大多只看球星上場或者自己支持的球隊表現如何;真正研究棒球的人會去收集許許多多的數據來統計每一場比賽裡的每一種事件,比較看看哪些人、哪些球隊、哪個球季有什麼樣的統計數據。

棒球比賽裡最基本的事件是投手的投球,投手投出一個球的時間很短,但是每一個球記錄下來的資訊卻是大量的。先看時間點,投手投某個球是這場比賽的第幾球、是哪一局的第幾球、是這名打者的第幾球?投出的球球種是什麼、時速多少、投到哪個位置、是好球還是壞球?對應投過來的球有打者的反應,打者的每一球有沒有出棒、被判好球或壞球、揮棒擊出後球飛往哪裡、是飛球還是滾地球、結果造成了幾個壘打數或是造成幾個人出局、怎麼出局?

再往上推還有今天投手與打者的統計數據、投手與打者的對決統計數據、防守者與跑壘者的攻守統計數據、如果這場比賽有誰有特殊的表現也會另外記錄……我雖然不是棒球專家,但是球賽看久了也知道有哪些數據常被提起,也可以發現專業的棒球可以說是想要知道什麼樣的資訊幾乎都有這樣的記錄。

在這裡我想要表達的是,在一場比賽裡每一次的投球所需要記錄的資訊是如此之多,再衍伸出來可以查詢的數據還有更多。倘使我們將系統(或元件)的開發視為整個球季,把每一個方法的設計視為每一次的投球,這樣是否可以感受到Method設計資訊在系統(或元件)裡的重要意義呢?

2008年4月11日 星期五

N11 令人感到麻煩的設計(15)──避免將參數放在設定檔

決定不用程式碼寫死使用關係的同時,表示我們必須在某個地方記錄對應的關係並在執行時取得。通常的最快作法是將參數放在properties檔案,再使用ResourceBundle方式讀入為Properties取用;不過使用時再多往幾個方向思考一下,或許會有不同的決定。

檔案位置的固定。很多時候為了方便設計與管理,Component的參數檔案會被指定放在一個固定的地方,而且通常是使用它的Package裡。這樣的放置會造成設定檔被“綁死”在那個地方,無法應付系統要求要集中所有的設定檔方便管理的需求。

檔案類型的固定。在設計上採用傳入檔案所在的URL後再於內部讀取內容,這樣的設計可以讓設定檔在任何存取得到的地方。不過取得的來源就只限於properties檔案,要是客戶另外有xml或是資料庫來存放設定的話,就沒法動態由不同的來源產生。

雙向讀寫的要求。即使設定檔由外部組成Properties物件再傳入Component的作法可以滿足以上的想法,但是Properties的特性是只能讀取的。設定一般來說是允許客戶修改的,直接修改檔案內容雖然快卻有修改內容發生錯誤的狀況,此時可能需要選用別的物件。

靈活替換的判斷資訊有很多存在於設定檔,針對以上各方面的考量,在設計上我會定義自己的設定檔物件,由客戶決定怎麼生成Component需要的設定來滿足以上的想法;當然,這個設定檔物件是存放在元件庫裡的。

2008年4月10日 星期四

N10 置換整個Class與用參數涵蓋可能變化的比較

在軟體工廠的概念裡,所有的元件都像是積木有自己的規格,系統的開發在大部分的地方都是只選擇適合的積木堆成我們想要的模樣。在需求變更的情況下若有元件變得不適用,可以將之拔除再另外放一個更適合的進去。

以取出積木再另外放一個進去的想法,有可能會使得功能幾乎相同的元件有許多分身存在,這是因為製作元件的人不想分析比較彼此之間的差異,而直接把變化寫死在一個特定元件裡。如此一來每次的小變化都形成一個元件,時日一久自然就可以擁有“看起來很多元件”的元件庫。

玩過積木的人應該注意到,同樣有那種具有可動部分的積木,像是螺旋漿、門窗、旋轉軸之類的。這種積木只要撥動一下可動部分,就可以讓積木以不同的的方式呈現。如果今天的積木在開門、關門與半開關之間都要求使用者“置換”該積木的話,玩的人會不會覺得頭很痛呢?

直接把變化對應到不同的元件是最直覺的,開發者幾乎連想都不用想就可以由copy-paste或是繼承快速地做出一個新元件;去分析改變與影響並在同一個元件內去涵蓋相對地就得花上比較多的時間處理,但是對使用者而言卻有能應付多樣變化的單一元件。如果你是開發者,會決定怎麼做呢?

2008年4月9日 星期三

N09 令人感到麻煩的設計(14)──Component間的避震器

Component更應該要堅守使用專用Data Model的原則,因為Component之上是各式各樣不同的其他元件以及系統。只要兩個以上的Component使用了同一種Data Model,即使二者之間並沒有直接呼叫對方的Method,兩者還是產生了間接的使用關聯。

如果在設計上它們不應該產生使用同一種Data Model的關係,那麼應該先讓兩個Component使用各自的Data Model,並另寫一個資料轉換的函式負責在兩種不同的Data Model間搬移內部的資料,或者是在系統裡準備一個共用的系統資料區,在全部需要準備Component Data Model的情況下都與系統資料區作資料的交換。在開發系統時我比較偏好後者,縱使需要為每個Component撰寫一個資料轉換程式,卻可以保證在同一系統下是同一種作法。

在動作方面,可能某一個系統的動作需要同時使用數個Component的方法,直接依序把Component Method放到系統動作方法裡是直覺的設計。但是在分析階段時,必須要檢視是否有一個系統動作需要同時執行數個Component Method的狀況,如果有的話還是另外抽取一個系統方法存放為佳。

無論是Method或是Data,在有差異存在時務必要安排一個物件(而且只有一個)來負責填補其間的差異,以期在使用時能夠直接呼叫而感覺不出有差異。

2008年4月8日 星期二

N08 開發系統與設計Component的差別

設計一個功能,是要解析出達成功能的所有動作,並將之以正確的順序串連起來。在時間急迫的專案裡必須以完成功能為優先,這時可以簡單地先寫出正確順序的所有動作而先不寫上完整的錯誤控制,等到測試階段再根據測試出來的錯誤補寫上錯誤控制。這是很好用的快速作法。

可以這樣做的原因在於專案的功能佈置最上層,除了少數幾個功能可能使用之外沒有任何程式使用,因此無論作什麼樣的改變都幾乎沒有影響。但是Component的設計卻絕對不能用這種走捷徑的方式,因為有非常多的程式使用Component,只要內部的設計有不正確的結果或是因為修正錯誤而改變,都會有多到難以估計的程式受到影響,根據理論所有被影響的地方都必須要重新測試過以保證正確。

有時使用其他元件時會遇到邏輯控制不理想的狀況,那時只能在自己程式的流程裡記錄現在動作的狀態,在後面的流程再判斷動作狀態的內容作對應的處理。這樣的繞路處理很像是在沒有設計好的主機板跳線來達成功能,是很不理想的設計結果。在之前使用swing與swt兩種UI元件時都曾經遇到許多這樣的現象。

設計Component的態度應該時時戒慎恐懼,一方面要努力思索提供足夠的Method應付全面的需求,另一方面要維持設計的品質與定義吻合且沒有錯誤,因為設計不良時微小的改變都會造成其他系統的大量變動。

2008年4月7日 星期一

N07 設計的製程(8)──提供不同範圍的流程Method

雖說Component只提供動作方法,讓系統定義各自使用Component的流程方法是最有彈性的作法,但是使用Component的流程也無可避免地會因為大多系統的使用方法都相同而必須抽取到元件庫的狀況。

系統對於列印的要求定義了一個方法printData(),內部的分解動作是進紙closeInserter()、列印print()、退紙closeInserter(),這三個動作是Component提供的基本方法。後來發現printData()是所有系統對印表機的共同功能需求,因此將集合數個列印動作的printData()放置到Component。請注意此時Component已經帶有了流程方法。

再更進一步在分析系統時又發現:即使列印是把三個動作集合在一起,但是如果接下來還要在同一張紙上接著印其他資料時是不需要退紙的,因此在原有的printData()加上列印後要不要退紙的參數。我們可以發現,Component帶有流程方法後就增加了設計的複雜度。

在流程方法裡的每個動作,使用者都有可能決定要不要做?該怎麼做?所以設計時也必須要同時考量是否要提出參數供使用者決定;但是如果呼叫時每次都設定一大堆參數又相對地比較難使用,所以同時要提供參數較少的預設的使用方法。有時的需求又會要把數個較小的流程方法集中在一個較大範圍的流程方法裡使用。

Component加上流程Method時將會增加許多設計的複雜度,切記要完全遵守OCP設計原則來設計。

2008年4月6日 星期日

N06 設計的製程(7)──備齊Component應有的Method

Component的初期只是系統裡的一個Package,Method的設計都是根據Activity的定義而來。當我們覺得這個Package適合抽取為Compoent時,或許在現在的系統而言只要直接將之劃歸元件庫就可以繼續開發,但是對於一個必須應付多方面需求的Component而言卻應該把所有該有的Method都定義清楚。

想像一下ATM對於晶片金融卡的動作,我們需要的是讀取卡片帳號、比對密碼、變更密碼、顯示約定轉帳帳號等動作,所以在ATM的設計裡定義了這些動作。後來覺得處理晶片金融卡的動作適合抽取為Component,但是原有的動作Method是足夠的嗎?當開發網路ATM系統時,我們會發現還需要加上寫入約定轉帳帳號的動作。

當然有人會質疑在第一個系統時並不知道未來需要什麼,但是我們可以根據簡單的原則來檢視定義的Method。首先是生成與消滅,用來確認如何取得Component物件來使用與不用時消滅它的方法;再來是資料的CRUD原則,Component裡的所有資料都應提供Create、Retrive、Update與Delete四種方法才算是完全的使用(有保護性質的資料就需要隱蔽其中幾種)。

這兩個原則用來檢視存取資料的靜態方法,如果Component裡提供了流程控制的方法,就應該有更進一步的考量。

2008年4月5日 星期六

N05 Component設計要恪守OCP設計原則

Component是基於抽取共用原則所抽取的Package層級物件,一旦標記為共用則表示每個Component Method都會在未知數量的系統被未知數量的程式所呼叫,我們可以想像如果Component Interface Method的定義改變時(例如原來沒有傳回值現在改為傳回boolean),跟著而來的會是大量修改與測試的動作。

處理所有共用部分的最高指導原則就遵守OCP設計原則:Open for Extension, Close for Change。對於原有的方法要努力讓它保持不變,如果內部的作法有所改變就試著在內部程式消化掉,永遠讓方法的使用維持著原來的定義;如果改變無法在內部程式消化而勢必影響到外部的使用,那就就另外定義一個新的Component Interface Method提供給新的需求使用。

單純目標的動作改變的機會不多,大部分的改變發生在流程裡,尤其是改變動作順序或增減動作之類的。也因為動作內部隱含的邏輯極少變化,所以它們是比較適合開放為reuse的部分,這也是一般Component Interface Method定義的方向。流程控制著動作方法使用的順序與方式,因為每個系統操作Component的需要都不一樣,所以會在Use Case實作方法的邏輯裡處理;不過還是建議針對一個Component的操作邏輯另行在系統裡拉出一個對應的地方放置。

使用Third Party Component時若擔心其他公司改版造成的影響,可以幫它加一個Wrapper Class把Component封裝在裡面,讓改變只影響Wrapper的內部作法而不致於擾亂系統的使用。如此將可以更放心地使用其他已經經過測試的好用Component。

2008年4月4日 星期五

N04 設計的製程(6)──設計Component Method

Component Interface Method所定義的,一般都以功能流程裡的一個動作來看待的;流程的分析裡有對某個動作的需求,因而要找出元件裡有支援該動作方法的來使用。

Component的抽取大多是以Package為單位,即使有單純以一組Class為單位的,但是整體來說都是基於Interface Method的定義適合獨立作業。M02裡拉出的特定用途Method時一方面需要檢視元件庫裡是否已經有同樣功能的元件存在,如果沒有的話還要檢視這個Method是否適合作為Component。決定是的話還要再決定它應存在的reuse層級(通用, 行業別, 系統別, 專案專用)

Component的本質是Package,我們可以將之視作更小型的System,裡頭應該只有使用到的其他Component或內部Method。首先我們要用Class Diagram描述Component的內部結構,接著每個Component Interface Method都要至少用一張Sequence Diagram描述執行的過程,作法就像處理System Interface Method一樣。

Component Interface Method進入點同樣會先進到流程Method,設計時要記錄流程裡使用到的每個動作Method;對於每個流程與動作Method也要記錄所使用到的內部變數與其他Component。以Method為主軸記錄使用到的Data與Component就可以追溯出Component的所有使用關聯。

2008年4月3日 星期四

N03 Compoent與API的判斷與佈置

在我而言,判斷的準則其實很單純:就看程式模組處理後是否在物件內保存著狀態。單純地處理傳入的資料並傳回者為函式,除了處理之外還將會影響本身內部狀態者為元件。例如:把檔案讀出為字串應是API、讀取檔案後放置資料的物件就屬於元件。判斷類型後再根據其應用範圍決定它的reuse層次,接著再依照功能放到所屬的區域裡。

在架構上來說,元件與函式都與專案無關。專案之下先是元件,再往下是API,最下面是Java API或是Third Party API,使用的方式是上層的可以使用下層且可以使用同一層內的物件(不可造成循環使用)。Third Party API有時也會被列入到自己的元件庫管理。

在元件的使用上,雖然可以直接呼叫元件提供的方法,但是在系統裡對於元件的使用結果通常會有自己的邏輯處理,這些元件專用的邏輯處理為了避免散落各處,最好是規定一個地方來放置。也因此,我建議對於元件的使用要用以下的佈置。

一個典型的範例在於要處理印表機的低階控制碼:元件動作管理控制碼的輸入、元件動作控制負責印表機對於每個控制碼的反應,這兩者都屬於元件層;專案對於列印的要求是組成固定的格式並控制進退紙與列印,這些複合元件動作就交由元件動作結果控制與元件資料轉換來處理。

2008年4月2日 星期三

N02 資料變化的設計與做事方法的設計

程式設計的目的是要存取資料,這是毋庸置疑的道理。基於這個想法所衍生出來的方法論,簡單地說是先分析出功能裡資料存取的順序與使用方式,再放置處理用的程式;因此我們可以得到資料流動的流程圖,與每個資料流動時的程式處理模組。

這樣的設計可以滿足系統的開發,因為系統最終的目的就是要改變資料;在這個情況下,資料結構與處理程式的定義與追溯關係就顯得十分重要。無論哪一種設計方式,其實都會要求資料結構與業務邏輯的對應關係,E-R Model與資料庫的正規化都可以讓我們設計出最佳化的資料結構。

OOAD的介紹裡常說它是貼近自然界的設計方式,一部分的原因是其設計的重點在於物件的封裝,將系統裡的資料依據特性分別包裝在對應的物件,同時也將處理資料的程式封裝在一起。功能依Use Case Driven的方式設計,強調功能裡應該具有的每一個動作這比起依資料變化所做的設計,更要求了做事方法的設計。

現在的設計方向走到了服務導向,希望連Use Case都可以做到reuse。設計從資料層次,往上推展到處理流程,再往上擴充到整個功能,開發時越來越便利的使用,表示設計時要容納更多的考量。隨著封裝更多的邏輯,除了要保證功能一切要正常之外還要顧慮到使用上的變化。每個功能越來越複雜,也越來越難實作……。

2008年4月1日 星期二

N01 元件庫與函式庫的重要性

在開發系統的過程中,有時候我們會發現某個Interface或某個Method,除了可以滿足現在的系統需求之外,還能夠應付未來的使用。這樣的特色,即使Interface或Method內需要作小部分的調整或是將一些邏輯變數拉成參數,但是還是適合投入一些努力使之成為可以reuse的元件或函式。

擁有元件庫與函式庫的好處在於各個不同的專案裡,為了相同目的所設計的動作,可以找到一個已經開發並且測試好的元件或函式直接使用。抽取元件與函式時,應該也要規畫元件庫與函式庫裡的結構,規定基本共用層、行業共用層、系統共用層等放置的地方(參考L07的層次,元件與函式都各要準備自己的結構),再依照其特性放置到該放的組織層次裡。

除了放置到應用上的結構外,還要依據其應用功能加以分類。我們可以想像元件庫與函式庫就像百科全書一般,想要特定方面的功能時可以先找出符合的元件與函式,再參考reuse層次從中選擇最適合自己需要的。對於一個特定的元件或函式,同樣也可以取得使用時的所有資訊。(也可以考慮將Third Party API加以整理歸類)

函式與元件應該是經過良好設計與完整測試的,在取用時儘可宜接應用而毌須浪費專案的資源再去了解設計、實作與測試。在系統需要與元件功能能夠吻合時,對系統的開發是有絕對好處的。