2008年5月31日 星期六

Q08 部落格的驗收(3)──堅持是沒有終點的漫長道路

最早打算只寫一百篇左右心得的部落格文章,就在想法不斷檢驗與修正下莫名其妙地寫了快一年。近半年接下的專案讓我有機會實際演練了自己寫出的方法與產出,也讓自己更願意在設計時承擔架構軟體時的多花的心力,以期望讓未來使用與維護的人用更簡單快速的方式完成他們想要做的系統功能。

搜尋過Google上關於OOAD的網頁,卻從未發現過有教人如何應用OOAD設計與實作的範例,說的幾乎都是應該怎樣、可以怎樣或是OOAD不實用等等的話語。因此,到了最後我幾乎是憑著要從頭到尾將內容完成的意志力,保持每天撰寫一篇文章的進度;我花了“非常多的”時間在這個部落格裡。

直到結束這天為止,每天造訪部落格的人數從未超過35人次,很感謝每天都進來看的人,因為會常來是心裡相信我的想法對自己有所幫助。或許現在能夠影響的人不多,但總期待有那麼一天可以影響更多的設計者跨出“只想趕快完成功能”的迷思。讓使用者感覺越好用與讓維護者容易修改,代表著設計者要在系統裡考慮更多的控制,所以我們得先多繞一點遠路奠定好底層的基礎後才會有跟著而來的收獲。

寫出的心得大約涵蓋心裡想法的八成以上,餘下的部分都是零散而難以表述的經驗。未來在工作經歷上的領悟會融合在心裡而不會回來修正內容,最後就讓這一年來留下的文字記錄成為永遠的紀念;這些是現在這個時期個人對於程式設計心得的總結,而且以後會努力用心裡的想法來影響身旁所有的人!

2008年5月30日 星期五

Q07 部落格的驗收(2)──公司內部可能遇到的難題

公司裡又有好幾個人提出離職了。在公司的幾年內也經歷過幾次離職人數較多的離職潮,除了因為搬遷因素而無可避免之外,大多集中在專案即將告一段落的時候,這是因為心裡感到疲累而想在責任告一段落時求去。

離去的原因大致分成兩類。一種是上層決策不合理或是勾心鬥角浪費資源。有同事說過專案明明做得很累,長官卻以其他專案忙為由遲遲不讓高手進去支援,因而打擊了士氣;也有同事說看到其他人加班到半夜卻不忍申報一百多元的誤餐費,卻看到上面的錯誤決策動輒損失數百萬元連眼都不眨。

另一種則是因為個人無法成長。跟著在專案裡做了許久,說明與訓練都不足,常常只指出程式碼在哪裡後就得自己去看,弄到最後都只是管中窺豹而已;即使努力地做完現在的專案,下一個專案也只是把現在的成果複製過去然後從頭改起,沒有reuse與開發方式最佳化使得每個專案的開發都耗費了相去不遠的資源。

公司高層決定的發展方向是既定的政策,員工只能被動地觀看公司的政策是否合乎個人的職場規劃,二者相輔相成時自然會選擇留下一起努力;反之只是把員工當棋子看待的話,遲早都會離去。對於管理因不是專長而無法置喙,但是讓設計容易被瞭解、開發變得較快速、專案人力更節省卻是我可以努力的方向;當工作變輕鬆、環境變快樂,員工就比較容易穩定地投入工作而不去看上面有多誇張(至少我就是這樣)。

2008年5月29日 星期四

Q06 部落格的驗收(1)──擁有能力就應該去做

有很多人認為系統設計就是用最快的速度正確地完成所有的功能,但是當致力於開發速度的快速時,通常會忽略其他方面對系統的要求。製作的系統如果屬於框架類型,沒對上層開發者制訂設計準則而要專案自行定義放任自己開發時,幾個專案做完後依然沒有標準的開發流程。

許多技術好的人都不屑OOAD的作法,因為他們覺得那樣會降低他們的產能;明明很快就可以做出來的功能,為什麼要多繞那麼遠的路做一些事再去完成?許多理論好的人知道要往某些方向作努力,但是礙於無法提出完整的實際作法以說服快速開發的人,於是只能提倡一些理論上的堅持。雙方的矛盾就這樣地產生,因而誰也不服誰。

我算是剛好居於夾縫中的人吧。實作很快且理論看得不多,但是總覺得原來的作法似乎有點問題而謀求改進。然而身邊的人不是不屑做到這麼精細, 要不然就是不知道要怎麼做,所以只有我一個人努力地往這個方向走。沒有人可以討論是寂寞的,因為我知道想法都會有差錯,沒法保證每個細節正確無誤;沒有人能夠檢驗是可怕的,因為正確的細節可能被忽略,或許再也沒被發掘出來。

直到現在我已經可以在開發系統時的任一個地方提出足以說服自己為什麼要這樣做的理由,也能夠遵循自己的想法去做事;雖然在實現的過程中也經過不少修正,但是總是找到屬於自己的道。期望在不久的將來,我的想法能夠通過同儕的檢驗並推廣到公司內,並依此想法製作出開發更快速、使用更容易的各種系統。

2008年5月28日 星期三

Q05 產出驗收文件(4)──應不應該給客戶完整的程式碼?

在思考這個問題之前,應該先檢查公司內部對於專案程式碼的控管是否妥善並確認客戶是否註明原始碼交由他們自己維護。

在公司規定統一的控管方式之前,各個專案裡會有一部CVS電腦放置程式碼,但是在驗收後就會撤除。那時每個在專案裡的人都會在離開之前備份“當時”所有的程式碼,可是我們知道程式還會不斷地修改,在驗收後又再度需要修改程式時問題就出現了:每個人都不知道手上的程式碼跟客戶用的是否相同?更慘的狀況是根本再也找不到完整的程式碼。公司內有套系統要不是離職員工自行備份了一份,恐怕就正式成為孤兒軟體了。

我去維護某個系統時就是這樣,在沒人有把握的情況下只好自己用反組譯程式拉出要修改的Class到類似的環境裡,修改後編譯檔案再放進Jar File裡。在C++語言的系統沒有程式碼時,由於沒法反組譯exe檔案,有問題只好拿類似系統的內容過來再按執行時的需要當場修改。這樣的維護真的是人見人怕。

為了避免這些淒慘的狀況再度發生,現今的我會不管客戶合約有沒有註明要附上程式碼,在驗收的同時會將最後的工作區用密碼的方式壓縮後放在客戶端的電腦裡(當然自己也會收藏一份)。但是即使確保了最後的備份,要確定未來修改時改動到的是最正確的程式碼,還是必須仰賴所有經手的人把修改的部分更新到統一的控管裡才能達到。

2008年5月27日 星期二

Q04 產出驗收文件(3)──執行檔案的反組譯與混淆

產出系統執行檔的動作也需要記錄,即使使用了Ant來自動產出結果,對於操作的步驟與設定檔的內容還是需要說明。不過還有一件重要的事是防止被競爭對手看到程式碼的保護。

Java語言跨平台的特性使得我們寫出的程式會先被編譯為一種中介檔案,各個平台的JVM再讀入這些中介檔案進行處理,並在各自的JVM平台上執行。現在有很多可以將這種中介檔案還原為source code的工具程式,雖然完全沒有註解卻還是可以從命名規則上看懂內容。

對很多設計者來說,其實只要瞭解需求後就有能力作出系統來,但是為了防止競爭對手快速地經由程式懂得這個領域的東西,多少還是會使用混淆的方式來保護程式。它的作法是將指定修飾字的Data、Method、Class與Package都使用無意義的代號加以替換,使得看到內容的人不知那是做什麼用的。但是在使用上限制不能對映射取得的一切加以混淆,因為名稱改變後就再也不能對應到正確的目標。

在我的元件結構裡,由於大量使用Interface(public method),這使用混淆的對象只能是protected或是private修飾字的那些,保護的功能是無法達到預期效果的。

2008年5月26日 星期一

Q03 產出驗收文件(2)──使用手冊、API手冊與教學文件

系統是要給人使用的,因此完成後首先需要一份使用文件好讓使用者知道如何操作系統。使用手冊首先要說明如何安裝系統與啟動,接下來可依照Use Case依序編寫。操作相關的內容大致上有啟始狀態、操作順序、反應訊息與設定方式等小節;這些可以說是Use Case需求文件進一步的實作說明。

API手冊(包括Component與API)是給會碰到系統內部的人看的,需要維護系統的客戶或是未來接手維護與新增功能的人都需要這樣的文件,這樣才能夠知道如何對系統除錯以及應付需求的變更。如果API裡需要固定的幾個串連,也必須在手冊裡說明才不致造成誤用。試想,即使維護者已經想好需要做到的系統動作,可是對應系統動作的元件或方法很難找出來或是根本沒有被封裝為方便執行的方法,這樣是令人無法再繼續下去的(以前面臨的就是這種情況)。

為了讓使用者與設計者都能快速瞭解常用功能或方法的用法,有時會需要撰寫教學文件。教學文件的編輯會先收集常用的功能後,再編出一個實際的故事來串連所有的操作,藉由模擬真實的輸入與產出讓使用者親自體會實際的使用以期快速地進入狀況。

不過,教學文件只是輔助性質的,實際需要的還是使用手冊與API手冊這兩種。

2008年5月25日 星期日

Q02 產出驗收文件(1)──建立系統執行環境的安裝手冊

系統要能驗收,首先要做的當然是得讓系統可以啟動,能夠啟動需要的是準備、安裝、與啟動。作法都是依步驟順序記錄成文字搭配抓圖,就可以讓使用者直接依照內容操作並驗證結果;必要的話甚至可將安裝的過程錄製為影像檔比對操作。

準備的意義在於告知使用者軟硬體的限制以及需要預先做好的事情,安裝是告知使用者怎麼將系統放置到他的電腦裡,啟動則是讓使用者知道如何執行整個系統的進入點。使用者依照手冊進行這些動作並正常地執行系統,之後才能夠依照使用手冊的內容進行各項功能。

在維護的場合,我們同樣需要建立一份開發環境的安裝手冊,讓接手的人知道怎麼從無到有地建立起原始程式的工作區。裡面除了需記載安裝的步驟外,每個使用的Library出處及版本,還有最新版本程式碼check out與check in的地方也需要詳細地記錄。當然也要包括如何產出系統執行檔案的說明。

以設計的角度來看,使用、開發與維護的這些動作每一個都是目的,針對完成這個目的都需要一連串的流程、動作與例外處理,也就是需要一份說明文件。用這樣的思維去看待那些動作,自然會明白一切的需要都是很合理的。

2008年5月24日 星期六

Q01 輔助的文件(8)──將使用的文件與工具建立為範本

大家都知道,最底層的Data Model固定後才能往上構築出對應的API、Controller,這個道理也同樣適用在開發系統時的產出。當我們的設計結構遵循著一定的樣式後,同樣可以把處理各個區塊的處理工具及產出收集起來,作為未來直接reuse 的標準。

想法的根本在於Component的結構明確地切割為MFAV四個部分,把所屬的方法放置在對應的地方;在此前提下,就可以使用結構產出工具快速得到。不管是在哪個專案,只要全部的SA或SD都應用同樣的結構,快速產出與容易相互了解都會是顯而易見的好處。

在SA與SD階段都有個重要的任務:決定Use Case並分析設計出裡面的Flow與Action。Use Case List、Action List以及建立Use Case與Action關連的Flow是該階段的重要工作,在收集資訊的同時建立關聯的對應,並依照產出的位置所撰寫的小工具同樣具有reuse的好處。

還有一個階段是從實作的程式裡再取回需要的資訊,像是列出Component裡所有用到的Data Name、Property Name與Message Name(可再去記錄每一個資料是從哪裡取得的)、倒回Rose Model建立靜態圖表、擷取Flow或是Action裡的處理流程產出動態圖表等等。

輔助工具的reuse擁有的最大好處是節省很多設計、實作與記錄的時間,但是這些好處都必須在擁有自己的方法論並固定好自己的產出結構才能擁有的!

2008年5月23日 星期五

P16 測試的過程(7)──任何修改後都進行迴歸測試

每一個找到的問題點都會有許多執行流程經過,完整找出經過這個問題點的所有執行流程並加以測試,是保證修改後所有功能依然正常的迴歸測試的任務。

修改某個Method之後,使用Eclipse的Preference功能可以找出上一層有直接呼叫的Method,根據找出的Method清單再向上找出更上一層的Method清單,再根據更上一層的Method清單往上找……。在沒有記錄的情況下往上找,可能就像從一大群同姓的宗親中找出自己的所有直系血親一樣困難,如果有系統裡有類似族譜的記載不是就容易得多了嗎?任何一個人都可以很快地找出自己的所有直系血親。

如果沒有做出自動測試的話,每次修改都要重作一次影響部分的Unit Test、功能測試、使用者測試……等,可以說改得越多就測得越痛苦。不管是時間因素或是人為因素,只要跳過沒測試的部分都可能在系統裡埋下了潛在的問題。以經驗來說,系統或元件都會是需要修改的,因此能夠節省時間的自動測試就相對變得重要。

測試是為了找出系統有問題的地方,但是進行測試是需要代價的。如果能夠讓測試自動地被進行,問題也自動地被找出來,那該是多麼理想的一件事?

2008年5月22日 星期四

P15 測試的過程(6)──多執行緒與重覆操作的壓力測試

在Client-Server架構下的效能測試還要包括多個Client同時存取一個Server時的壓力測試,用來驗證同一個交易在上百個Client同時對Server存取時是否同樣在執行時間的限制內正常地動作。

客戶會指定最少要在幾個Client下要能正常運作,我們的作法大致會讓一台電腦開啟10-20個thread同時發送交易電文到Server,然後多台電腦同時重覆對server發送交易電文,連續執行客戶指定的時數後沒有發生當機或是記憶體使用異常的現象才算過關。

重覆性的測試在UI的部分同樣也需要,因為使用者會在工作的時數內一直對UI作各式各樣的操作。理想的作法是用能錄下使用者鍵盤與滑鼠動作的Robot軟體,依序錄下指定交易的輸入流程(可包含故意輸入錯誤的操作),然後設定為重覆播放,這樣就可以對UI進行不斷的操作測試。

同樣地準備幾個不同的電腦用Robot執行不同的混合劇本,連續執行客戶指定的時數後若沒有發生當機或記憶體使用增加的狀況才過關。這個測試有時會被跳過,改由使用者測試時進行較多Test Case的操作來觀察是否有異常現象;不過這種替代方式的操作數比不上Robot而會有某些狀況無法測出的缺點。

2008年5月21日 星期三

P14 測試的過程(5)──進行系統設定測試與效能測試

由下而上一層層針對操作功能的測試告一段落後,接下來要做的是系統環境與設定的改變測試。對於一些不提供給使用者變更的執行參數,一般會放在設定檔案或是資料庫裡,測試時就逐一改變存放於外部的設定值並觀察是否依設計影響系統的行為。

這時候的設定vs Package追溯表就顯得很重要:改了什麼設定會影響哪些Package,Package又在哪些Use Case裡被使用,根據這個表格才能確認是否一切都正常。通常系統設定測試的內容會在Test Case時一併考慮,以節省再度重覆測試Test Case的時間。

與功能的流程、動作有關的測試結束後,還會進行非功能的測試,其中最重要的是效能測試。客戶對於一些重要的Use Case(例如Client送到Server再回來的反應時間,使用者按下某鍵後的反應時間,或是Client Server間傳送的流量等量化的測量),都會要求在特定條件下耗費的時間上限。

對於執行時間常見的測試法是在一進入指定的方法就先記下開始時間,執行返回前最後一步再取一次現在時間並減去之前記下的開始時間,所得即是執行總共所花的時間。我大多測試七次後取中間的五次作平均並記錄下來。測試流量則是在負責收送的架構元件上記下傳送與接收字串的長度。如果有不合規定規定的現象則要研判原因並加以修正。

2008年5月20日 星期二

P13 產出設計文件(5)──記錄Use Case與Module的關聯

等到使用者測試快到結束時,流程上的程式大致也趨近穩定,這個時候就可以開始記錄Use Case與Module之間的使用關聯。這裡的作法與Component Flow、Component Action幾乎雷同,只是層次換到Use Case Flow、Use Case Activity(對應到Module Interface Method)來記錄。

在直覺的設計下,如果目標放在如何完成功能的話,除了會造成處理流程只有達到功能的分解動作而失去層次感,這樣一來作任何修改都面臨無法縮小影響範圍的風險,因為執行順序的分工或是執行動作的深度都涵蓋了所有的層次,使用從上到下都有可能被影響。造船明明只造一個船體並依需要分割艙房即可使用,為什麼要多造出數層看似無用的防水匣門呢?這當然是為了預防“萬一”船體漏水時能夠限制影響的範圍。

系統開發已近尾聲,其實已經可以發現設計的方向可以導成遞迴處理,只要我們的產出是有規律且有層次的,就可以切割為處理方式相同的多個層次。另一方面,設計者在設計時只需要將注意力放在切割後的空間,可以在這限制的範圍內儘情陳設需要的結構而不用擔心影響其他地方;設計的產出遵照設計準則的話,還可以使用一些小工具來產生必要的資訊與文件。

設計的時候多花一些時間安排結構,可以回收的是快速找出問題點、修改時可限制影響範圍、容易讓別人瞭解並使用、減少文件的製作時間以及穩定地重用並擴充。設計時損失的時間可以在未來數倍甚至數十倍地回收回來,這還不足以令人轉變開發系統的觀念嗎?

2008年5月19日 星期一

P12 發現動作組合後的陷阱

在很多時候,只看單一動作的實現步驟時感覺不到任何異常而認為合理,但不知為什麼只要把幾個動作組合為功能實作後,就會發生意想不到的問題。整合測試與使用者測試除了要確認完成功能所需的動作是否依序執行之外,同時也要找出動作組合後才看得到的潛在問題。

UI是最容易產生陷阱的地方。例如focus的改變可以即時呼叫requestFocus(),但是在呼叫在內部會以另一個Thread來進行動作,這就會產生時間差的問題;有時誤以為呼叫後會立即生效,就接著在後面寫上預期focus已經移動的程式,問題有時就這麼出現。另一個可能是有兩個動作都有對不同UI元件requestFocus()的動作,這在各自檢視動作時並無不妥,可是兩個動作在同一個流程內一起經過時,就會發現focus有時會落在另一個UI元件上。

在流程內應該只執行一次的動作(像requestFocus())被呼叫過兩次就有可能產生問題,從流程上來看應該是經過所有動作的同時判斷該不該作那個動作。因此含有那個動作的方法可能會增加“要不要執行那個動作”的參數,或者是把那個動作另外拉出成另一個方法在流程裡自行呼叫,一切端看怎麼做最合邏輯。

在這個階段還有可能作方法的重構,是一定要具備的心理準備,如果動作不能迅速而安全地搬移或拆解,就會在此時付出嚴重的side effect代價。

註:有時在組合動作裡難以決定要怎麼做,那時會在被重覆使用的地方加上synchronized以保證一次只有一個thread進入。即使做兩次是浪費資源,但是至少每一次的結果都是正確的。

2008年5月18日 星期日

P11 測試的過程(4)──讓使用者確認系統沒有問題

在經過一連串的內部測試,確定在較正常的操作下可以無誤地執行功能後就準備進入使用者測試階段。在此之前要保證輸入後的執行與傳回都必須可以正常完成,因為使用者測試主要是測試各種輸入的狀況並確認執行時的檢查與傳回之顯示是否正確。

進行的方式與Test Case大致相同,不過使用者會根據他們的工作習慣來操作,同時還會故意用錯誤的操作與不正確的值來檢驗系統是否能夠防止並提示。功能的執行大多是線性的,頂多會有流程的分歧點指向另一段線性流程,但是UI介面的設計就具有很高的複雜度,這是因為使用者的每一個輸入動作都是獨立的功能,許多UI的動作又會相互牽動造成影響。我們可以想見在使用者測試時會冒出許多大大小小的錯誤回報。

使用者會根據交易的生命週期來切割出每一個交易應測試的範圍,然後就從開啟畫面、輸入資料、事件觸發、資料檢查、訊息提示、執行功能、畫面切換、結果回報……等等不同的層面來檢查系統各個階段的正確與錯誤的處理方式。對於使用者回報的問題,我們必須具有快速分辨出問題屬於哪個Module或Component問題的能力,方能快速、準確地定位問題並加以解決;如果一時之間無法找出發生點,可以經由討論與測試來縮小範圍以便定位。

解決使用者測試出來的問題,最忌諱的是產生side effect與defect reopen,這會令使用者感覺系統極端不穩定。問題應該越解越少,問題數能夠持續收斂的系統是可以預期在哪一天可以結束測試的。

2008年5月17日 星期六

P10 測試的過程(3)──驗證Use Case Scenario的整合測試

Test Case的目的是要驗證Use Case的正確性。Use Case在其所屬的Activity Diagram裡已經被切分為Flow與Activity,而Activity已經於功能測試裡保證其動作合乎需要,因此Test Case的重心將會是在Use Case Flow控制的正確性。

測試的想法與功能測試、Use Case是相同的,先分析出Use Case流程裡執行路徑的最大數,再決定在分歧點要準備的資料是什麼,而那些資料必須執行哪些動作才會出現,然後就依序將那些動作寫成測試步驟;記得每一種測試方法就是一個Test Case(換句話說,一個Use Case有一個或一個以上的Test Case)。

由於Use Case Senarios的操作大多經由UI的動作才能備齊,所以這部分的測試都會由客戶的測試人員進行。使用者在畫面上輸入劇本裡的資料後執行,同時檢視系統反應出來的結果是否合乎預期。如果整合測試時已經測試過純資料的執行,那麼此時只需要注重使用者輸入資料的正確與否、執行UI資料的收集與執行後反應在UI上的動作即可。

使用者的操作會出現許多瑣碎的輸出入反應,這些也是會被回報為錯誤;不過同時也會發現實際操作的使用者測試出來的問題,有很多都是在需求、設計與測試時沒有顧慮到的。為了應付問題修正時的改變,保持彈性的設計真的是必要的。

2008年5月16日 星期五

P09 產出設計文件(4)──記錄Package之間的關聯

在前面的Package Diagram與Class Diagram,我都會先作出“擁有關聯”的靜態圖表,先檢視結構編制。使用的關聯一般不會另外作出圖表,因為每一個Package或Class都會使用許多其他物件,如果要將所有的關聯物件拉到同一張圖裡,一定會發生過於擁擠且根本看不清楚的狀況。

另一個困擾是在程式裡面的寫法並無法在使用Rose倒回程式碼時顯示出來,因此會需要再花人力去更新為真實情況。對於這種需要再額外花資源的更新我一向比較排斥,所以在查詢使用關聯的時候,都會使用Reference功能尋找以及檢視import內容來達成。

要記得關聯是在流程或動作時使用其他物件來完成功能所產生的,只要流程或動作有所變化它就會跟著改變。由於使用的關聯錯踪複雜,並不希望把使用關聯同時顯示在一張圖裡;這時的希望仍然寄望在Java構文解析,指定某個Package、Class或是Method後就自動列出我們想要的資訊。

在CMMI裡一般都會要求水平追溯表與垂直追溯表,在非做不可的限制下就以Excel勾選Package vs Package、Package vs Component這兩個追溯表,至於Package vs Class可以參照Eclipse Package Explorer取得,而Class vs Class就直接看程式碼判別吧。

2008年5月15日 星期四

P08 產出設計文件(3)──用適當的方式補完動態圖表

UML的支持者可能會推薦製作Sequence Diagram或Collaboration Diagram,但是就如我所提過它們只能提供線性的執行順序而不易表達出分歧流程的現象,所以我幾乎不製作這兩種圖;當然,如果公司堅持要產出這種圖,也只好咬著牙畫出來交差。

利用Component結構的分工,流程與動作都分別在放在Flow與Action裡,我也建議在每一個分解的動作使用註解來表達該段程式的意義。因此只要順著Component Interface Method進入到Flow,幾乎就可以掌握到達成功能的必要經過,所以我沒有要求過一起開發的人畫過任何一張Sequence Diagram或Collaboration Diagram。這一方面因為Flow就足以表達所要傳達的意義,另一方面也是因為讓分析時的Activity Diagram等同於設計的流程與動作。

在所有Component都具有相同結構時,以Java構文解析技術去拆解Flow的程式碼搭配註解,再呼叫製作流程圖工具的API來產出流程圖,等到這個程度時甚至連Activity Diagram都可以省略,直接在Component裡設計。這必須要具有相當經驗後才能這麼做以免一做下去就亂成一團,另外可惜的是Rose並沒有提供這種API。

製作文件的時間點我認為在Module作完功能測試或Component作完Unit Test後就製作它的圖表,一方面是因為測試後比較穩定而較少修改,另一方面則是趁記憶猶新時加以記錄。(以Package為單位)

2008年5月14日 星期三

P07 產出設計文件(2)──倒回Rose補完靜態圖表

實作進行到一定程度,系統內的Class一多起來,Workspace拉開後的Package、Class清單排成一大串,根本不知道其間的從屬關係。一堆被壓平為階層式排法的檔案並無法詳細表達三度空間的關聯,因此繪製靜態的UML圖表交待關聯是勢在必行的。

在最上層需要Package Diagram來表示Module間的從層關係,即使同樣是Module也要依使用與被使用的層次由上而下排下來,同一層的儘量水平地方放在一排,接著還需要一組Package Diagram來表示Module與Component間的使用關係。如此一來我們就可以在這兩張圖裡立即看出Module的層次與使用Component的關係。當然要記得要同時放上Modele Interface與Component Interface以顯示提供Method的資訊。

接著以每一個Package為單位,繪製各自的Class Diagram。其實在所有Package內部結構都相同的情況下,各別拉出的Class Diagram幾乎都一樣,差別只在於Component Interface、Implementation、Flow、Action、Properties、Model與Exception各自提供了哪些Method,未來在修改或新增Controller那一層時就立即可以知道Package裡提供了哪些功能。

當然,這些在Rose下可以直接由整個Workspace倒回Rose Model,只需要找人依實際使用狀況與Component結構拉好就可以保存;日後即使在方法上有重構的現象,由於Method移動的範疇已在軟體架構上提供,所以圖都不用調整只要再從程式碼直接倒回即可更新。

2008年5月13日 星期二

P06 產出設計文件(1)──邊寫測試內容邊補程式說明

在撰寫Unit Test或是功能測試文件的時候,雖然理論上只要根據Interface Method的描述就可以寫出來,但是為了測試的完整性還是仔細參考Method內部的流程與資料比較好。

理論上測試程式應該由設計的人撰寫而且不要讓實作的人知道,以便在不知測試內容的狀況下真正測出程式的正確性,但是在實際的專案裡大多得自己進行Unit Test 甚至是功能測試,得到整合測試時才會有其他的人幫忙。在人手不足的專案裡,如果必須在自己實作後撰寫測試程式的內容時,倒是個review與補足註解的良好時機。

完整的Unit Test應參考所有的流程分歧點與動作的參數。在準備Unit Test資料的時候可以先檢查待測試的流程種類並決定如何來進入不同的分歧流程;進行動作的檢查時再參考執行時需傳入的資料作最大化的測試。在檢視的同時一邊調整、一邊補上註解與Data、Method的Java Doc,如此一來就能夠兼顧Unit Test、Review、Refactoring與Comment、Documentation。

2008年5月12日 星期一

P05 測試的過程(2)──Module Interface與Activity Interface的功能測試

底層的Component的出版理論上都已經通過自己的Unit Test,因此在專案裡選用這些Component都可以先不用擔心會有錯誤,能夠將注意力放在與Use Case實現有關的Activity Interface與完成Activity功能的Module Interface。

測試的範圍同樣以Interface Method為對象,測試的方式與原則與上一篇的內容相同,不同的是Module與Activity都屬於專案的範圍,應由專案內安排人力負責這個測試。功能測試的目的是測試各個分解動作是否都依照設計的要求運作,當我們確認所有的動作都正確地被實作出來後,接下來就能將眼光放到更上一層的整合測試。

其實功能測試的作法與Component Unit Test幾乎相同,不過測試的對象都屬於專案上負責功能的物件,而且測試時通常需要建立較複雜的環境。Unit Test測試的是底層元件的功能,功能測試則是測試專案元件的功能,在設計時記得儘量讓專案元件封裝底層元件;原因是專案元件提供的功能會符合專案Domain的意義,而底層元件只是達成特定計算機行為之瑣碎功能,並不適合專案上直接使用。

2008年5月11日 星期日

P04 輔助的文件(6)──從Excel文件產出Unit Test空殼

在開發元件的同時,可以從Excel文件裡根據Class Name與Method Name來產生Unit Test的程式空殼,這樣可以省掉一點從每個Class逐一產生Unit Test Class並勾選待測試方法的單調工作。

以Package為單位,每個要測試的Class應該要產生一個對應的UT Class放在相同名稱的Package;UT Class內應有的UT Method則以Package Interface為基準先行建立。在每一個Package的結構都相同、Constructor也相同的情形下,setUpBeforeClass()與setUp()兩個預備方法都可以預先建立預設型式加以修改,甚至tearDownAfterClass()就固定呼叫disposeObject()即可。

產生空殼並準備好測試前的程式碼後,就依照上一篇的原則撰寫測試程式碼進行Unit Test。每一個Class測試完後,記得依每個Project擁有一個Test Suite的原則,把該UT Class加入到Test Suite裡,未來可以用Project為單位,一次測試整個Project的所有Class的所有Method。

對我而言,未來還會在Interface Method後面定義各種不同的Method測試參數種類,在產生測試程式空殼時順便產生各種參數的呼叫程式碼,如此一來只要再補上Assert的內容就可以再加快Unit Test的準備速度。

2008年5月10日 星期六

P03 測試的過程(1)──對所有Component Interface作Unit Test

Component與API可以說是基本的積木,所有的系統主要都是先取用元件庫裡的元件根據客戶需要慢慢堆疊出來的;正因為會有很多系統都會採用基本的元件來使用,身負各個系統“絕對重用”責任的元件更提供有正確的功能,才能夠讓各個系統無後顧之憂地專注在開發上,以免因下層的改動而導致上層的混亂。

正因元件庫是系統的根本,所以元件與API的每一個動作都應該保證正確,才能夠再測試上一層的流程。在Unit Test的時候,主要是在確保Component Interface Method或API方法的功能是正確的,此時可以不用測試到裡面的每個Interface以免累死;不過當實作的Class有數個時,每一種實作Class都必須建立起來然後完整地測試一遍。

完整的測試,是指方法內的流程所有的支線都涵蓋在測試的範圍裡,而且每種狀況的所有輸入資料組合都有測試。首先計算流程所有的分支點,再列出改變執行流程的所有集合,接著再根據每一段流程片段所應處理與不應處理的資料加以記錄,列表為測試的內容建立Unit Test的內容;每個測試方法都依此方式建立,就能夠建立趨近完整的Unit Test。(撰寫完整的Unit Test所需的時間會比定義及開發功能還要多)

如果開發後的Component或API方法抽來換去的,原先為這個方法寫的Unit Test幾乎得全部作廢重寫,相信沒有人會這樣做的,因此定義好Component與API的功能方法並維持定義的不變,是建立方便重用元件庫的基本要素。

2008年5月9日 星期五

P02 所有修改都要為變動範圍註明原因

修改程式的原因如果不是為了重構就是為了除錯,無論如何都會有一個原因驅使我們去變動。在修改的同時,會希望把變動的原因以註解的方式記錄在程式碼裡,這樣就不會讓這次的改變在未來被淡忘。

註解除了記錄單次的改變之外,另一個目的是減少維護時的痛苦。對於後來接手程式的人來說,最好的狀況是只看現在版本的程式就能夠獲得所有版本的變動資訊,否則面對什麼註解都沒有的程式,勢必得拉出所有版本的內容才能明白哪裡有修改,基本上拿到一個版本號碼超過十的程式就不會有人再拉出來比對。

從這個角度來看可以明白程式裡適當的註解有多麼重要,可以讓所有人避免去比對多個檔案才能找出差異,而讓差異在同一份文件就看得出來,而且知道每一個差異發生的原因,這會是讓其他人迅速瞭解內部作法的捷徑。

2008年5月8日 星期四

P01 儘早來幾個Scenario Prototype

設計時幾乎只是純粹的思考,就像事先擬定工作計畫一樣,決定好的工作順序有時得開始實行後才知道執行順序是否正確以及動作是否需要增減。設計上雖然可以經由Review來修正看得到的問題,但是人為的動作都可能有盲點存在,一群人一致通過的設計並不代表一定可行。

因此在設計的初期,決定好架構與Module後,會針對系統的啟動與幾個重要的執行功能先行試作幾個Scenario Prototype,藉此找出在設計時被忽略掉的動作。在2007/11的專案裡,架構鋪設好後,就為了要不要先作一大堆的需求與設計文件頭痛,最後決定面對現實,先確定幾個重要功能再說。

在製作Scenario Prototype時,重點在於檢查流程的處理過程,至於動作的實作可以直接傳回要用的值來處理,而不要堅持連動作都要做好才測試以便加快速度。在測試的過程中一方面檢視自己的設計,一方面測試是否都執行到應該要做的動作,先確定系統重要的骨幹可以正常執行後再往下設計會是較安全的作法。

2008年5月7日 星期三

O17 Java構文解析的應用理想

回到現實面來看,設計都希望有設計文件的產生。如果依照一般的方法論在先作出設計文件,在實作或測試發現設計有錯誤的時候,除了改程式之外還要多出修改文件的動作;如果直接把想法寫成程式,在完成系統後還要再依程式內容重新寫出文件。

依照我的程式結構,流程是被集中在一個Method裡的,此時運用Java構文解析可以有三個方向可以努力:一個是分析出使用到的其它元件、一個是把流程轉換為流程設定檔,另一個則是將流程輸出為流程圖。當然,首先要做的是把程式碼讀入到處理的程式裡;目前可以運用的技術是JDT。

定義好規則之後,才能寫出對應規則的程式,再運用可以處理規則程式碼的元件產出我們要的東西。讀入程式碼後,我們可以按照需要輸出;以產出流程圖為方向的需求下,只要找到提供適當API的工具,就可以依照程式碼解析出來的內容產生對應的表格。

這樣做的好處是程式不管怎麼修改,只要符合規則就可以立即產出對應實際內容的文件(關聯與流程)。

2008年5月6日 星期二

O16 流程使用Flow Engine實作的可能

Component結構是把焦點放在最容易被修改的Flow上,努力將內部的使用對象依特性放置到不同的Interface裡。這樣的作法在Use Case上接下來的可能,就是使用Flow Engine來實現以定義檔來處理流程的可能。

想像有流程產生器時,程式寫作除了Action Method需要多加注意之外,其他部分的設計就變得很單純,加上Component, API已經建立在元件庫,就可以定義執行流程後透過Flow Engine使用映射來執行其他部分或其他Component的方法。想想這樣的系統開發是多麼棒的一件事。

將執行Method需要的Class Name、Method Name與輸入參數定義在動作,流程的控制可以根據執行動作可能傳回的結果設定接下來要執行哪一個動作,依此推疊出一個功能完整的流程。Flow Engine本身會是一個要額外開發的元件,如果想為流程編輯設計一個編輯工具又會是一個更大的元件,但是往這個方向開發卻可以有更多的回收。

Flow Engine需要設計的動作是有限的,經過完整測試後就可以不再變動;使用者需求的變動都被轉化為設定後,只要不出執行、判斷的範圍就只會改變設定檔而不用動到程式。重點在於系統任何的程式變動都需要經過一層層的測試才能確認是否沒有問題,使用流程設定則只需要測試過程是否正確即可。

2008年5月5日 星期一

O15 理想元件結構的調整

Component在我的想法裡會有19個檔案,這是為了解決之前提過的現象而鋪設。重點在於Flow與Action,在設計的流程裡經由Impl進入,參數參考Properties,處理的資料放在Model,處理的動作放在Action,唯一存取外部的是其他元件的Component Interface Method;Action裡也是類似的作法。在這個結構裡,接手的人可以把重心放在Flow與Action內部而不用到處找東西放在哪裡。

如果所有的元件結構都是這樣,在遇到有人新接手的時候,只要先講解Component的基本結構再說明現行系統的Package Diagram,最多只需要一天就可以讓新人開始追蹤與除錯。但是複雜的結構帶來的是設計與實作時必須在19個檔案中工作,手忙腳亂再加上偶而忘記把東西放在哪裡卻是免不了的,這也相對地更降低了產能。

在確定不需要某些需要的時候,可以縮減結構的複雜性。在各個部件裡,如果確定不會有改寫的需要,可以不要放置Abstract Class;Action動作不多時可以取消而以Method的型式放在Flow裡;Properties也可以直接用Java Properties或是基本Data Model的方式放在Impl裡取用。在堅持應有的Interface與Class下,元件的檔案數量可以減少到10個。

2008年5月4日 星期日

O14 設計與重構就像光與影的共存

設計的人雖然已經作出可行的系統結構,但是實作者是否有與設計者一致的觀念是一個問題,設計者對於這個系統的想法能不能傳承給實作者是一個問題,另外實作者撰寫程式時臨時發生的錯誤又是一個問題。

對於實作上較沒經驗的人,即使提供已經設計好的Method,實作時還是會發生責任錯置的狀況。在大陸的專案裡,有個集合物件本身擁有多個子物件,在需要記下所有子物件的現在狀態的功能實作時,把狀態記錄在集合物件而非要求各個子物件記錄自己的狀態。這是一個很明顯的責任錯置。

即使是設計相當有經驗的人只要一時閃神放錯地方,未來還是得重構。在設計的時候,我起初把數種不同元件的event處理都放到相同的Package裡,後來裡面Class實在太多太雜,只好依所屬元件再分開到幾個package存放,使其成為一對一的對應。

設計是把完成功能的動作放置到最適合的地方,但是沒人設計時能一直保持分工正確的,也沒有人寫程式不會出錯的,因此我們必須不斷地檢視自己所作的設計並予以調整。設計與重構就像是光與影的共存時常會發生,這也是我一直堅持要設計出只需移動方法就能完成重構的系統架構的原因。

2008年5月3日 星期六

O13 動態的應用(3)──Data

Data的存取是使用Class與Method混合搭配所產生的運用。我使用的地方在於Context的再生與Bean、Property內容的生成。

製作Context的輸出首先得要有易於列出現有資料內容的設計,然後要將列出來的Data名稱與值轉換為字串,這組字串可以存放在任何地方。再生的時候從字串裡逐一取得Data名稱與值再動態呼叫資料名的setter便可以復原儲存時間點的Context。

Bean的生成可以想成是Context再加上一個動態的Class,也因此定義的方式就可分析為Class與Data的組成。定義時需要特別注意Data的值為集合或是物件的狀況,集合主要有List與Map,要設計出集合類型與其中值的定義法;物件的文字定義或許可以依循bean的方式,只要維持固定的模式即可。

目前我選用的是用Hibernet的XML定義方式來存放執行時期所需的Data。

2008年5月2日 星期五

O12 動態的應用(2)──Method

另一個動態的應用是Method,是根據取得的字串對應指定的Class呼叫特定的Method。應用這個功能我們可以把想要執行的動作放到外部的字串裡。這樣的好處是可以把注意力放在提供執行方法的Class,而不用跟著變動執行呼叫的部份。

最常運用的是Data的setter與getter,根據屬性名稱組成存取屬性的方法並自動呼叫;雖然在提供存取的Class裡必須提供所有的getter與setter的方法,但是在呼叫的那一端就可以使用簡潔的程式碼來應付所有的Data存取。

除了資料的動態存取,利用Method的映射還可以作出執行的流程。讓執行的順序與內容定義在外部檔,用一個元件負責流程的處理,依照順序執行對應的動作(當然,動作要集中在一個Class或一個Class僅負責一個動作都可以),使用這種方法可以把變化的部分抽取到文字檔案的定義而不需要改動程式。

對系統而言,只要更動程式的任何一行都必須重新測試所有的影響部分,即使某些流程的改動很單純,但仍然需要完整的測試,一些要求更為嚴謹的客戶甚至會詢問改變的原因與影響。使用外部定義可以避免這個問題,這是因為定義的意義都已經定義為對應的方法,改變只限於傳入的屬性。

2008年5月1日 星期四

O11 動態的應用(1)──Class與Constructor

不管是Class的生成或是方法的呼叫都儘量使用映射的機制。這樣做的好處在於可以把字串動態地變成指定的Object或執行Obejct的特定Method,不必像初學者般運用大量的if-else判斷才能定位到應該要執行的程式。

當所有物件的規格都有定義Interface時,我們就可以在使用該物件的地方定義使用Interface。當這個Interface只擁有一個實作Class時可以直接new出來使用;但實作的Class種類變多時,就能夠依狀態的不同取得不同的Class Name再使用Class.forName(className).newInstance()拿到指定實作類型的物件。

生成物件時如果需要傳入參數,則需要取得Class的Constructor,取得時需要傳入Constructor所需的參數類型陣列;呼叫時也要將傳入的參數依序放在陣列裡,然後就可以取得指定的物件。一般來說會把Class Name加上id放置在設定檔裡,依不同狀況取用不同id的設定,如果需要傳入參數的話,則將相關設定放在同一組id下。

記得物件範圍的切割度會影響作法,物件範圍涵蓋得越大則越可能得準得許多應付不同需求的Class,但是如果切割得過小也會造成花費時間過多。這一直都是個值得深思的問題。