2007年12月31日 星期一

I15 紙上談兵誰都會,真槍實彈的話呢?

“修改程式後應該通過所有相關的測試,才能保證程式是正確的。”我問過認識的所有軟體工程師,每個人都同意這個說法。再進一步問:“那麼修改程式後要如何取得正確範圍的相關測試呢?”,至今還沒有聽到有人提出理想的解答。(註)

這就是理論與實作的差距。很多聽起來很美好的理論,像是軟體外包會減少專案支出,軟體工廠的元件化能重覆使用程式可以降低成本,這些都因為沒有可以做到的方法而使得講求實際的專案根本無法導入。理論與實作在大家的口中都是無法串接在一起的平行線,軟體工程的理論變成討論時抬槓的內容而已。

在撰寫Blog後兩三個月內,心裡理論串接到實作的關節差不多都打通。在2007/11/12凌晨為了思考如何在專案裡套用軟工的作法,前前後後想過各個細節的作法、作用與影響;確認一切都能依序做出後,又再思量要如何向主管證明這樣做的好處以及施行的內容等等,到最後幾乎整晚都沒有睡好。之後的幾天,以心裡的想法與同事、主管們討論過實施的細節,慢慢地建立起每一個專案產出物件的關聯,同時套用設計工具並實作出一些簡單的開發工具來加速開發。

這真的是一條很長的路,在很多地方都需要認真思索該怎麼做、如何做才比較好,也難怪絕大多數程式設計者只想省事地做出符合功能的系統而不願花精力走這條路。但是,我以近兩個月內以這個模式迅速建構起兩個結構穩定系統的實作經驗告訴各位,邁向理想設計的路途雖然很難走,但是走過之後就絕對不會再用以前的方式做事……。

註:同事告知有xUnit Test可以作出涵蓋專案裡所有Use Case的測試,刻意在修改的程式裡傳回錯誤的值就可以立即發現哪些Use Case沒有通過而得知這段程式影響了哪些Use Case;不過要寫出好的xUnit Test內容投入的資源可能與開發這個專案相去不遠。而我自己的解法是先知道程式所在的method與使用它的所有Interface Method,然後去查詢所有使用到那些Interface Method的Sequence Diagram,所屬的Use Case Realization即是答案;當然,要記錄下所有的關聯也得花不少功夫。一切就看自己喜歡哪一種方式。

2007年12月30日 星期日

I14 進化到功能流程定義更難

想要再進化成像SOA那樣理想化,只要畫流程圖就能做出功能的境界,除了元件庫裡的元件要跟得上需求之外,還必須寫出更複雜的執行架構。

再往上包裝為功能流程定義來應付使用者不同的需求很困難,因為使用者想做的動作種類太多,我們必須先推測有哪些地方會有改變,而且還要預測使用者”可能”會怎麼改變。這些預測的動作都要先準備好對應的元件,才能夠讓使用者不要寫程式而只需要設定就可以達成。所以快速新增動作的元件庫是必要的。

執行架構與元件庫都準備妥當後,製作一個繪製流程圖的工具並提供所有元件讓使用者選用,在流程製作時指定已經具有完整功能的元件或API,如果一來任何一個人在客戶端談好需求並拉好流程圖設定,不用寫任何一個程式就可以立即完成一個功能。另外,要有將部分動作提取成子流程再呼叫的機制;將流程也依架構設計的原則分層負責自己該做的事,將可以組織成架構分明的系統。

這兩年在工作上遇到印度最大的Ta Ta公司到台灣作生意,在作法上他們就採用類以這樣的方法來開發系統。雖然現今在特定領域裡的元件與彈性都還不夠理想,但我很訝異其他國家已經進化到這個地步了;我也相信再過一段時日等他們的元件庫壯大後,所有用程式做功能的公司將都不是敵手。

我深信,能夠把系統開發進化成功能流程定義的公司,將會是最後的贏家!

2007年12月29日 星期六

I13 用程式包裝執行架構難

為了解決程式的僵化問題,把功能流程切分為需求層級與動作層級是必須的。動作層級是包裝在元件裡通用的,注重動作的正確性與所有錯誤狀態的回報;需求層級則是依使用者需求隨時客製化,保持變動的部分。元件強調的是動作的一致性以便重覆使用,節省開發的成本。

需求層級的部分最低要求是直接寫程式達成,但近年來所流行的系統架構則慢慢把一部分需求的製作帶到用設定檔的方式。如果系統架構是自己開發的,那麼需要設計出設定工具與執行環境;對這樣的一個架構需要定義出適用的範圍與設定的方式,負責開發的人就不是只應付一個功能,而是要應付在定義範圍內使用者所有可能的需求。這樣的設計難度已經提高很多了。

有些設定僅是設定實作的Class是哪一個,執行時只要叫起指定的Class並使用就能運作,不過在直接做時只需要一行new的指令,到架構設計時需要讀入設定檔再使用Factory Pattern來產生實體以執行的方法,相對已經變得複雜;再加上有些設定檔必須有流程的控制,對於能力較不足的人將會是個挑戰。

與用程式寫功能比較,若能包裝成執行架構就能有較多的地方不需要寫程式,如此可讓一般不會寫程式的使用者來參與系統的開發而且可以加速功能的達成,不過指定執行Class的實作還是需要會寫程式的人來做。

2007年12月28日 星期五

I12 用程式實現特定功能易

回想起2001年第一次用Java寫出公司需要的系統時,功能全部都符合需求,但是被評論為裡面寫法跟C語言差不了多少。那時連SCJP都沒通過,觀念不清楚的情形下寫得亂七八糟是可以預期的;但這也表示即使是亂寫,也是可以寫出堪用的系統。

用程式實作已知的功能比較簡單,因為功能與動作都是固定的,只要明白功能想做的事就有法子“組裝”出想要的模樣;即使寫錯也沒有大礙,頂多砍掉重寫這一塊便是。在求功能正常與開發快速而不注意影響與變更的前提,可以說任何一個人都能把系統做出來。

只是以這種思維去開發系統,得到的程式碼很可能品質參差不齊、沒有經過適當的測試同時也沒有記錄使用上的追溯。在程式包含太多功能的邏輯又分布在與功能相關的Class中,這表示該元件已經被特殊化,很難適用在其他只有些微差異的類似需求上。此時Copy-Paste就會出現,生成另外一組程式碼再直接修改內容去套用新需求。空有一大堆程式碼,卻沒法把相關的需求封裝在同一組程式碼裡。

僅注重眼前功能的實現,是造成系統程式僵化的最大原因。

2007年12月27日 星期四

I11 架構僅是外殼,設計方為本質

最近工作上開始接觸J2EE與SOA,發現到他們都是架構上的延伸使用,最重要的本質還是在Interface的訂定與內部的設計。

J2EE裡有三種EJB:Entity Bean、Sessiong Bean與Message-Driven Bean。以本質來說,Entity Bean相當於Data Model、Sesstion Bean等同於Controller、Message-Driven Bean則類似Event Listener。定義出Interface與實作Class後以EJB的型勢包裝起來就適用在J2EE的環境裡。

SOA也很類似,先準備好Web Service元件;其內部可以再使用其他Web Service元件、EJB或POJO做成的元件,重點同樣在於定義的Interface與實作Class。比較特別的是,在組成客戶功能的時候是使用製作流程圖的方式,拉進元件後判斷執行的結果來決定下一步要走哪一條路徑,在元件備齊的狀況下不需要再寫任何程式碼就能組合出符合客戶需求的功能。所依賴的是整合性產品解析一堆描述資料來“自動產生”功能裡的動作。

雖然應用的範圍如此廣泛,但可以解析其內部作法:首先是做出帶有Interface的元件,接著可以往上包裝成EJB;以上兩者都可以再包裝成Web Service。使用上的技術規格是需要去學習的,但是Component的設計本質則是我目前著重的方向。

2007年12月26日 星期三

I10 Iteration開發方式原則

要在專案裡使用Iteration,必須先對系統有初步的架構設計才行。因為我們需要知道功能之間的使用關係,把下層被使用的功能排在前面,上層使用下層的那些功能排在後面,再上層的功能則排在更後面。

以設計系統的順序來看,硬體架構與其相關的橋樑功能必須在第一個Iteration裡,因為所有的功能都是架構在這之上。(可以想像一下其他功能先做時,到最後沒法子完整測通時的窘態)其次就是子系統的畫分與排出優先順序,當然得要參考子系統間的使用關聯與客戶想法。

在子系統裡第一個要做的絕對是Data Model,資料模組沒有定義好還真不知道程式要如何生出來。接著就條列出子系統中所有的功能,一樣是參考功能的使用關聯與客戶想要先做的來安排。最後把排列好的架構、子系統、Data Model、功能放到一張表裡略估工作人天,專案經理參考系統區塊與工作時數並依其智慧由上到下框出每一個Iteration的範圍,再加總Iteration裡的工作時數安排時程。

千萬要遵循的原則是:放在底層的功能一定要先做好!否則等到系統快完工時,最後才做的架構發現有問題而必須更換成變動較大的方案時,我敢保證這個專案一結束就會樹倒猢猻散。

2007年12月25日 星期二

I09 如何學好英文

說起來,語言的使用也與程式設計扯上很大的關係,因為語言的使用同樣可以分為文法(Controller)與單字(Action),適用程式設計的思維。

首先我們可以把文法的句型視作為Controller,每一種句型都可以對應到一個想表達的想法(功能需求)。所以心裡在有某種念頭時,要能夠立即選用出對應的文法句型來;這個前提是心裡必須要存放所有文法句型以供需要時挑選。

再來是單字(Action)的挑選,單字的詞性可視作為Interface,整個句型的組成就變為只要挑選同樣詞性且符合心裡意義的單字即可。如此逐一把挑選的單字放入句型,完成後迅速在心裡從頭到尾檢查(測試)一遍,改掉有問題的單字並重新檢查直到沒有問題後就表達出來給對方知道。挑選單字時,當然心裡要有足夠的單字庫可查詢,否則你能表達的英文永遠只有那幾個字,所以背誦單字且明白詞性是一定要下的苦功。

要用英文表達時也切忌先用中文造句後再轉換為英文,應該在心裡組成“句子的意思”後直接成為英文;因為先組成中文於再轉換為英文,會造成先有中文變為意思再對應成英文的轉換時間差,同時會有使用英文卻得連中文都一起綁住,使得句型與句意的使用受到另一種語言的影響。想法直接組成英文句子是最理想的方式。

註:師長與同事一致認為我的英文實在不怎麼樣,如有人受本文影響改變學習英文的方式卻未見進步,本人恕不負責!

2007年12月24日 星期一

I08 廚師改行設計程式應是好主意

廚師是利用各式各樣的食材組成佳餚的重要角色,一名出色的廚師必須先知道要做的菜是什麼,還要明白每道食材的意義與可能造成的影響,才能選擇出最適當的食材並參考其影響來制訂出最理想的步驟以完成該道美食。

現在暫時把要做的菜視為需求、把烹煮的流程控管視為Controller、把烹煮的步驟視為Action、食材視為Component,我們可以改寫第一段的最後一個句子:一名出色的廚師必須先知道要做的菜(功能需求)是什麼,還要明白每道食材(Component)的意義與可能造成的影響,才能選擇出最適當的食材(Component)並參考其影響來制訂出最理想的步驟(Controller & Action)以完成該道美食(功能需求)。

在調理的時候,廚師還要視臨場狀況來決定是否增減某種食材或調味料,或者些微調整作法等等,就等同於系統設計時的狀況判斷與例外處理。由以上的觀察可以推知廚師除了重視需求的達成外,還會注意到使用元件的效果與影響,進而研擬出正確的進行步驟與方法;相信對具有程式設計思維的廚師加以基礎的訓練後,可以適用於開發系統的工作。反之,程式設計師也適合去當廚師。

雖然有位同事同樣具有烹調美食的能力,但是如果有人在專案缺人時找一群廚師加以訓練後就開始開發,我絕對不會擔負任何責任的;反之,如果聚會時直接把食譜與食材丟給程式設計師,最後搞到大家沒東西可吃時,我也同樣不會擔負任何責任喔。

2007年12月23日 星期日

I07 設計常見問題(5)──問題並不在方法論

以上提到的幾個問題時常發生在程式設計領域裡,幾乎就是系統開發失去彈性的主要原因。

問題的成因是因為設計人員對於客戶的需求只在心裡產生對應的模型(系統做到剛好滿足需求即可),只注重達成現在的功能而忽略了物件與動作的關聯及責任,以致於在客戶提出一個踩到痛處的小變更時,整個團隊改到天昏地暗。

我刻意對方法論隻字未提,因為心裡的設計層次正確時,不管用哪一種方法論來開發都能夠保有彈性。相對地,如果思考的模型只為了做出來,那麼不管用哪一種方法論,你的專案同樣會一直修改,直到不成人形再棄置一旁,然後每次的下一個專案又另起爐灶重新來過。

雖然設計的成果與方法論無關,但是OOAD的思維談的就是物件的佈置、責任與關聯,這是XP的4項價值觀和12條實踐法則裡完全沒有強調的東西。這兩年來自己在程式設計方面的成長,要歸因於接觸OOAD後有所感觸並努力思考與實踐所致。

2007年12月22日 星期六

I06 設計常見問題(4)──用重構來解決問題

“重構是解決程式裡所嗅出的壞氣味”。我是不大明白理想的重構應該針對什麼樣的範圍做什麼樣的事,但是“現在隨便寫寫能動就好,反正未來還會再重構的“,卻是不少系統設計者拿來作為沒有花時間做良好設計的理由。

在我的理念裡系統是一層一層靜態物件搭配動態方法所組合起來的。從最底部的硬體架構搭上溝通橋樑、在上頭佈署元件結構再搭配定義介面、再向上有程式類別與方法呼叫,甚至連傳送的資料也有層次的不同;每個層次又有不同的參數、反應與控制動作。整個系統就像是用積木堆疊起來的巨大模型。

重構如果能侷限在局部不影響其他任何模組應該是最理想的結果,但是我所看過的重構卻是動不動就產生一個新的類別與介面,或是更改物件繼承與實作的關係。架構與物件間層次的變動會影響構築在其上的所有程式,物件繼承特性的改變也會影響內部的行為與使用的時機;這樣的改變將會造成許多與之相關的程式變得非常不穩定。

如需上述的重構應該在上頭還沒有程式構築其上時儘速進行,等到系統都快完成了才發現要變動架構或物件特性,就像民眾都把房子蓋在某片土地上後政府才說那土地是要蓋公園的,或者是像結婚典禮快開始了才發現配偶的性別不是原來我們所想的。有些改變太晚發生時,造成的影響實在是太深遠了。

2007年12月21日 星期五

I05 設計常見問題(3)──邏輯散落多個層次

在D02裡提到Use Case裡的邏輯應被拆分為需求邏輯與動作邏輯。需求邏輯是根據客戶的規格加以規劃應有的動作與順序,動作邏輯則是與需求無關而是以能夠完成指定動作為目的;後者的作法應是固定不動,動作的微調用設定與參數來改變,前者需去適應客戶可能有的改變,再依動作的規格找出適用的元件或專案上的特定方法。

需求邏輯與動作邏輯的分工可以讓重覆使用的元件與專案的客製化差異能同時存在並達到相輔相成的效果,其中的使用界限是非常明確的。但是一般在設計時很有可能發生需求邏輯散落在功能邏輯裡做事的問題。

客戶要在進行列印動作後在畫面顯示一個對話盒告知成功或失敗原因,在我的設計觀念裡訊息的內容與顯示方法都該在需求邏輯裡處理的,不過專案裡如果沒有人去定義層次與該在哪一層做,訊息的部分有時會不小心被寫進印表機的動作程式裡。這便是所維護產品裡的一個實例,而且兩組印表機的不同實作竟然是一組沒處理訊息而另一組有。

在訊息處理寫入印表機動作的那組實作,在不同專案裡想把訊息顯示在畫面欄位,此時必須加一個顯示方法把欄位傳進去才能在裡面顯示。但是這樣一來印表機程式就綁上了顯示的元件,要不然就是重新改寫印表機實作;改寫比較符合設計原則,但是改了以後之前使用訊息處理內建的那些專案要怎麼辦呢?

專案裡時常發生東西改來改去的狀況,而且一改就是一大片,其實那一大片都是自己造成的……。

2007年12月20日 星期四

I04 設計常見問題(2)──動作責任定義錯誤

在系統裡兩個物件之間的互動動作,有的時候並不能很明顯地判知該動作要由哪一個Class提供動作方法;不管是客戶提供的需求模糊或是設計者的觀念不佳,都有可能使得動作的責任定義在錯誤的一邊,造成物件的層次被打亂而難以應付需求的改變。

比如說人與車的關係,人可以駕駛車輛,所以有Person.drive(Car)的方法,在裡面再執行Car.move()讓車去計算移動的內容。如果設計的人一時不察,把車子移動的計算寫在drive()裡再直接設定Car的相關資訊的話,雖然呼叫Person.drive(Car)這個功能同樣可運作,但是往後把Car移到其他的系統時就會發現Car根本不會move()。

動作責任的錯置在功能測試上很難測試到,即使是Unit Test裡如果測試程式由設計者寫的話,那麼他將會用自己的定義來作Unit Test而沒測到。問題將會直到某個情境下,Car被獨立出來後才會被發現,但是到那時錯誤的drive()方法已經不知散落在多少地方而難以修正。

動作的責任定義錯誤或是內容遺漏都是負責設計的人在依劇本切割時,必須戰戰競競地時時妥善加以定義。唯有所有Class都僅守本分才能讓系統更有彈性去改變。

2007年12月19日 星期三

I03 設計常見問題(1)──物件範圍沒有對應

有一種心理測驗是這樣的:在紙張印上一團不規則的黑色區塊,要人說出自己連想到的第一種物品,藉著幾種可能的答案來推測受試者心裡的想法。在設計Data Model時也有點像這樣;每個人都知道要根據Domain設計出系統專用的Data Model,可是同樣是專案的成員設計出來卻風格迥異。

舉個生活上的實例來看。客戶描述他們需要的是“椅子”,藉由椅子可以提供他們靠著坐或可以站在上面拿高處的東西;需求講得很單純,於是A廠商想的椅子是一體成型的木椅(一個Class),B廠商想的是有滾輪與腳座的辦公椅(一個Class內含三個Class)。

當然兩種椅子都能符合客戶“目前的需求”,不過客戶可能視臨時的需要而改變需求的內容;例如:客戶希望椅子是不能推動的,而且椅面距離地面的高度增加五公分等等。我們常去抱怨客戶亂改需求,但我們必須知道客戶大多是根據實際需要來改變小地方的;當然,要求改成按摩椅的類型真的就太過份,因為設計理念已經完全不同,怎麼改都會很淒慘。

回頭看合理的兩個小變更,A廠商的椅子本來就沒有輪子所以不用改(同時也慶幸不是改成加上輪子,不然真不能看),但是高度少的五公分該怎麼辦?要換掉四隻椅腳再釘回去好,還是在椅腳下加釘五公分木條好?要是長度再改變的話該怎麼辦?相對起來廠商B就好辦多了,要不要輪子,要多高的腳座都可以拆卸下來換成符合需求的物件;不過在需求沒改變時就相對地多花了不少功夫。

設計是為了應付未來可能的改變,所以我選擇的是廠商B的作法,儘可能地把一個物件拆成多個可抽換的組件,以期望符合更大規模的變更。使用廠商A的作法雖然比較快,但是一旦面對臨時的改變就會顯現出規格訂死的痛苦。

2007年12月18日 星期二

I02 eXtreme Programming的潛在問題

XP的開發方法論,由於強調是輕快與應變,註解只要寫在程式裡即可,所以受到很多人的推崇。XP強調4項價值觀和12條實踐法則,著重在功能設計與測試,並經由不斷地改變與整合保持系統的正確運作。不過支持者是因為這樣做不用寫文件而開發較快,或者是真正遵循它的設計理念而採用就有待商榷。我所看到較大的潛在問題點有以下幾個,也因此不偏好用XP開發系統。

設計者的功力會決定系統的架構。設計層次概念不夠好的人想到什麼就做什麼,極易造成結構的混亂。雖然可以透過回饋與重構來調整,不過架構先天不良時的重構,與我們現在亂寫一通再調整似乎沒什麼差別。

程式中留下註解的質與量是否足夠讓別人看懂?沒有文件的狀況下,所有說明資訊只能依靠口授與註解。嘴巴說的大家可能很快就忘記,寫下來的是不是別人想要知道的呢?

重構時提取的共通部分,是否確保定義了動態抽換的介面?很多時候為了求快,使用物件時就直接用了,等到後來發現那裡應該要做成可擴充的介面時,又得動一大堆程式。而且程式只要一動就有可能出現Side Effect。

測試要怎麼測才算完整?測試的內容根據的是什麼?測試若太含糊勢必會留下很多錯誤,每次出錯就得修改再測;要是測試的內容定義的漏洞很多,將又可能是一個縫縫補補的大漏洞。

2007年12月17日 星期一

I01 OOAD設計基本原則

物件導向設計(Object Oriented Analysis and Design)的精神在於將生活中的實際物件對應成系統裡的程式物件。簡單地說一下我自己使用的設計原則:

靜態物件(Data Model)的拆解原理如下:
有形的物件以一對一的原則生成Class
物件內能拆解出來的獨立物件也要有對應的Class
生成物件Class的同時為之定義一個專用的介面(提供置換用)
符合同樣介面的其他物件Class表示可以取代現在物件的位置

輸出物件(View)的拆解原理如下:
頁面、區域、欄位等等可視物件與可拆解物件以一對一的原則生成Class
生成輸出Class的同時為之定義一個專用的介面(提供置換用)
符合同樣介面的其他輸出Class表示可以取代現在物件的位置

除了靜態的物件,動態的動作(Controller)也需要拆解:
功能執行的流程邏輯由一個隱喻的Class來控制進行
每一個獨立動作視動作範圍拆解為一個方法或是另一個Class
生成動作Class的同時為之定義一個專用的介面(提供置換用)
符合同樣介面的其他物件Class表示可以取代現在物件的位置而存在

如此一來,一個功能需求實作的基本Class組成與關係大致成形。

2007年12月16日 星期日

H17 Blog終於告一段落

從直接把想法寫成程式的作法,到接受OOAD的方法論是一個很大的轉變,其間也花了很多時間在思考如何將理論變通到可以實作的方式。後來發現與其他不接受OOAD想法的人討論時,時常淪落到在一個單點上的爭執;而那時最常聽到的一句話是:我這樣做不是又快又正確嗎?為什麼要浪費時間做那些?

在實作過一次並把OOAD融會貫通後,我發現得先把所有產出要生成的來龍去脈交待清楚後才有辦法清楚表達我的想法,所以開始記錄有關專案開發的過程、思維與作法。其間工作上經歷公司產品維護、幾個小元件的製作、還有對同仁作一天OOAD設計的講解(2007年8月初),我的想法陸續地調整與修正,從原先預計的一百篇左右變為現今的超過兩百篇。

提到軟體測試時,大多都會引用Grenford J. Myers在《The Art of Software Testing》一書中的觀點:軟體測試是為了發現錯誤而執行程式的過程;測試是為了證明程式有錯,而不是證明程式無錯誤。一個好的測試用例是在於它能發現至今未發現的錯誤;一個成功的測試是發現了至今未發現的錯誤的測試。

得先有系統存在才能想辦法去測試它,我也得先把自己所想的從頭到尾先完整表達出來,其他人才能看得出我在哪些地方是錯誤的;這就是我當初開始寫Blog的動力。最後,我用一句話來勉勵自己:現在的版本是為了下一個版本而存在的。(能明白它的意義嗎?)

2007年12月15日 星期六

H16 做人的方法(7)──時時確認影響

整個世界是一個類似系統的複雜結構,在許許多多不同的結構與層次裡到處都存在著人事物;人,充其量也只不過是世界裡一個渺小的組成,但是卻與身邊影響範圍所及的一切共同立足在這個世界的角落。

我們必須知道,我們在任何時間裡所作的任何行為,都或多或少地影響到所在的範圍。如果所做的事情影響較小時,周圍附近的一切會逐步吸收掉改變而不致擴大;相對地如果做出了一件影響範圍較大的事情,則必定會牽動世界的平衡。例如路上丟空罐只會影響了少數人的視野與負責撿垃圾的人;命令工廠對河流排放有毒的廢水就會破壞河流與其下游的生態;人類大量地產出二氧化碳而使得地球面臨溫室效應的浩劫。

切記自己所有的行為都會對這個世界有影響。少消耗一些物品,多回收一些資源有助於延長地球的壽命;待人多點和氣,避免針鋒相對有助於避免蓄意的殘害減少家人的傷心;日常生活裡養成趨吉避凶的生活習慣有助於多維持自己在地球上的責任等等。

生物群聚在地球之上,所有生物的作為累積起來將匯集足以改變世界的力量,尤其以人類為甚……。

2007年12月14日 星期五

H15 修改的歷程(3)──版本與資源

版本與資源的關係類似於班級與學生的關係,代表的是每一個不同的系統版本與上一個版本所涵蓋的變動清單。

在上一篇文章裡,我們可以得到系統的變動清單(Release Notes,也知道任兩個版本間的變動清單總和是升級版本時所需要做的動作。還有一個需要追溯的是系統裡的任何一個物件,曾經在哪些版本裡因為哪些問題而作過修改;這也就是程式或文件的修改歷程。

有的時候程式出現的問題是因為前面的版本沒有改好,而修改時大多都會幾個地方一起修改。當我們動手前,應當先確認是在哪一個版本出現的問題(修改垂直追溯),再確認那個版本的修改同時改了哪些地方(修改水平追溯),同時再參考系統水平與垂直的追溯關係,這樣才能作全面性的思考。

維護並不像開發那樣可以直接把程式湊一湊就能運作;寫功能即使是疊床架屋的作法都可以弄出一個可以驗收的系統,但是維護卻是牽一髮而動全身的高風險動作,如果不能確定自己動的那一根木頭會不會讓整個疊疊樂倒下來,維護這個系統就會變得非常辛苦。

2007年12月13日 星期四

H14 修改的歷程(2)──問題與變動

當系統出現必須修改程式的問題,為了解決問題就一定會動到程式,問題與修改程式之間同樣需要追溯。

程式被修改之後,要進入到系統的追溯層面,找出所有直接或間接使用到修改部分的Class,看是否有需要跟著修改的地方,有的話也要列入被修改的程式清單。確認問題變動到的Class清單後,要找出他們對應的測試程式視需要變動後加以測試,還要找出與修改部分相關的文件章節並且修訂;以上一連串需要修改的地方,都是與這個問題有關的追溯記錄。

單一問題逐漸被修正而且通過,在即將出版本前應把所有的問題與其影響物件整合起來看待,程式、測試程式與文件全部被修改過哪些,如果某個物件在多個問題內都有被變動到,則要把問題單代號列示在該物件的名稱後。接著找出所有變動程式影響的Test Case加以修改並作整合測試。

每個問題單要知道為它所改變的全部物件,每個被改變的物件也要知道它影響了哪些問題單。系統的任何改變,都必須要能找出範圍以便“封裝”改變後的影響,如此才能以最經濟的方式加以測試且能確保品質。

2007年12月12日 星期三

H13 系統設計結構是四維空間

在架構設計階段時提到,系統層面的設計要以功能為經、用層次為緯定出所有放置程式的方格,這是水平層面的設計。在系統裡與邏輯控制有關的程式,會去呼叫各個不同深度的元件,這是垂直層面的使用。另外用以上三度空間的概念所設計出來的所有Class,在特性上有著與其他Class的繼承關係,又組成了第四層的使用。

每一個Class所存在的位置有著這麼多的資訊,一個系統裡擁有的Class數量又是那麼地多,直接將想法寫成程式的作法很明顯地可以發現,在一小段時間後設計的人就會忘記一個Class如何與其他的Class互動的方式,以及該Class所具有的影響。正因為需要將所有Class定位的資訊是如此之多,再加上與需求、測試等等物件的關係又加深了複雜度,所以我一直堅持必須將想法記錄下來,以便隨時查詢到任何一個物件的影響。

系統裡任何一個物件都是牽一髮而動全身,更改一個Class後如果沒法把變動他後連帶影響的細線全部找出來的話,只要漏掉任何一條就有可能造成系統變得很不穩定。倘使沒有記錄,所有關係的追溯都必須靠回想或是再解讀程式碼,這不僅花掉更多的時間而且也無法保證能找到全部。記錄,真的是很關鍵的行為。

2007年12月11日 星期二

H12 修改的歷程(1)──時間與個人

小學中高年級時都有編班的動作,重新編排各班級的成員。現在想像自己是一位小學生,入學時低年級的編班是一個版本,中年級時是另一個版本,高年級也有一個版本;如此一來,班級、學生與版本就出現了關係。

班級裡原本就有多個學生的關係,現今再加上版本的歷程,需要追溯的關係就變得更多,因為原先只需要一層的班級學生追溯表,現在就必須因應版本成為三層,另外再加上垂直的追溯關係。比如說,我們應該會記錄各年級時所有班級的學生,還有學生在各年級中所處的班級。

班級的成員對學校來說是管理上需要的資訊,而學生所處的班級則是個人擁有的歷史。有的時候不同版本的班級需要知道前一個版本的成員有幾個人還在同一個班級;有的時候數個同年級學生聚在一起,會經過資訊的交流來知道他們有沒有共同的歷史(曾經同班)?有的話持續了幾個版本?

班級、學生與版本就已經讓追溯變得複雜,如果追溯的關係再加上各個版本的每個班級所有科目的課任老師,甚至再加上每個科目使用的教材出版社的記錄呢?現實生活裡的關係追溯就是這麼複雜,與開發系統相關物件的追溯關係只會更多而已。

2007年12月10日 星期一

H11 客戶想升級新版本時,怎麼辦?

每一個版本的異動都要建構管理,因為在同一類系統為不同客戶開發的狀況下,我們極可能會遇到前面任何一個客戶“需要”升級到最新版本的事件。不管是舊客戶升級新系統,或是新系統所出的更新版本,面對“升級”的詢問時該怎麼做呢?

升級最重要的部分是差異的比較,但這也是最難的部分。如果客戶的版本到最新的版本之間都有詳細的Release Notes,只需要總和所有變更即可;再次之的話,只要有舊版本的功能清單與新系統的功能清單,也可以多花時間比對出差異;但是我們遇到的總是最麻煩的狀況,兩個系統都沒有功能清單之類的文件。所以目前只要提到升級,都沒有人能夠保證要怎麼做才能100%無痛升級。

大家都說經手過的所有系統都留有最後的程式碼,可是在無法分析比較的情形下,程式碼只有在系統毀損後快速回復的作用。所有版本留下的意義,在於可以立即查詢到任何版本的所有文件,同時快速地求證到“任兩者之間的差異”。找得到所有的差異,就能夠安排工作項目來填補它們,使用舊客戶完整地升級到最近的版本。

2007年12月9日 星期日

H10 每一個版本都有它的意義

系統每出一次版本,可以算是完成一個里程碑。這個時候要把全部的新程式碼、新文件加上與變更有關的Impact Analysis、Release Notes放在一起一同建立一個保存的版本。通常來說系統執行一段時間沒有問題後,這些系統的內容幾乎都會沉睡在公司的伺服器裡頭。

版本保存的最基本用意,在於當客戶遇到系統任何一部分有缺漏時,可以快速地回復客戶的系統;另一方面在客戶發現問題或要變更需求時,有一個作為開始修改的基準。回復與修改使用到的是程式碼與文件的部分。

在某些狀況下,需要比對兩個不同版本間的差異,這時就需要與變更有關的文件加以分析比對。出現的時機大多在於詢問新功能在某個客戶用版本有沒有,或者是有升級系統版本的打算時。在台灣的軟體環境裡,做出系統功能都快來不及了,誰還會有時間管文件、記錄與變更的分析呢?也因此只要有人提出這個問題,就一定沒有人能正確回答。

2007年12月8日 星期六

H09 下一個版本的Release Notes

所有問題單與需求變更的內容幾乎都會成為Release Notes的一部分。在完整的Release Notes裡除了註明版本、出版日期等等的基本資料之外,還會記錄的至少會有每個問題單的編號、敍述、修改的程式名、更改的文件名。

Release Notes的用意大致上來說在於記錄與上一個版本的差異,讓使用者知道更換成現在的版本會有哪些改變,而且那些改變與哪些系統物件有影響;根據這些記錄,使用者能夠明確地界定更換版本的變更範圍,並快速地查詢到會有什麼影響且知道應該要怎麼做。

對於升級多個版本的客戶來說,只要把所有的差距版本的Release Notes整合起來一起看,就能得到其間差距的總和範圍與影響。我們可以將Release Notes視為告知系統所有改變的一份清單,不需要一一去核對新的版本與前一個版本就能得到差異的資訊。

2007年12月7日 星期五

H08 系統出版計畫

當問題單收集到一定程度,必須要出一個新的版本時,一般來說會訂一個停止處理問題單的日期與系統出版的日期,同時發布下一個版本即將要修改哪些問題的通知。

在停止處理問題單的日期之後,還會留幾天繼續修改現在已經確定要修改的問題,同時在確定全部修改過的程式都通過基本的測試後,接下來會進行出版本前必須要做的迴歸測試。迴歸測試原則上要把系統的功能從頭到尾重新測試一遍,目的在於確保新的版本不會有任何已知的問題。

在進行測試時若發現任何問題,必須儘快判斷該問題能否被快速解決,以便公布這次版本會解決這個問題或是延到下次再處理。對於使用者來說,明確地知道在什麼日期出版的系統會修改掉哪些問題是很重要的訊息。

2007年12月6日 星期四

H07 修改系統程式(4)──頭痛的問題

這個題目其實可以反過來問:修改系統程式時需要哪些資訊會感到輕鬆自在?以前面三個標題來看,大致上需要知道的資訊也有三種:原來的程式這樣寫的原因?新的功能要怎麼寫才能達成?還有程式修改下去會有什麼影響?

第一點需要的是程式碼裡包含足夠資訊的註解,有時會需要功能規格書甚至是原始需求資料;第二點需要的是API參考文件與系統開發教學文件;第三點需要的是程式對Use Case與Test Case的對應表,如此方能保證修改後的系統通過了所有相關的測試。

可以想像以上所有資料都殘缺不全時的景象,不是詢問原先的設計人員就是要自己從程式碼反推出那些文件裡應有的內容。問原設計者是最快的,但我的經驗在細節的部分以趨近100%的比例得到”已經太久以致忘記”的答案;也就是說,幾乎每個問題都得靠自己去反解譯程式才行。

由於自己走過才知道痛在哪裡,也因此自己在從事設計時都會特別注意要留下足夠讓別人明瞭或是換醒自己記憶的資訊;然而最理想的作法還是要留下足夠的完整資料才對。

2007年12月5日 星期三

H06 修改系統程式(3)──動手修改後

修改後並不是保持系統正常就沒事了,接下來還有很多收尾的動作,就是去修改與更動過程式碼有關聯的物件。

同樣透過系統各式各樣的追溯表找出程式有關聯的所有物件,像是需求文件、功能規格、使用手冊、開發參考手冊……等等所有應該隨之修改內容的文件,修改後註記在Analysis裡,並另外記錄下問題單與修改程式、修改文件的影響關聯。

修改之後,我們應該得到修改過的程式碼、修改後的相關文件、在上面註記所有修改發生處的Impact Analysis以及一份收集所有Impace Analysis影響資訊的清單;這份清單的內容會在出下一個版本時記錄在下一版本的Release Notes上。

這個時候的重點在於追溯並修改程式的部分影響到的所有系統物件,同時加以記錄作為日後追蹤問題修改後的影響。

2007年12月4日 星期二

H05 修改系統程式(2)──動手修改時

在確認出問題的程式碼後,應該先在Impact Analysis上註明出錯的原因,並推斷應該如何修改來解決現在的問題。文件經過原先設計者或其他同仁的審查並同意後,再依照文件上的作法去修正。

修改的時候要注意儘量把原來的程式碼用註解的型態保留下來,同時在後面註明是誰因為哪一張問題單才這樣改的;另外加上去的程式先要以明顯的註解標記上同樣的資訊。這麼做的原因是萬一修改後還是有問題,甚至演變為更重大的Side Effect時,還能很快地恢復為原來的樣子。

修改後當然應該立即作Unit Test以保證程式本身還能夠通過原來的測試,出錯的部分如果原先沒有被測試到則要加入Unit Test的範圍。通過Unit Test後要參考系統的追溯表找出這些Class所影響到的全部Test Case重新測試一遍;通過所有的測試後,把修改程式碼的Class名稱與變更的內容全部記錄在Impace Analysis裡。

如有任何錯誤則一直重覆修正到通過全部相關的測試為止。此時的重點在於解決問題並通過測試,還有就是記錄下所有修改的地方。

2007年12月3日 星期一

H04 修改系統程式(1)──動手修改前

當問題單無法直接回覆而傳送到負責修改程式的人這邊時,幾乎表示這個意見是非修改程式不可了。

首先不管是修改問題或是新增功能都要新增一份稱為Impact Analysis的文件,先在上面記錄問題單的編號內容與負責人員的姓名。這份文件的目的除了記錄這次的修改是基於哪一份問題單的提出之外,另外會記錄修改的原因與修改後影響的部分。

在動手修改程式之前,如果提出的是錯誤的話,那麼第一步必須先重現使用者的問題;如果依使用者提供的資料仍無法重現問題,那麼會先請使用者提供更詳細的資料。當問題可以重現時則反覆測試直到能明確判定是在什麼地方造成這個錯誤的發生。

定位出需要修改的程式碼位置後,再來就是要決定如何修改,與要花費多少時間去修改。在此時著重的是先去明白動作為什麼是這樣做,現在的動作在哪裡出現了錯誤導致眼前的問題發生。

2007年12月2日 星期日

H03 發現錯誤與需求變更的處理流程

在維護階段時,不管是發現問題或是想新增功能都適合這個流程,Defect System可以沿用測試階段的設定;但是出版本後的Defect System內容從空白的開始較好,因為測試階段所開的問題單的內容大多屬於系統內部的描述,使用者大多看不懂描述,而且問題理應完全依照需求與設計修正過,可以預設為正式系統裡不會出現。

使用者在發現問題或者想新增功能時,要先到Defect System上搜尋想提出的意見是否曾經被提出過,如果已經提出則應看該意見的回覆而不該重覆提出。提出的意見會由負責維護的人員決定是否處理該意見,不接受則直接回覆使用者並視需要提供更進一步的使用說明,接受的話會更改意見的狀態並將問題單轉傳給負責的人,由該人員決定如何處理。

2007年12月1日 星期六

H02 CMMI的精神在於規定Process內容

CMMI(Capability Maturity Model Integration)的精神在於規範軟體開發的各個階段,包含專案管理之類的管理層面,應該要做哪些動作,帶有哪些產出。一個公司導入CMMI的目的,是希望藉由它的精神依公司的特質,來制訂出公司開發系統時各個相關流程領域的標準作業流程(SOP)。

CMMI制訂出來的流程,意義與系統裡的Controller極為類似,都在於要求負責人員在特定情況下該去做什麼事(作出什麼文件);系統開發在某些時候是數個階段同時在進行的,在流程相互有影響的時候,依做事的順序與方法訂出適當的流程是很重要的。不過對於什麼都求快的專案來說,習慣從起點就飛到終點的我們,要去適應一步一步照規定來做通常都感覺到痛苦。

CMMI只要求在特定情況下要產出哪些文件,但是對於文件的內容並沒有要求。也就是說,習慣把系統設計成一個大黑箱的團隊,即使套用了CMMI LV5的作法,最後的產出一樣是一個大黑箱──只是多了一堆言不及義的文件。所以在導入CMMI的同時,理應再選擇適用的軟體開發方法才能有最大的效果。

以Engineer Processing的幾個流程領域來看,每個流程領域都是在以達到特定的目的的前提下,搭配使用的方法論來規定什麼時候應該要有什麼樣的產出。因而公司一定要先決定開發系統所使用的方法論,再導入CMMI加以規範開發的Process,方能進而提升品質。

2007年11月30日 星期五

H01 Controller控制系統物件,Process控制專案產出

還記得Data Model與View吧?它們都接受Controller的支配,何時該提供值與該設定值,都要看Controller裡如何定義。同樣的道理再往上一層,我們把全部的需求與程式都視為系統必需物件的話,那麼這些物件該在何時出現,何時提供給其他文件參考則都視Process如何定義。

CMMI是一個定義Process的標準。參考著許多開發系統的專案,專案們得到許多經驗值去明訂從什麼文件經過什麼動作之後可以導出下一份文件,專案依此一步一步地往前演進,也獲得許多按照步驟應該出現的產出,不管是程式還是文件,直到完成驗收出了版本總算把專案結束掉。而Process則是在專案完全停止前,規定要用什麼樣的順序來做與專案有關的動作。

即使系統已通過驗收,但沒人敢保證不會再發現錯誤或者使用不會再添加原來沒有的功能;所以維護同樣也是接受Process所管理的動作,以確保在應該有所動作時做應該做的事情。

2007年11月29日 星期四

G26 建立第一版的同時建構維護基準

終於,忙碌許久的系統終於要出第一個正式版本了。收集的需求、功能規格書、Data Model、系統硬體架構、設計規格書、元件規格書、實作的程式、測試程式、測試報告、API參考文件、系統安裝手冊、系統使用文件、系統開發教學文件……等等所有與系統有關的物品,全部都要在剛出完版本的那一刻進入建構管理。

為什麼要建構管理?首先當然是讓日後使用的人明白與現在版本相關的一切都在這個建構之中,取出的任何物品都是正確的版本。以前公司的專案曾經發生過負責人員沒有把專案最後版本的程式碼放回公司伺服器,使得後來去維護的人在找不到從哪個版本去改的狀況下,從程式反推需求後再寫出對應的程式。原先十分鐘可以改好的問題後來花了整整兩天在處理。當然,那個歹命的人又是我。

千萬要記得,系統每出一個版本,與當時系統有關的所有程式與文件也要建立一個對應的建構的版本,這是最基本的觀念。但是不要以為依版本收集妥當就沒事,同時還必須要建立現在版本與上一個版本之間的全部差異!

2007年11月28日 星期三

G25 追溯關係(5)──System vs Document


一直認為每一個動作都會有它必須要做的來由,動作會有它的產出,也會有它的影響。基於系統的功能目的,會有對應要有的程式,接著有對應來保證程式運作正常的測試程式,有對應程式開發的API參考文件,也有教使用者使用的說明文件。

文件與系統的對應追溯差不多就是如此。系統上的每一個物件都有它所對應的功能規格,也有它所對應的使用文件,不管是程式、元件、設定檔或是訊息檔,裡面的每一個小項目都有其應該對應的來龍去脈。

很明白地,需求、設計、程式、測試、文件、使用是一脈相承的連續產出,有前面的需求才會有後面一系統物件的存在;唯有記錄其中的關係方能明確地知道動了其中一個部分的時候,同時會有哪些東西需要跟著修改。只要忘了或懶得建立其中一項關聯,那麼在需要知道追溯關係的時候就難以再分析求得。

2007年11月27日 星期二

G24 英文字典vs英文課程──API Reference vs Tutorial

當我們開發好系統後,要如何讓其他原先不懂的人明白如何使用系統內部的程式來做出想要達成的動作呢?最理想的方式莫過於使用Tutorial循序漸進地來引領大家慢慢瞭解系統的內部。

公司的產品剛做出來時,提供給相關的專案作客製化時,專案人員抱怨說新的產品不知道要怎麼用,不清楚有哪些API。的確,產品剛出第一個版本時的說明文件少得可憐,做出來的說明文件坦白說實在有點不知所云,連我看了都不懂裡面的狀況,更何況資歷更淺的人呢?

有人提議說把教學文件做好一點就夠,但是我堅持應該要有系統所有的API參考文件。Tutorial就像是課本,會說明其他人所希望知道的系統,可是教學不可能教全部的東西,應該另外有一份所有東西的參考資料存在;就像英文字典會收集世界上所有的英文單字,而我們的英文課本只會選擇出英文字典裡最基本,最需要學會的一些單字來教學。

API Reference與功能清單很類似,後者是列出系統全部可以做到的事,前者則是列出系統內部可以使用的Class與方法;相同的是兩者都需要記錄才有可能寫出內容,這是直接把想法寫成程式的作法難以做到的。

2007年11月26日 星期一

G23 驗收的文件(11)──系統開發教學

一般只提供使用的系統,交付安裝手冊與使用手冊後就可以過關。但是系統如果要交給客戶自己維護,甚至自行在系統上開發新功能時,那就得再交付一堆下述的文件才能過關。

首先要有開發環境的安裝手冊,包括開發伺服器的安裝、開發工作站的安裝,同時應該會需要安裝同步開發時控管的軟體。接下來要開發使用的工具軟體清單、安裝手冊與使用手冊,再來是如何建立原始程式工作區,如何使用同步控管軟體;以及前面提到的系統API參考手冊。

前面這一堆都還只是準備動作的文件而已,開發的重頭戲在於要怎麼教會使用者系統的精神、架構的意義,接著是一步一步地帶領使用者由淺入深地慢慢懂得如何使用API來組合成他們想要做的動作。之前為我維護的系統撰寫這一部分時,光是最上層開發或維護Use Case花了五天才寫出細節不夠完整的教學文件,講授時則是用了三天才講完較基礎的部分。

每一件想做的事物都必須投下相對的人力與時間,在此又是一個有力的印證。

2007年11月25日 星期日

G22 驗收的文件(10)──系統使用手冊

將所有的電腦都安裝妥當後,接下來要教客戶怎麼使用系統的功能。設定、起動與結束是最基礎的幾個功能。

使用手冊大致上依照Use Case包含的Activity Diagram裡的User那塊swim lane的動作依序講解下來,在需要操作系統的時候貼上一張圖顯示要在哪裡輸入些什麼,系統有回應時解釋各種不同的反應代表什麼意義,接下來要怎麼做等等。原則上依每個Activity以一個步驟來講解為宜。

如果系統有明顯的子系統區分時,要編排不同的使用手冊。例如系統含有使用者管理與管理者監看系統狀態等等的功能時,由於使用者角色的不同,這些都應分開為每一種角色的使用者撰寫一份使用手冊。可以考慮由測試人員在Test Case測試時,把結果正確的測試步驟一一轉出為使用文件的內容並擷取對應的系統畫面。

每一份使用手冊也需要針對其範圍另外編寫系統的可執行功能清單、可用參數列表、錯誤訊息清單、詞彙表等等的章節作為參考。這部分應可在設計文件中得到並附加在使用手冊後面。

2007年11月24日 星期六

G21 驗收的文件(9)──系統安裝手冊

根據硬體架構與設計文件可以知道系統有哪幾類的電腦,每一類電腦的硬體規格、應該接續哪些週邊、使用什麼作業系統、安裝哪些軟體、需要哪些程式執行檔等等。系統安裝手冊就是循序漸進地帶領使用者建置起各個角色的電腦設備。

安裝手冊的編排要按照先後次序,內容則要巨細靡遺地說明每一個步驟,附上執行該步驟時的畫面圖檔或是擷取安裝時的動作影片都是讓使用者更能明白安裝操作的理想方式。製作安裝手冊的人通常會拿一台符合規定的乾淨電腦,按照規定從無到有地依序安裝並且設定,同時將所有過程按部就班地記錄成安裝手冊。安裝手冊裡每完成一項軟硬體的安裝與設定後,應該說明如何驗證該項軟硬體已經正常安裝的動作以便確認。

如果系統需要交給客戶使用開發環境,那麼安裝開發環境也需要另外製作出一份安裝文件。總之,每一種用途的電腦都該有一份專用的安裝文件。

2007年11月23日 星期五

G20 驗收的文件(8)──系統測試報告

關於系統的每一種測試的時間與結果報告,都應該彙總起來在驗時交付的。

系統程式碼的Unit Test會與原始程式一起交付,在開發環境裡隨時可以執行驗證正確性。這方面的報告只要填寫何時跑過全部的Unit Test且沒有任何錯誤即可。

與Use Case有關的Test Case應被追溯,測試時開立的問題單都會註明屬於哪一個Use Case,修正後再用該Use Case的所有Test Case重測一次。

對於系統來說,每隔一段時間就應跑出一份報表,記錄各個Use Case至今總共測出了幾個問題、修正了幾個、修正的百分比多少,這些數字也會被加總在子系統層級與整個系統層級來表現。測試階段時每隔一段時間就要跑出一份這樣的測試報告,所有的報告都應交付並建檔管理。

2007年11月22日 星期四

G19 驗收的文件(7)──API參考文件

在OOAD開發流程下,在每個Class、每個方法與每個屬性被設計出來的時候,應該都已經有它應該存在的原因與其對應要去處理的動作。在Java的開發環境裡,幾乎只要使用Java Doc把所有程式碼的註解跑過一遍就可以擁有完整的API文件了。

在Component的情形下,除了基本的註解顯示的內容,應再附上Properties使用的名稱與其影響的功能,以及在各種不同的例外狀況下傳回Exception裡的所有傳回狀況代號;如果有使用到任何外部檔案的時候,也要包含檔案格式的說明。當然這些在元件設計的時候都應該同時有追溯表才是。

在把方法視為函數呼叫的類別裡,最重要的是詳細說明每一個方法的呼叫方法與作用,最好是能附上幾個使用範例。這些內容的寫法可以參考JDK的API文件寫法。

對開發與維護的人員來說,API參考文件就有如系統的開發API字典一樣,要包含系統層次裡所有可能會使用到的Class、方法與屬性說明。

2007年11月21日 星期三

G18 從程式倒回Design Model的問題

身邊偏好將想法直接變為程式的人常說,程式裡隱含可正常執行的Model,如果需要UML圖的話就從程式碼倒回來,保證可以拿到最新最正確的。

這句話原則上沒有錯,無論是Class Diagram或Sequence Diagram都是系統真正在執行的。但是令人困擾的是倒回來的Model是一大包什麼都有的產出,我們不容易從裡面找到我們所需要的條理,也不容易正確地分辨系統的層次。更要命的是,要是設計者只是為了功能而實作,倒回來的東西可像極了混成一大團的毛線球。

試過用IBM RSA把維護系統的程式碼倒回Sequence Diagram,得到的結果實在很長,一個特定方法之下所有呼叫順序全部都列出來。由於該方法原先就沒寫好,有些邏輯在上層,有些則放置在更下面的邏輯裡,完成不知道原先設計者思考的是什麼;後來只好去問原來的設計者,得到的答案竟然是:我忘了,你可以先看程式碼後再問看不懂的地方嗎?當然我就丟下不看了。

設計的層次本來就有問題時,怎麼倒回Design Model都是沒有用的,頂多貼到交付文件裡騙騙那些什麼都不懂的使用者。建議還是先用Model設計,確保各個層次與物件負責範圍,以免系統最後變成一團大毛線球。

2007年11月20日 星期二

G17 程式設計的能力(5)──取得狀況、判斷資訊與執行動作

做事與寫程式的理念實在太接近了,因為二者的在細節上的處理同樣都是取得狀況、判斷資訊與執行動作。

做事情是為了達到目的,但並不是一味地往前直衝而已,而是需要判斷現在的局勢然後決定一個最適合的作法,執行作法後還要再檢查結果與影響並進行再下一步的動作。程式設計也是如此,我們可能設計直接執行一個動作,或者先取得系統裡某些地方的資訊,加以判斷狀況後執行不同的動作,當然也會根據執行後的結果來取決接下來要做些什麼。

開發系統的時候,在商業邏輯的處理時主要著重於配置物件、定義動作與要處理的資料這類的思維;再詳細設計商業動作內部的分解行為時,則是以取得狀況、判斷資訊與執行動作這樣的想法來逐步堆疊出達成方法目的的內容。

2007年11月19日 星期一

G16 驗收的文件(6)──系統原始程式

這部份應該比較沒有問題,再怎麼樣都只要把專案裡用到的所有程式碼,一股腦地壓成光碟直接丟給客戶存查就鐵定沒錯。不過在一般的專案裡由於沒有控管與追溯,裡頭大多都帶著一堆雜七雜八沒用的程式,也沒有人敢任意拿掉什麼,因為就怕拿了一個Class後讓系統再也跑不起來。

以架構與元件設計的產出為基礎,分門別類地把Package放到所屬的資料夾。在每一個Package與每一個Class的產生都能找出原因,且能找出誰在使用時,在專案裡要出現多餘無用的程式是很不容易的;只是要有這樣的效果,都是必須投入時間從頭開始分析、設計並記錄才能享有的。

測試用的程式最好也在原始程式的交付範圍,因為要驗證原始程式執行時的正確與否,使用曾經使用的測試程式是最快的;同時也可以檢查測試程式裡寫入的測試範圍有哪些,而且可隨時加上新的測試內容來驗證原始程式。

2007年11月18日 星期日

G15 程式設計的能力(4)──配置物件、定義動作與輸出入資料

當我們可以正確地拆解動作與物件,同時也瞭解異中求同、同中求異的概念後,接下來要做的就是把拆解下來的各種不同的“零件”依概念組合成系統需要的Design Model。這時候還需要參考的設計理論是Design Principle與Design Pattern。

物件的放置與群組化的處理可以根據Design Principle,像是OCP(開閉原則)、DRY(勿自我重複原則)、SRP(單一責任原則)、和LSP(Liskov替代原則)等OO設計原則,期望產生可擴展、可重覆使用、且好維護的系統。在需要達到特定的需求且避免可能的問題時,則套用Design Pattern的設計來塑造具有彈性的局部設計。

接下來就按照物件分析出來的關係,根據動作的先後一一在對應的物件上加上適當的方法;在此同時將傳入的參數、傳回的結果與拋出的例外一併宣告。如果設計的是元件,則建議將可能會有多形使用的方法一併加上,以減少未來變動時再度產生改變的影響。

系統再怎麼做幾乎都能滿足客戶的需求,但是系統要能有彈性、易變動、好維護之類的特性,就全仰賴設計者有多少能力了。

2007年11月17日 星期六

G14 元件切割大小的抉擇

元件在定義時就需要花上功夫。由上而下設計時,開出的方法包含的範圍是比較大的,屬於Controller裡的一個特定動作;但實作時我們會發現許多真正要實行的動作是更加細微的;這之間的落差該怎麼辦?

我們當然可以只用一個方法來含括符合Controller動作裡想要做的事,這樣一來在Controller上簡單易懂;只是在Component實作時會在裡面處理較多的邏輯與動作。元件寫得越大,內部邏輯包含得越多,也越加讓這個元件只能符合部分的需求。

為了讓元件符合重用並滿足更多樣化的需求,我們也可以讓元件控制的範圍最小化,如此一來就有機會任意組裝順序以滿足不同種類的動作需求。但是元件寫得越小,就得加寫上層的控制動作,使得Controller的管理變得煩瑣。

我的解決方案是使用複合型的元件。需要直接使用底層控制元件時,就用最小單位的Component;在較多邏輯的情況時,就在原先的元件外再多包裝一個大的元件,讓原來的元件只是大元件內部的Action,邏輯就集中在大元件的Component Controller來做。如此一來就能依想使用的範圍選擇適合的元件。

2007年11月16日 星期五

G13 驗收的文件(5)──細部設計相關文件

如果執行的步驟是經過設計一段程式碼,那麼通常會獨立成一個方法。這時需要方法的宣告規格,然後是裡面所做的流程與各個步驟執行了些什麼。

如果執行的步驟經過設計是獨立一個元件來負責處理,那麼我們會先定義出Component Interface的規格,再依照Component Interface的定義在一個(或數個)Package裡設計出一些Class來完成Component的應有規格。

以細部設計裡所提到的想法,靜態部分會有實作Class與Component Controller、Component Action兩層,另外還有Properties的定義以及Component Exception;動態部分則應該要有每一個介面方法在執行時的順序。依此可以得到Component的Class Diagram與Sequence Diagram。

別忘了架構設計時使用的元件方法追溯是維護時很重要的一份文件。

2007年11月15日 星期四

G12 程式設計的能力(3)──異中求同,同中求異

在多個不同的地方找出共同的部份抽取出來,在抽取出來的共同部分裡又能分辨出差異的地方。聽起來有些玄妙,但是這卻是要做好設計不可或缺的能力。

從物件角度,系統每個物件都是各自獨立的物體,我們必須去分析物件間的共同關係,將之抽取為Interface,在處理Interface時又必須可以分辨出它的實體是哪一種物件。

從程式角度,某一段程式碼在多個地方出現,因此我們會將之獨立成一個子方法供需要的時候呼叫,但是在子方法裡要能知道是從哪裡呼叫來的。

從系統角度,每個Use Case都被設計為一個View對應一個Controller彼此是沒有交集的,此時會在所有的View進入Controller之前加上一個Façade作為共同經過的處理,在Façade裡必須知道現在進入的是哪一個Use Case。

共同的部分是要作為reuse使用的,差異的部分則是用來區分每個物件、程式或Use Case的唯一性。找出同理與區分差異的動作做多了,現在幾乎隨時隨地都在對身邊的所有對話與事件作推理,這應該屬於職業病的一種吧?

2007年11月14日 星期三

G11 做人的方法(6)──明白自己的定位

在系統裡處理事情的單位有Controller與Action,對應到現實生活上同樣也有管理者與執行者的角色。管理者負責決定事情應該有哪些動作並控制現在執行的狀況,執行者則負責執行特定的動作並向管理者回報執行的結果與無法處理的狀況。

人在參與事情時應明白自己的角色:管理者決定好動作並分派給適當的執行者去做,隨時蒐集各個執行者的狀況並參考調整接下來的動作與時間點;執行者只需要努力在限制時間內做好指派的工作,在有結果或是應由管理者決定的例殊狀況時立即向管理者回報。管理者若管得太細會有失焦的可能,執行者若把管理者該作的決策自己決定掉了則會有失控的狀況。

開車在路上時,對於車子而言我們是管理者,但是對於後面的車來說我們卻是執行者。開車時,我們要不斷地接收路況、車況等等的資訊來決定要如何駕馭車輛;另一方面我們又必須使用燈號讓其他車輛明白我們接下來會不會有加減速、轉彎等動作,好讓其他開車的人控制他們的車作好配合的準備。應該可以想像車禍的發生大多是駕駛無法控制好自己的車,或者其他車子沒有告知接下來的轉變而來不及反應。

人應該明白自己當下所處的位置與應該做到的責任,才比較不會搞砸事情。

2007年11月13日 星期二

G10 驗收的文件(4)──架構設計相關文件

在架構設計的那些文章裡,根據MVC的原則畫分了系統的各個層次。架構的層次可以依照實際的狀況調整,比如像上一節的Web-Presentation-Business-Integration-Resource就被分為五個層次。

每個層次如果應用了一些Design Pattern或者是打算設計為Framework,那麼為了形成系統的架構,就必須先有形成系統框架的程式;這當中也包含了各個層次或硬體之間資料傳遞所必須的橋樑設計。如果有這樣的設計,在這部分就應該先有框架的功能規格,裡頭應包含Class Diagram、Component Diagram與Sequence Diagram。

每個Use Case依功能規格書,以符合框架設計為前提被設計成一堆Class,Class裡依步驟的解析會有使用到Component的動作。在Use Case Realization裡記錄的時候向下只需記錄到使用Component Interface的哪個方法即可,主要記錄的內容著重在陳述Use Case裡的各個步驟與遇到的各個狀況要如何處理。這部分的文件應包含Class Diagram、Sequence Diagram及其他視需要而畫的UML Diagram。

請記得這時雖然還沒有牽涉到Component的內部,但需要記錄使用哪些Component以及使用了什麼方法。

2007年11月12日 星期一

G09 驗收的文件(3)──硬體架構相關文件

系統的程式要佈署到Client與Server上時,有些人的作法是在每一部執行的電腦上放置一份所有的程式,那麼不管在哪一部電腦“肯定”都可以正常執行。

當然以使用者的角度來看只要系統正常運作,其他都是次要的問題,可是一部電腦裡明明放了一大堆根本用不到的東西,卻又無法明確指出哪些是必要的,哪些根本用不著會不會太浪費資源了些?如果放置物品的地方是你的房間、你的車或你的床,你會容許把一大堆有的沒的全堆在裡頭嗎?

隨時隨地建立關聯的追溯是可以立即追蹤出指定的物件是否必要的最快方式。從Usc Case、Library、Framework、Component、Class一路追溯而下,所有物件應該在什麼地方出現並被使用都可以知道它為什麼要存在。

下圖來自Sun OO-226課本第15章的Tier and Layer Diagram,是個不錯的範本。硬體架構的決定將會影響再上一層的架構設計,並會由此導出安裝手冊的內容。

2007年11月11日 星期日

G08 程式設計的能力(2)──能夠拆解物件

程式設計的另一個能力是物件組織能力。能夠準確無誤地從一堆雜亂的名詞挑出適合作為Class與應該是其對應屬性,並分析出所有Class之間的參照關係,是建立Data Model的重要基礎。這個能力其實不難培養,通常有兩個方向可以思考。

一種是觀察靜態物件的組成。生活裡有很多實際的物品,每件獨立的物品我們都可以仔細觀察它擁有了什麼特性:像是顏色、尺寸、形狀等靜態描述,或者是動力、移動方式、跳躍等動態描述,這些都屬於物品的特性。還有物品是否使用了其他的子物品?像是汽車裝的輪胎就可以視為子物品而存在,這樣擁有的關係也需要記錄在物件裡頭。

另一種是動態關係的組成。在拆解目的時我們會得到許多工作步驟,精確地把每一個工作步驟指派到應該執行它的靜態物件,就是最終的目標。要往這個方向邁進,我們必須清楚的知道每個靜態物件的責任,才能判斷出該由誰來負責。這同樣是日常生活與工作上時常會遇到的情境。

我們並不能保證每個人想的分類都會是正確的,因此需要review的機制來驗證每個人的想法並加以調整。

2007年11月10日 星期六

G07 驗收的文件(2)──Data Model相關文件

系統所要處理的是資料,資料在系統裡面不會漫無目的地到處亂放以免增加存取的難度,因此我們會將輸入輸出用的資料,依用途與關聯分門別類地建立盛裝的Data Model。

Data Model裡的Class名稱與內部的屬性應該都可以在系統詞彙表裡面找到並定義其關聯。Data Model是很重要的設計,由於這是最底層被使用的模組,所以必須要求物件之間的關係,不管是擁有或是使用關係,都必須是正確的。一旦後來發現錯誤調整了關係,那麼會連帶地影響所有使用到變更部分的程式內容,影響是十分龐大的。

Data Model文件主要要表達的是一共有哪些資料物件,內部包含了哪些屬性,以及與其他資料物件之間的從屬及使用關係。可以選擇用Class Diagram表達,也可以選用ER Model來表達這些內容。資料實際儲存的格式也要同時記錄下來,不管是XML的格式或是資料庫的語法。

2007年11月9日 星期五

G06 程式設計的能力(1)──能夠拆解動作

系統被拆解為功能,功能拆解出步驟,步驟中決定做的動作,每個步驟與功能都預先想好可能會發生的例外狀況,並決定好萬一發生了該怎麼處理。雖然會有資深的人幫我們準備好所有的資料,但是這卻是我們應該培養出來的能力。

上級交待一件事情,在確定目的後就應該要去分析這個目的可以拆解為哪些相關卻不重複的工作項目;工作項目確定後,要再去想可以分解為哪些獨立的工作步驟,而且該做些什麼且跟哪些人有關。這樣的能力在生活裡隨處可見,程式設計的原理也剛好不謀而合。

同樣的概念也發生在希望在程式裡達成一個目的時使用。想要做到的功能要去思考要由哪些動作來完成,那些動作各由什麼元件提供或是要另外做,能夠乾淨清楚地切出正確的動作並找到該負責的單位,是做所有事情所必須具有的第一種能力。

2007年11月8日 星期四

G05 做事的方法(9)──寫日記的方式

高二時曾寫過一本日記,那時的想法是忠實地記錄下每天生活的過程,也就是記流水帳。絕大部分對於寫日記的建議都說不要記成流水帳,要選擇特別的事情記下來,可是一直沒法理解為什麼要這樣。

開發與銀行相關的系統時,客戶很重視電子日誌的功能,這個功能可以記錄下所有使用者在系統上所執行過的任何功能與內容。由於銀行系統都跟錢有關係,所以需要這個機制在有狀況發生時可以追蹤到在什麼時間誰執行過什麼功能。這感覺上就像是在記系統的流水帳。

開發系統時,我們會記錄與系統有關的資訊與想法,因為這些東西在經過分析與規畫後,會使用想法更為融合與精簡,去蕪存菁後留下與系統開發有關的部份,藉此讓系統慢慢地成形並進而成功。我們並不會特別去記什麼文件在什麼時間點產出,因為內容會隨著時間再慢慢修訂,重要的是裡面所記下的想法。

可以發現前者只是一種記錄,便於在特殊要求時去找出相關的一系統事件;後者則是記下與成長有關的所有資訊,讓未來可以引證參考而能夠有更成熟的想法。兩種記錄的內容與方式恰巧對映在寫日記的類型。如果要的是在未來知道以前曾發生過的所有事情,適用第一種記錄法;如果要的是留下能打動心靈的瞬間,那就使用第二種記錄法,才更有機會反省並成長。

2007年11月7日 星期三

G04 驗收的文件(1)──需求階段相關文件

客戶對於想要做出來的系統,通常會有幾個大方向的資訊:

1、系統應用的範圍。提到的是系統的設計將會是應付什麼領域裡的功能所存在,這裡會提到很多使用者商業領域的相關知識。(Domain Knowledge)這裡的資料在設計時要產生系統詞彙表,並進而產生系統可以處理的Data Model。

2、系統處理的功能。提到的是系統將讓使用者可以做且要怎麼做哪些功能,每個功能的目的與進行的流程應被分析出來並明確記錄到需求規格書裡。所有需求都應被明確地定義出來並有清楚的流程後才開始設計。

3、對系統整體的要求與硬體配置。前者的清單會記錄著與功能的配合關係,並與硬體配置同時在作架構設計時視為最重要的參考資訊。有時客戶不是很明白這些資訊,在訪談時如有不清楚的地方要建議幾個方案給客戶選擇,客戶的決策就是系統確定要做的架構。

2007年11月6日 星期二

G03 文件是必要之惡?

開發系統的人幾乎都覺得寫文件是很痛苦的事,但是一些理論與公司規定卻又要求非寫出哪些文件不可,只好心不甘情不願的花時間敷衍了事。以前的我也抱著同樣的心態,但是在領悟應把心裡的想法留下記錄的道理後,就變得十分支持依系統開發的各個階段產出應有的文件。

設計人員通常會把瞭解需求之後的構思直接轉換為實作的程式碼,這對寫作的人來說是自然不過的事;系統的需求、需求如何轉實作、實作時的邏輯、物件的對應與使用等等很多的細節都是在他腦海裡的產物,當你拿到的只是開始的需求資料與結果的程式碼,要多少時間才能找出其間的演變關係?

順向依各個階段把心裡想法記錄下來是我現今堅持的理念,即使專案沒有足夠的時間,我也會依循文章裡提到的原則簡短地記錄。當文件成為想法一步步實現時的腳印後,就不需要做完系統才寫回憶錄而且寫了還完全沒用的痛苦,因為裡面寫的都是形成系統的重要概念與想法。

接下來就依系統各個階段的演進來看看應該會產生出什麼樣的文件與其代表的意義。

2007年11月5日 星期一

G02 建立系統標準安裝內容

程式開發完成並通過層層的測試後,那表示系統即將要可以驗收囉。但在驗收前最後的考驗,是把系統安裝到正式的環境裡作最後的線上測試;這也就是說,我們必須把所有開發出來的結晶作包裝的動作,以便客戶安裝到他們的電腦裡執行。

首先要知道的是每一部電腦對應的角色應該安裝哪些必須軟體,接著要知道各個電腦角色需要哪些建置好的程式檔案。另外,不管是系統的功能或是元件的使用,如果牽涉到其他公司的公用程式,除了在設計階段要註明使用的關聯之外,在準備佈署的時候必須參考使用關聯先行放置那些必須的檔案。

在基本佈署建立之後,再依層次向上決定每一個硬體所需的執行函式庫,還有系統執行檔,如此一來,每個硬體一層一層讓有哪些東西都可以決定。這樣的記錄將會延伸定義為執行硬體需求、軟體需求與安裝手冊,未來所有電腦的採購與安裝都需依照文件的內容進行。

2007年11月4日 星期日

G01 建立系統標準出版方法

針對系統開發的所有程式碼,在建立的同時應參考應用的場所與範圍來決定所屬的Package與Project,並依此來決定程式檔案的佈署配置。

Package通常會以功能的特性加以封裝,Class少的時候全部放置在同一層,多的時候再依用途再劃分Package放置,但嚴格來說會視作一個Component。把功能類似的Component放置到同樣的Project裡,並將Project的內容匯出成一個執行函式庫,同時依Component的使用關聯記錄Project的使用關係。

依據使用者需求設計的系統所寫的程式,同樣也依功能性分Package來存放,再將相同層次的程式依硬體配置擺放在不同的Project裡,每個Project再匯出系統程式檔。在不同的硬體上配置系統程式檔加上執行函式庫,就是使用者真正在使用的系統。

從原始程式建置系統檔案時,可以使用像ant之類的佈署工具,撰寫描述檔讓程式自動建立所有必須的程式檔案。

2007年11月3日 星期六

F26 系統應該被測試,想法應該被Review

系統應該被測試,然後除錯,以期望它能正常的運行
想法應該被review,然後修訂,以期望它能順利地實用

由於與同事討論程式設計的想法時,大多流落到局部面的思考,所以原本想藉這個Blog記錄對於開發系統時要有的全部想法,作為日後討論的依據。開始的時候試著用處理事情的思維加上軟體工程的角度寫短篇的文章,卻在不斷地思索之後慢慢發掘到更多要素的思維與影響。

雖然已儘量用主題表達方向,並使用簡短的文字帶出我的想法,不過我感覺只寫出心裡想法的四分之三左右;而更重要的是,雖然我覺得自己的想法說得通,但是不知實際上有沒有問題。於是幾天前在Java World @ TW論壇公開部落格的網址,讓自己的想法來接受檢驗並加以調整。不過一個沒考過SCJP認證又沒看過幾本書的人所說的話,理論上是不會有人進來多看的。

至今遇到過的人都還停留在“用程式做功能”的層面上,希望有那麼一天可以跟幾個理念接近的人共事,討論出實現軟體工廠的可行方式,並進而先“用程式做工具”讓很懂商業邏輯卻不大懂寫程式的人“用工具做功能”。等到那麼一天我們就可以有很強的競爭力了。

2007年11月2日 星期五

F25 客戶會在意系統開發的哪些方面?

在工作上一直都是廠商的角色,以公司的作法來滿足客戶的需求。用我現在的標準來衡量,如果我今天是需要採購一套電腦系統的客戶,我將會以下面幾項為出發點來審視:

首先是功能部分。系統最低限度是要滿足需求,除了由客戶提供功能清單之外,我將會檢視廠商如何記錄與控管所有的功能與流程的描述;尤其是需求規格書的內容。要是我可以決定測試標的內容,我一定會加上幾份重要功能的規格書審核。

接著是設計部分。我會檢查系統架構設計書上是否完整註明所有硬體與所用的技術?決策的過程是否有被記錄?當然設計規格書會是重頭戲,不過我著重的將會是Controller與Action的層次是否分明,另外看元件的層次是否整齊,系統的訊息是否獨立,功能上要求可以置換的地方是否採用夠彈性的設計。

測試將會是最看重的部分。測試的範圍必須與功能的設計相互呼應,藉此來保證所有功能都被完整測試;另外測試的時程與報告也是一定會看的重點。最後則是文件的內容是否切確地描述系統的安裝與如何使用所有的功能。

其實我覺得重點是在系統開始的時候,客戶就要用心投入系統的規劃,如此才能夠隨時隨地掌握住系統的狀況的內容。

2007年11月1日 星期四

F24 做事的方法(8)──做前三思,做後反省

生活裡總會遇到許許多多要作決策的事,之前提過做事要有目的與達成的步驟,但是在做之前還有個重要的決策:到底該不該做這件事?

在心裡快速地作沙盤推演可以是個有效的方法,把每個決定與作法都在心裡思考一下,整理出每個決策的優劣與影響並加以比較,如此將最有機會選擇到最好的決定。很快地作決定並不見得是好事,古人要我們三思而後行就是希望我們先確認再作最好的決策。

在學生時代寫考卷時,師長總會要求我們寫完要檢查;現在要問的是,要怎麼檢查才符合之前所提到的測試概念呢?每一個題目是需要寫答案的最小物件,所以在寫每一題的答案前應該先在心裡檢查一下對錯,這是Unit Test;每一個大題包含了該類型的所有題目,不過每一題間並沒有關係存在,這一層倒可以省略不用檢查;整張考卷會被批改並標註分數,於是把考卷視為系統再作一次檢查也是合理的要求。

做事前養成先想一下再做的習慣,一定會比先做下去再觀察反應的方式好上許多。這是在從事任何決定之前應該要有的習性。

2007年10月31日 星期三

F23 測試結果與對應文件裝訂在一起

需求被定義為功能,功能經由設計而實現,實現的程式藉著測試來保證運作的正常。不止是測試的結果,這一系列的相關文件具有演進的垂直關係,理應放置在一起以便查詢。

不過物件的位置與關係至少會是二維的,處於同一層次的物件彼此間會有相互使用的水平關係。像最開始會在功能裡使用其他功能,接著就影響到其下的設計與測試,都必須等待自己使用的功能完成後才能開始。這樣的關係也是必須在文件裡註明的。

另外也要記得同種類物件的清單必須產生,這是各個層次物件的快速索引表。如果有能力的話,水平追溯表與垂直追溯表還是做出來會比較方便查出所有的關聯;但是如何與真實物件間的關係作同步的修改,將會是日後需要多花資源的地方。

總之,讓同一個功能之下所有項目的資料都放在一起,這是符合聚合並封裝概念的作法。

2007年10月30日 星期二

F22 追溯關係(4)──Use Case vs. Test Case

Use Case應該與Test Case作好關聯的追溯。我們可以藉由測試過哪些Test Case來達到保證某特定Use Case可以正確執行的結論。

不管是因為什麼原因更動了程式碼,最基本的就是那個Class必須重作Unit Test;其次再依序往上,一層層地對使用到這個Class的Class作Unit Test,並依此原則遞迴測試所有最終會用到該Class的所有程式。

接著需要分析的是剛才所有重新作過Unit Test的Class,各自被包含到哪些Use Case的範圍內;有包含那些Class的Use Case對應的Test Case都必須重新進行測試。如果更動的是很多Use Case所使用的核心Class,那麼重新測試耗費的資源會是很可觀的。

要能根除“改過這裡之後要測試哪些東西才能再度保證系統是可以正常運作的?”這樣的問題,唯有作好對應功能、設計與相關測試的追溯關係方有終點。

2007年10月29日 星期一

F21 測試錯誤的處理(3)──迴歸測試(Regression Test)

如果問題單提出的內容的確是系統的錯誤,那麼系統理應針對這個問題作修正。程式的任何變動都會造成程式所在方法的改變,進而影響到呼叫該方法的所有地方。為了保證一段程式被修正後,其他使用它的方法同樣運作正常,我們必須再重新測試過這些方法所對應的測試項目(即使修改前已經全部測試通過)。

由此可見,程式的變動是需要審慎從事的。在國外的專案裡,每一行程式的變動,客戶都會詢問是為什麼而改,同時會影響哪些範圍,因為錯誤的解決方法會讓系統如同骨牌傾倒般到處出現連帶的問題。有時邏輯上的問題造成兩個地方糾纏不清,A改好B就有問題,去改了B時A又壞掉,這樣的Side Effect是任何人都沒法忍受的。

前面提到程式的追溯最好是到方法層級,這是因為方法內部的改變只要往上追溯使用該方法的地方;如果追溯的程度使用的是類別,那會使得追溯出來的範圍過大且不正確。涵蓋了一堆沒有使用關係的方法,會測試到一堆本來就正確的地方(因為沒用到改變的地方),比起在設計時去追溯方法的關係,將會浪費更多的測試資源。

2007年10月28日 星期日

F20 測試錯誤的處理(2)──用Defect System記錄

問題單應該統一開立在一個地方,至少可以使用一個Excel存放;但是最理想的方式還是使用Defect System。後者的好處是可以集中存放,多人同時使用與編輯,而且在與自己有關的問題單改變時,會主動發出郵件通知(Event)而不用時常進入系統查看。

把開立的問題單視為資料庫,我們可以在特定時間點統計開立問題單的數量、退回數量、解決數量、未解決數量、重開問題數量……等等的統計與百分比,這些數據的統計範圍除了整個系統之外,也可以針對各個定義的範圍(模組或功能)統計出各個部件的問題單狀況。

另一個好處是,任何時候遇到系統上的問題都可以先到Defect System上查詢以前是否發生過同樣的狀況,如果有的話先參考之前是如何解決或是如何正確使用的,如果沒有就能放心地開立新的問題單而不用擔心會有重覆的狀況。

類似的系統在系統出版本後,可以應用在FAQ(答客問)的方面,使用者遇到問題或是不會使用的狀況,都能找到一個集中管理類似問題的資料庫搜尋想要的資訊或提問讓系統維護人員回答。

2007年10月26日 星期五

F18 Unit Test(4)──測試Component Interface

表面上Component Interface要測試Interface,但是事實上同樣是要測試實作Interface的所有Class。

測試時每個實作Class生成一個測試類別,每個Component Interface方法都對應一個測試方法,這與Interface的測試策略相同。從Class到Interface到Component Interface逐步的測試,代表的是由組成單位往上組裝成元件的測試,經由一層一層的設計與測試確認,我們得到的將會是穩定度很高的元件。

元件是用來組成系統的最大單位,在元件之上就是系統使用它們的控制流程;測試控制流程的部分就進入到Use Case相關的功能測試與整合測試。參考系統架構設計的圖,單元測試主要是保證的是右下角元件的正常運作,整合測試、功能測試與使用者測試則是針對其他與專案相關的層次加以測試。

註:專案層級裡所有程式同樣也需要通過單元測試,先保證每個Class的方法都正確後才可以進行更上層的測試。

2007年10月25日 星期四

F17 Unit Test(3)──測試Interface

在元件內部的設計層次我們的架構留了幾個介面的存在,介面之下的實作少則一個Class,多的話也有可能超過十個。無論如何,Class測試通過後就要往再上一層的元件內部介面進行測試。

每一個元件內的介面,其往下的實作Class會生成一個測試類別,每一個介面裡定義的方法都生成測試方法加以測試。測試的原則同樣以在時程允許下寫出最多的測試內容為理想作法。這裡進行的已經是小規模的Class整合,有時明明每個Class都測試通過,但是放在一起就是沒法全部測過。就像有時社會裡明明每個人都守分地只做自己應做的事,但是整個社會還是會莫名其妙地產出問題。

Interface層級的測試,是把它之下從實作的Class起的使用關係都視為封裝的組件,測試通過保證這一部分的程式都沒有問題後,才允許再往上一層的元件使用這些層級來組合達成元件預期提供的功能。

註:Class與Interface的測試都要依據使用關係,從沒有使用其他Class或Interface的開始測試起,依序照著使用關係往上測試。

2007年10月24日 星期三

F16 Unit Test(2)──測試Class

Class是系統組成的最小單位,因而同樣也是單元測試的最小單位。設計時讓每個Class都各如其分地負責自己的工作,測試時則確保每個Class都能正常運作,這麼一來採用這個Class組成的大小組件就不會出現基本層面的問題。

一個Class會有一個對應的測試類別,生成的同時應針對public、package與protected的方法產生對應的測試方法,每個測試方法裡依設計規格產生輸入用的資料後,呼叫該方法並驗證傳回值是否正常或是否如預期般拋出例外。public的方法是必測的,package與protected的方法如果可以測試會更理想,然而因為後兩者在包裝層次與元件時可由外部測試來涵蓋相當大的部分,要是時間不夠的時候略過也無妨。

能夠針對規格裡提到的所有狀況都寫出一段測試程式是最理想的,因為測試涵蓋度越大就越不容易有問題,不過測試時大多都只會挑選一些重要的規格,省略測試功能的結果就是有可能讓小問題在未來因為相互影響而成為更大的問題。

註:測試Class的範圍包含static APIs但不包含Abstract Class。

2007年10月23日 星期二

F15 Unit Test(1)──基礎的單元測試

程式的實作都在Class裡完成,元件與系統都是由Class所組成。確保Class提供給外部使用方法運作的正確,就是單元測試所要提供的保證。

程式實作的過程會根據Coding Standard來review所有的程式碼,寫作的風格也有檢查的工具,只要依希望的程式風格設定參數檔後,工具就會依設定檢查程式並條列出不合規定的所有地方。由於改變程式就會有影響,所以在測試之前應先進行review與調整到符合規定的穩定程度再進行單元測試。

Unit Test有測試工具可以使用,對於Class來說會有一個測試用的Class裡面有每個方法對應的測試方法,我們在初始化方法定義每個測試方法之前的動作後,即可在測試方法裡寫下所有的測試動作。工具允許把Class依所屬關係集合起來一次執行,並在結果上註明有哪些測試的動作沒有得到預期的傳回值。

Class本身的方法、元件層次Interface的定義、封裝元件的Interface、系統層次的Interface,由下往上組合而成的各個組件,每一層在測試時都會被視為獨立的黑箱,由Unit Test工具進行所有方法的結果檢查。

2007年10月22日 星期一

F14 Use Case測試(3)──系統整合測試(System Integration Test)

在主要功能(由客戶指定哪些是重要的)的各個層次做好且通過單元測試後,我們必須把這些層次的程式放置在系統架構上,以驗證這些程式放在一起時能否正常運作。

像Client與Server間收送的模組、Context、Flow Engine等與系統架構有關的必定屬於最重要的功能,這絕對是需要優先整合完成的;系統架構沒有問題後,才能把重要功能的程式放上去進行它們的功能測試。整合測試與功能測試要檢查的都是系統執行的正確性,但整合測試時主要是強調系統架構是否可以讓功能的執行順暢且無誤,而不在於功能的內容是否全部正確。

因此整合測試的內容通常會先列出系統架構上的測試項目,接著會基於幾個重要功能裡足以驗證可以被正常執行的項目。不過我們通常會要求重要功能裡的所有測試項目都要通過,因為幾個重要功能可以在架構上完全正常執行,大多代表著其他功能不會因架構問題而產生錯誤。

2007年10月21日 星期日

F13 Use Case測試(2)──功能測試(Functional Test)

正常執行所有的功能是系統最低的要求,也因此通過所有的功能測試是最低的要求。雖然測試功能是非常重要的,但是不要誤以為客戶只注重這個測試而跳過其他底層的測試,因為底層幾個交錯的小小錯誤,很可能讓問題在功能之間相互影響而難以根除──除非重新設計。

功能測試我會分為兩個層次來進行:第一次由測試人員依使用者測試的步驟操作,一方面準備執行的資料,一方面可以藉此先找出操作介面上的基本問題。在輸入好全部的資料執行功能的一開始,用一個暫放的小程式把Context的內容儲存下來,往後測試人員在進行這個功能的測試時,可以使用Hot Key叫出以前的測試值直接重現,可以省卻許多重覆輸入的時間。當然這樣的小工具在功能測試結束時一定要移除。

一邊輸入資料,一邊觀察系統反應,執行功能後再確認執行結果的所有內容都是符合測試項目所描述的。所有的功能都測試成功,就至少符合系統的基本需求。

2007年10月20日 星期六

F12 Use Case測試(1)──使用者測試(User Acceptable Test)

Use Case基於系統架構作完設計後,應該把該功能從頭到尾的使用者操作動作記為測試的內容,並與預期的系統反應比較是否正確。

測試的內容以如何進入這個功能為起點,接著開始一步一步地註明要如何操作系統輸入,有時夾雜一些錯誤的操作以證明系統能夠有正確的反應。藉由逐步操作準備好輸入的資料後便執行功能(功能測試要先完成),同時觀察系統的輸出是否與規格書相同;如果需要再進一步的操作則同樣記錄並加以測試。

每個功能的每個步驟都應該測試正確,在提供給使用者作真正的User Test之前,專案人員當然必須先行測試過讓排除掉顯而易見的問題;沒有使用者會希望給自己測試的系統連立即可見的錯
誤都還有一大堆的。幸好這在功能測試時大多都會被發現。

對於可以正常執行功能的各個步驟與系統反應要稍加註記,因為使用手冊裡的操作步驟會由這些內容依序匯集而成。另外在使用者測試進行到一定程度,確定系統架構足夠穩定、功能可以順利執行且操作介面沒有大礙之後,可以增加同時進行測試的人員數量,藉此開始測試系統在多個使用者操作時的穩定性。

2007年10月19日 星期五

F11 系統整體測試(3)──組態設定測試(Configuration Test)

在設計各個元件與功能時,會有許多設定拉出到允許使用者設定的層級,這些設定的內容都需要加以逐一驗證與測試。

通常的測試都會一一將各個設定檔內容加以調整再進行測試,逐一驗證系統的行為模式。我覺得可以使用兩段式的測試方法:首先先測試設定檔是否可以正常被讀取為Properties;再來就只要寫測試程式去改變系統內部的Properties內容並呼叫對應的測試方法去測試特定的功能即可。這樣一來就可以讓系統一次測試所有的設定檔內容以比對結果。

如果設定檔改變的是顯示時的外觀,必須另外分開使用人力來測試,因為外觀的改變必須要由人來判斷正確與否。如果影響的操作的動作,我們像前面那樣完全由人來測試、錄下使用者的操作或是使用Robot程式來模擬使用者的操作行為,並在關鍵時暫停由人來判斷結果。

組態設定的檔案、內容、測試使用的值與對應的結果正常與否都要詳加記錄的。通常這會在系統測試的最後階段進行。

2007年10月18日 星期四

F10 系統整體測試(2)──壓力測試(Stress Test)

為了保證系統在多個使用者時可以穩定執行,客戶應該都會要求Server必須在多少不同的使用者同時執行任意功能多久時間之下,不能有任何的系統錯誤。

假使系統的要求是一百位不同使用者同時執行功能,通常我們會選用十台電腦,每一台都用不同的Thread執行十個系統同時執行指定的功能,藉此來模擬一百個使用者同時上線的狀況。雖然與真正的情況有點小差距,但是要同時找上百台電腦可是件困難的事情,所以這是個可以通融的測試方式。

壓力測試有時會與效能測試的規格一同定義,比如說單機執行功能時要在三秒內完成,在一百位使用者同時使用的情況下,每位使用者都要在十秒內完成功能之類的要求。效能測試與壓力測試有時會因為瞬間的因素而不準確,因而會多測幾次,剔除絕對不合理的數值後再求得平均值。

或許我們可以像奧運裁判那樣測試七次,忽略最好與最壞的一個只取中間的五次來計算平均值。

2007年10月17日 星期三

F09 系統整體測試(1)──效能測試(Performance Test)

需求收集並加以分析之後,首先要決定的是系統的架構。在設計系統的同時,我們會參考客戶偏好的方式並顧及到非功能需求裡要求的項目。通常可以看到的是客戶會指定一些重要的功能,要求執行時間必須在某個限制之內;還有就是要求處理功能時電腦之間傳送的資料,必須在多少頻寬之內。

要如何加快執行時間與降低系統頻寬是設計時要注重的內容。對於測試來說,速度的測試會在功能執行的開始用Log記錄開始的時間,並在結束時記錄結束的時間,再用個函數求得執行的時間;這樣的測試若能使用程式準備好測試資料執行,同時收集每次執行的結果是最理想的。

在上面的測試裡,於發動傳送的程式點記錄下傳送出去的資料量與接收回來的資料量,會有助於知道頻寬的使用量。雖然網路傳送時會加上封包的大小,但大致上可以求得差距不多的大小,因為我認為這方面做到可以自動測試會比要求完全的準確重要那麼一點點。

2007年10月16日 星期二

F08 最好可以快速地重覆測試

一個系統動不動就上百個功能、近千個類別且上萬個方法,即使已經有所取捨,但是撰寫測試內容所花費的資源還是很多;當然實際測試時投入的資源一樣不少,也因此在求快型的專案中決策的人有很多決定只粗略地進行測試工作。

首先要注意的是要建立起系統項目與測試項目的關連,先建立起明確的關係才能在需要測試時立即知道進行哪些項目就能保證那些產出運作正常。在第一次測試時測出的問題會經過修正,修正時會進行影響評估得知另外改變了哪些模組,所有改變模組的測試項目都應該要重新進行測試。

沒有建立起關係追溯的系統就會在這個時候開始失衡,因為沒有辦法判斷出改變設計時的影響,也沒有辦法得知改變影響後相關的測試項目,甚至連對應的測試項目都不存在。這樣多改個幾次之後,整個系統就開始混亂。

為了避免重覆測試時投入過多的資源,最好是選擇可以自動重覆測試的工具與方法以節省測試時投入的資源。在具有自動測試比對的工具裡,需要重新測試模組的時候只要點個開始,就可以等著得到該模組甚至整個系統的最新測試結果報表。這應該是最理想的結果了。

2007年10月15日 星期一

F07 測試計畫、項目、步驟與結果都要留下記錄

測試相關的內容是基於功能與設計的結果而決定的,範圍的取捨與項目的決定會被列成清單來表示,最好在清單裡記錄著所有應測試的項目再註記哪些項目因為什麼樣的原因而在此不作測試。

在功能的測試方面,有測試工具可以管理所有的測試項目。每個測試項目對應到一個測試劇本,劇本裡可以編輯測試步驟的內容與順序,另外在需要檢查的步驟之下可以插入一個結果描述,測試的時候操作完該步驟後就核對是否與結果描述相同並加以記錄。經由測試工具管理的測試結果,可以依不同的需求產生出測試報表。

在程式的測試方面,有單元測試工具來管理所有類別方法的測試項目。定義好要測試的類別與其方法,在測試的方法裡用不同的參數內容呼叫要測試的方法並核對結果,依此作為測試是否正確的依據。在單元測試工具裡,可以一次測試所有的測試內容來判斷現在版本的系統是否運作正常。

把所有的測試作記錄,我們才能明白每個功能各有哪些測試、測試了什麼項目、用什麼方式測試與所有測試的結果。由於測試與各層次的產出都有關聯,這也是需要另外再記錄下來的關係。

2007年10月14日 星期日

F06 測試計畫的內容與涵蓋度(Model)

每個測試的功能或組件,必定都有其對應的規格書。根據規格書作出的設計一方面交給開發人員實作,在擬定它的測試計畫之後還要再進一步擬定要測試的項目,還要決定測試時的流程與每一個測試的步驟。

將每一個測試項目裡的每一種可能都至少測上一遍可以說是完美的測試,但是別忘了想出所有測試可能並對應每一個可能編寫一個測試劇本,必定是個勞民傷財的動作。因而一般來說,只會選擇一些非測成功不可的內容來決定劇本;但是在這樣的取捨之下,雖然保證主要的功能可以如預期般執行,但是必然會有些很少見的現象將導致系統有些小小的問題。

每個功能或方法都有它的目標與期望的結果,還有一些必須處理的錯誤狀況,這幾項應標註為必須測試的內容。由於分析與設計的人是最明瞭規格者,所以理論上應由他們決定測試的項目範圍,並參與測試流桯與步驟的制訂討論。

測試階段相關內容模型:

2007年10月13日 星期六

F05 擬定系統的測試計畫

當決定系統的架構並確定功能清單之後,首先要擬定並執行的是設計與實作的計畫;測試雖然是在實作開始之後才有可能施行,但是由於有些測試的資料來源在功能描述裡就有,所以此時也適合開始計畫測試的時程。

可以根據F03圖裡區分的開發階段,先決定在各個階段的產出要進行哪些種類的測試,並概估每個階段裡的所有產出大約需要進行多少時間,並將之排在實作階段的中後期開始陸續進行。以系統層級來看,能夠先行處理的階段是硬體架構與系統功能的部分。

接下來進行的階段,每個層次與每個元件的負責人員也必須依照原則來擬定測試該層次或元件的時程。務必記得測試的計畫根隨著功能的切割,每一塊組件都應該要有搭配要進行的測試計畫。測試計畫必須在該組件設計完後進行,測試內容則必須在實作完成之前全部擬定,因為實作完成後應該隨時可以進行測試,而且要由最底層開始往系統上層進行測試。

2007年10月12日 星期五

F04 測試的項目與內容都來自功能

設計的目的是要做出一個與預期目標相同的產出,經由該產出我們可以得到與描述相同的結果;而測試就是以設計時指定的方法去驗證該產出真的可以得到那個結果。

在系統架構、系統功能、架構設計、細部設計與程式實作時,我們對於每一個需求項目,都會設計出一個對應的功能。每個功能都會記錄它的目的,並敍述經由什麼樣的設定、輸入與操作,將會獲得什麼樣的產出與結果;測試時所要做的就是準備好符合輸入時的設定與資料來執行該功能,並經由驗證結果是否為預期中的結果來決定它是否正確。

一個功能的執行有正確與錯誤的結果,除了要驗證正確的結果之外,也必須準備預期會產生各種錯誤的資料來驗證功能是否能夠處理。因此,功能描述裡提到的正確結果與錯誤結果的總和將會決定測試項目的數量,有時同一種結果會經由不同的輸入達成,此時根據輸入而也會有不同的測試內容。

測試的準備工作是繁複的,我只能說準備得越多就有機會讓系統執行更加正確,而且日後有人詢問說這個功能作過哪些測試的時候,只要拿出測試項目、內容與測試結果就能夠完備地回答這個問題。

2007年10月11日 星期四

F03 測試的層次對應到設計的層次

在系統目標的那張圖上,闡述了系統從使用者的想法慢慢變為需求、功能與程式的各個階段。每個階段除了記錄性的文字之外,所有基於設計而實作出來的所有程式與設定檔都必須經過詳細的測試,來保證每一個產出都是能正常運作的。

從最小的程式開始,往上的程式介面、元件介面、架構介面每一層都應該有自己的測試內容;嵌入到系統架構之後,還有設定檔正確性、執行速度與同時可執行數量的測試。系統測試的種類與項目在不同的事件發生時,還會有不同的定義,比如說系統正式上線後才修改後的回歸測試。

這裡列出的是我認為系統內所需要最小數量的測試,分別對應到開發系統的不同階段。如下圖所示:

2007年10月10日 星期三

F02 應該保證每段程式都可以正常執行

只要人做的事就有可能會出錯,不管是忘記記錯或者是疏忽,在處理系統的各個階段與部分都有可能會有錯誤。使用者總是希望拿到趨近零錯誤的系統,因此找出系統現在存在著的問題,就是測試所負責的任務。

在求快的專案型態裡,測試的目的通常只是要求到使用者看得到的部分,也就是以系統的功能與效能
合乎規定為主;只是功能的正常並不表示內部所有的部件都各司其職,有時幾個地方都有小小的錯誤,反而使用最後的結果負負得正而沒錯。完整的測試應對於設計出的所有產物都有各自所屬的測試計畫、測試項目與測試內容。

測試的項目與內容涵蓋的範圍越趨近於設計的規格,就能夠保證在測試時驗證過的狀況在未來都不會再有錯誤(除非因需求變更而修改過內容),就能使得測試過的部件有越高的正確性。如果想要快速測試而花較少的時間準備測試個案,可以想見沒有被測試個案涵蓋的部分都還是可能有錯誤存在。

如此由下而上,從保證底層物件的正確性開始,一層層地往上測試各個部件並加以整合,到最後完成時我們就可以獲得一個穩定性極高的系統。

2007年10月9日 星期二

F01 系統是為了哪些人而存在的?

一開始,客戶會有對於未來系統的“想法”,雖然有些想法會過於理想,但是訪談的系統分析師要依據可行性記錄想法並分析使之成為具有完整描述的“功能”。功能可以說是系統的組成單位,因為從這裡開始會有不同的展開。

依據功能規格,程式設計師會以“設計”使原先使用者天馬行空的想法成為可能實現的藍圖,接著程式寫作員會將設計的功能實作為可執行的程式碼。在功能可以執行的時候,為了要保證日後交給客戶的系統是趨近沒有錯誤的,就需要經過“測試”的階段來除錯並修正。

功能在被設計並實作時,對日後使用系統的人來說除了系統執行看得到的部分外都是不可知的黑箱,因此還需要文件來“說明” 讓使用者如何操作各個功能。如果想要系統順利完成並驗收,這幾個項目都是必須做好的。

2007年10月8日 星期一

E30 萬物生息皆有其道,設計系統亦同

在早期的工作歲月裡,曾經我也認為達成系統要求的目標即可,但逐漸累積經驗後我慢慢感覺設計的東西要能彈性地應付需求的改變,才能夠正確實用在多種不同的地方。但是缺乏實際的方向時所能改進的地方其實是很有限的。

2004/07公司裡有講師講授OO-226 (B)的課程,那時第一次聽到這類的設計方式感覺似乎不錯,但是那時還無法理解。2005/10公司派我到恆逸上OO-226 (C)的課程,這次的課本編排得很好,所以聽起來也格外用心;只是聽完後依主管要求在公司講解一遍時,只能食古不化地依內容照念。

2006/01起公司開始研發自有產品,這時我大膽地與同組人員決定使用OOAD來做,不過當時原擬定四週的設計,光是Data Model就做了兩週半才定稿,與其他直接寫程式的組相比落後許多,但是在03/26那天初次感覺到OOAD的融會貫通。

2006/08接手了沒有一行程式是自己所寫的那個產品,除了首次以正式的方法進行錯誤的修正與需求的變更之外,也漸漸明白沒有分割好設計層次所得到的程式有哪些問題。2007年在公司又陸續講了兩場OOAD,加上聽過一場軟體工程的講座便興起了將自己OOAD的想法記錄下來的念頭。

2007/05/30開始在Blog上以每日一小篇的方式記錄,原先的計畫是以百篇上下的內容描述整個系統的開發與維護內容,但是一邊撰寫又一邊領悟到新的想法,現在到這裡已經成長到超過130篇,看來整個系統寫完應是200篇左右。

思索與領悟的過程裡,感覺在實作與設計之上還有一個“道”的存在,所謂的架構設計、細部設計等等都是依循那個常理所必然生出的產物,而且唯有領悟到那個道才能設計出理想的系統。現在的我也只不過是初次感受“道”的存在的新手而已……。

2007年10月7日 星期日

E29 設計的終點(6)──穩定、容易改變、又能快速開發

綜合以上所有的想法,開發一個系統時,我會先著重在MVC的分層設計,每一層再分為Implementation-Controll-Action三個小層次;不管大的層次或小的層次,其中的設計都使用Interface來規範裡頭打算要做的事情,Interface的實作程式會依需要去使用已經開發完成的元件並加以控制

每一層往上會去思考套用Design Pattern的可能性,再適當地改寫為適合作流程控制的寫法,甚至更進一步地做成基本框架的類型。把每一個功能與層次的內容與作法都定義出來,再統合審視可能性與適用性並修正,等專案人員都同意之後就定義出工作項目並發配下去進行。

系統經過精密切割後分層開發,每一層都檢視看哪些可以利用reuse的元件,在寫作的同時讓別人明白裡面在做什麼,並在與其他各層銜接的地方加上吸收改變的避震器類別。每一層都可以方便使用實作,又可以輕易改變內容,如此一來專案的品質將更穩定,開發速度也會比之前更快。

第一次的實作會是辛苦的,因為要比直接做一個專案花費更多的時間。決策上如果確定未來有類似的專案需要開發,那持續往這個方向改進會是最有利的。

這張圖要表示的是所有實作元件與檔案之間的關係:

2007年10月6日 星期六

E28 設計的終點(5)──以做成基本框架(Framework)為目標

把原先用程式表達出來的動作與邏輯,使用文字描述的方式來表達並依序實作,這是撰寫程式之上的另一個層次。

把文字設定讀入依設定生成程式來動作,概念與Web Service裡把XML再生成Data Model差不多,但是需要處理動作與順序,複雜度倒是有過之而無不及。雖然開發這類基本框架的處理需要能力更好的人才做得出來,然而一旦規格開得理想又能完成的話,應付未來的需求改變時,投入人力的數字與門檻就可以降低很多。

Data Model與Controller的基本框架是可以實現的,View當然也可以。同樣應用一組XML的設計內容,我們可以在不同的需要上將之轉變為不同的外觀,無論是哪一種程式語言所使用的UI,甚至是瀏覽器使用的html都可以分別以產生程式來產生。如此一來,整個系統都能夠大量地以設定的方式來生成。

2007年10月5日 星期五

E27 專案分析工具(4)──讓編輯工具能自由決定設定內容

對單一專案而言,製作出分析設定編輯工具之後已經可以算大功告成,但是更理想的狀況是針對用途不同的系統也可以reuse這樣的分析設定產生工具。

不同用途的系統有不同的思考方式,首先就會有不同的設定檔,有不同編輯外觀,也有不同的對應方式,當然匯出到系統的處理方式也有所不同。設定檔可以使用元件對應到Project Data Model,編輯外觀可應用通用編輯器再加工使用,儲存的對應方式與匯出的處理動作能夠使用Flow Engine的架構讓使用者撰寫對應的程式;如此一來在Model-View-Controller三個部分都可以有快速完成的方式。

撰寫客製化的程式已經具有相當的便利性,但是需要一定的程式功力與經驗才能寫得好,資深的設計與寫作人員在專案裡已經不多,有時候會難以再抽出適當人力做出這樣的工具。如果專案需要的工具設計變得容易製作而且沒有問題,是不是大家都渴望的呢?

2007年10月4日 星期四

E26 設計的終點(4)──用程式封裝商業邏輯與設計邏輯

應用Baisc Data Model的觀念,專案的分析與設定記錄就像是外部的檔案,為了更方便地編輯檔案的內容,我們可以使用通用編輯器來擴充為專案用編輯器讓使用者直接編輯並存檔。

在編輯器上編輯的欄位可利用提示文字與輔助說明描述欄位值要如何設定與會有什麼影響,使用者只需要參考詳細的說明來設定,就可以得到正確改變系統內容或運作的結果。這裡引用的技巧在於使用者按照說明設定的值,在匯出動作的同時被一組負責處理匯出的程式依設定在程式裡轉換出系統對應的輸出。

用程式封裝邏輯就是編輯器與轉換器最大的任務,無論是系統的分析與設計還是系統執行時的設定檔,利用編輯器明白地告知使用者所有設定即將造成的影響,再使用轉換器讀入設定值並反應出應該要有的動作。

減少懂得設定之前學習曲線與設定時的花費時間,將有助於所有人員對於系統的了解、使用與操作,這也會是節省專案開發時耗費資源的最大捷徑。

2007年10月3日 星期三

E25 專案分析工具(3)──將分析與設計的記錄匯成產出

再進一步想,以文字檔形式讀入的其他專案內容有Flow Engine定義檔、訊息內容檔案、元件設定檔案……等等,凡是在執行時期以文字檔案生成或設定系統的一部分者,都可以使用程式將記錄加以轉換產生。

由於實際系統的產出分散在許多地方,在取得清單與比較內容這樣的組合動作上都比較不易處理。倘使用集中處理再匯出的方式,同時加上把系統實際產出內容抓取到集中處理的設定檔,這樣就可以擴充到取得系統的目前狀態並加以修改再匯回系統的處理模式。

雖然集中處理檔案設定的內容,已經可以加快設定與產出系統檔案的速度,但是要去產生或編修相關的內容卻還是需要對系統內部有相當程度的了解。為了讓並未完全清楚系統運作的人員(甚至是幾乎不懂系統內部的使用者)能夠順利地正確設定,勢必還需要有輔助的設計才行。

2007年10月2日 星期二

E24 專案分析工具(2)──需求分析與設計的記錄

在需求分析與設計時,我們需要對收集到的資訊加以分類並整理,要能明白每個功能是由哪些動作所組成,而那些動作允許有哪些檔案與內容的存在。分析出來的需求規格書會以文字描述為主,接著對應到設計的設計規格書就會將文字描述轉換為應有的系統產出。

專案的整體設定、各個功能的需求規格書與設計規格書,在實作的觀點來說全部都是在描述系統即將會有的產出。通常程式員會依照規格的內容,逐一做出專案裡對應的所有產出,這一向都是最花費人力資源的工作。

試想,以上的文件與產出其實都有關係存在,如果我們把所有的內容與關係都定義在表格裡的話,系統分析人員就只需要把想法填入表格就可以期望未來有對應的產出。這個概念,就像是設計時把想法直接放到Design Model,再經由其他的工具轉換出我們期望的文件(產出),將會節省許多開發人力。

2007年10月1日 星期一

E23 專案分析工具(1)──執行設定檔的編輯器

我一直強調在設計時,一定要讓影響程式運行的參數定義在外部的文字檔(或資料庫),最大的理由就是讓改變設定不要發生在程式內部,因為設定會由人來決定要設定什麼。如果封裝到程式裡面的話,只要一改變就會需要作改變的追溯。

在設計時依據架構設計的原則,產出層次整齊且方式一致的設定檔案,會比較容易讓使用的人找到想要的設定內容。在提供給使用者修改的大多情況下,由於必須防止程度較差的人作出錯誤修改而導致系統停擺,所以都會準備編輯器;透過編輯器可以只顯示出使用者可以修改的地方與現在的值,使用者只能輸入經過檢核的值而且保證以正確的格式放在正確的位置。

利用通用編輯器可以對應編輯一個檔案,我們可以收集專案所有設定檔清單加以分門別類,做出一個符合專案範圍的設定檔編輯工具。這時對於使用者而言,只要打開編輯器就可以保證看到的是他全部所能調整的設定,同時也不用擔心輸入錯誤的問題,因為這些都已經被編輯器加以管理。

2007年9月30日 星期日

E22 設計的終點(3)──快速組合出商業功能模組

對我來說,SOA的想法實際上與Flow Engine的想法是幾乎相等的。作法並沒有什麼困難,但成敗的關鍵也同樣在於Web Service(等同元件)的設計想法;唯有封裝完整且功能齊全的獨立元件,才能滿足SOA想法的需求。

設計一個通用的Flow Step,可以依照傳入的id搜尋對應的Web Service執行,並可以將Context與Data Model作雙向的轉換(參考E21)。在Flow Engine裡的所有Flow Step就不再依class name呼叫實作,而是使用這個通用Flow Step並設定Web Service id。

當我們拿到Use Case需求後,首先還是要先分析完成這個Use Case需要哪些動作,以什麼樣的順序執行。在Flow Engine上就依執行的順序先定義好執行的框架,接著依每個動作的目的找出最適合的Web Service並將其id設定在Flow Step上,最後再定義好使用的傳入條件與傳出結果便算完成。

與架構設計的Controller概念比較,設計方式是不是沒什麼差別呢?

2007年9月29日 星期六

E21 SOA的實驗(3)──應用Context與狀態放置達成SOA

我想的概念很簡單:先從狀態放置區找出還沒有執行的Flow Step,再從Flow Engine的設定上決定Flow Step的執行先後關係,接著就依Flow Step的內容直接找Component 或Web Service完成動作並註記狀態。

以這個目的來思考,我們就得先設計好Context與執行狀態的存取,唯有資料有便利存放的地方才能夠快速且正確地使用它們;接下來的課題就是如何讓Context與執行狀態與Web Service的要求的XML迅速地作轉換。

我的作法會是根據每個Web Service宣告一個實作自Basic Data Model的資料模組來對應,在使用Web Service之前固定做資料設定的動作後,生成XML傳入;使用後傳回的XML依前述動作反著執行讓最新的資料更新回Context,就能夠讓系統繼續運行了。

2007年9月28日 星期五

E20 SOA的實驗(2)──在Context規劃執行狀態區

在最初的設計,大多都只會以一個變數來存放現在執行到哪一個狀態或結果,並依此來控管功能的執行結果。可是這只記錄了一個結果,對於執行清單與歷史都一無所知。

不管是不是使用Web Service,我們都應該在Context裡放置功能執行時所有使用到的元件或Web Service的執行狀態區;如此一來每個步驟除了可以把結果放入之外,還可以在執行前先判斷該步驟是否已經執行過。在這種設計之下我們將可以隨時追蹤Context所有執行過程的歷史與狀態,同時也可以再加上設計把所有未執行的動作同時發送給Web Service再依結果更新Context。

預留一個集合存放指向所有使用的Context物件,對於整個專案來說只要存取得到集合就具有監看所有已啟動Context的執行狀態及內容,甚至可以直接改變Context的內容來對該執行物件產生決定性的影響。

2007年9月27日 星期四

E19 SOA的實驗(1)──將Component包裝成Web Service

當Component沒有自己的預設行為,完全依靠外界傳入的設定與資料來決定它的執行內容時,就很適合再定義為Web Service。

Component傳遞的是物件,Web Service傳遞的則是XML字串。只要定義一個WebServiceInterface,在其中定義一個方法,其實作是將傳入的XML再成為Data Model、執行狀態與Environment,並轉呼叫Component的方法將這些物件傳入;最後執行結束或是有Exception時依照固定規則註記在執行狀態裡,最後把Data Model與執行狀態回復為XML傳回即可。

不管是元件或是Web Service,其目的都是將特定目的的動作封裝在固定範圍裡,只有傳入的資料與設定會影響內部運作的邏輯,執行結束再傳回執行的結果與改變後的資料供上一層的Controller來判斷與使用。這是必須掌握的原則。

2007年9月26日 星期三

E18 Project Controller(4)──Runtime Environment

把元件的觀念放大到系統,此時檢視所有的輸出輸入物件設計:Context等同於CompomentModel、Flow Engine等同於ComponentController、Flow Step等同於ComponentAction、傳回值的作用相當於拋出ComponentException。最後需要的是作用等同於ComponentProperties的Runtime Environment。

為了要讓每次的執行都是獨立的,在執行之前應該要有各自的初始化動作,另外就是提供執行時需要系統設定的內容存放的地方,這些就是Environment的存在意義。使用Environment時,可以在每次的執行方法中傳入,或是同樣包含在Context裡頭。

Environment的設計同樣應該是介面,依需求與設計陸續加上所需要的參數存取方法;實作的時候一樣繼承Basic Data Model,並衍生出以字串的保留與重現動作。有時系統的參數是存放在特定的機制之下(例開發Eclipse的plugin時會有它自己用的Project Properties),這個時候除了實作特定機制的用法外,還要再做出一組基本類別的以達到動態儲存與回復的目的。

設計裡的傳遞物件最好不要使用singleton的設計,也不要用方法設定到Flow Engine或Controller中存放再取得,以避免相依性過高,切記要在使用之前才傳入該次所要使用的。

2007年9月25日 星期二

E17 Project Controller(3)──Controller處理狀態的保留與重現

Controller實作的時候,我們大多會宣告變數(少數會用物件)來暫存執行時的狀態,這些大多發生在程式裡,即使在設計Flow Engine也是如此。這樣的設計會使得執行狀態只能在同一部電腦內被控制。

宣告一個類似Context的執行狀態存放區,裡面記錄著執行Flow Engine時的定義檔全部有哪些步驟,各個步驟是否已經執行過而且結果為何,同時讓它像Context那樣可以輸出為字串並再生回物件。這樣一來我們就有機會做到在這裡做到一半的流程,傳送到另一部機器繼續處理(當然要包括Context一起);或是每做完一步驟就將之儲存下來,如因故中斷時可以叫起來接著再做。

這個概念也如何Context般,可以使得測試與除錯變得更加方便。可以想像一下,在一個重要的測試動作前如果必須連上客戶大型主機,又要準備特殊資料時的不便;在花時間準備一次資料後,記錄下該動作執行後的狀態與Context,就能夠只針對指定的Flow Step作密集的測試。

執行狀態資料區可以附屬在Context裡,每次執行前都初始化其狀態。執行狀態跟著Context搬移將能夠更快速地取得與對照。

2007年9月24日 星期一

E16 設計的終點(2)──快速應付Controller的改變

實作Flow Step的動作後以Flow Engine定義來串連,其實這就像我們寫的程式在邏輯上呼叫已經完成的API一樣,道理都是相同的;但更進一步的好處是我們可以使用外部定義檔來決定執行的步驟與順序,而不需要更動到任何程式碼。

在只需要改變設定檔案的情形下,就適合使用編輯器來編輯檔案的設定內容。應用文字編輯器是最差的選擇,但那也比改程式要好上非常多;理想的作法是根據用途來製作特定的編輯器。像Flow Engine的定義就可以使用類似繪製流程圖那樣的編輯工具,只要定義好每個圖樣所代表的Flow Step,就能夠以圖形方式定義好執行的流程。

Flow Engine在使用Flow Step時,可作成兩種執行方式:一種是直接依Class Name來生成Flow Step並呼叫執行的方法,另一種則是取得Component ID再到網路上搜尋適合的Web Service來執行該動作。如此可使得功能執行的流程變化更為彈性且方便。

2007年9月23日 星期日

E15 Project Controller(2)──Controller Flow Step

根據商業邏輯的步驟,我們同樣要將聚合力較強的動作合併在同一個Flow Step裡,較弱的則分開到不同的Flow Step執行。這是專案裡流程設計的基本。Flow Step裡通常會有達成功能時自己的控制步驟,在這時可以用單一Controller的角度來設計內部。

每個Step Class在執行的時候都會傳入一個Context,在裡面取得傳入的物件,並將執行後需要傳出的物件放回;執行後的狀況以傳回值通知Flow Engine來決定下一個該呼叫哪個Flow Step。Flow Step裡所有的傳回值都應在Flow Engine上有相對的定義。

所有Flow Step都實作同樣的介面,這意味著每一個Flow Step可以任何一種次序來執行,只要我們在定義Flow Engine時注意Context內物件使用的先後關係,就可以自由定義執行的順序。Flow Step在設計時以完整達成一個特定功能為目標,因為這可以符合再進一步使用Web Service的要求。

2007年9月22日 星期六

E14 Project Controller(1)──Controller Flow Engine

最基本的Controller很自然地就只是一個Class,處理的流程與每個步驟都寫在裡面;在較好的設計下,我們應該得到處理的流程與步驟動作分開的結果。

在處理流程部分,雖然已將內容侷限在純粹的流程控制,但是到底還是以程式碼來實現,任何的內容變動都會造成影響而必須有後續的分析與測試。理想的設計會在這個部分以Flow Engine框架讀入使用者的定義檔案,再依內容來實行控制。

Flow Engine的控制概念,是定義通用的Flow Step介面,動作被封裝在實作該介面的類別裡,執行時指定第一個動作的Class進入執行,介面的執行方法會有傳回值,定義檔裡會定義每個動作Class執行後的所有傳回值各要跳往哪個步驟去執行。依此概念執行到沒有下一個動作Class為止。

在流程生命週期中,傳入的Context是負責存放Flow Engine內所有輸出輸入物件的唯一資料集合。Context對應Flow Engine的關係,就有如Data Model對應Controller的關係。

2007年9月21日 星期五

E13 設計的終點(1)──快速應付Data Model的改變

Project Data Model直接繼承Basic Data Model其實也只是個快速的作法,當類似的Project Data Model會在多個專案出現時,同樣也要在兩者之間再加上一個中間性質的Data Model作為吸收改變的設計。

如果改變的是存放的屬性,我們可以變更存放那個資料的類別存取方法;如果改變的是資料模組,我們可以增減或改變類別的名稱;如果改變的是類別放置在Context內的群組與位置,我們可以改變Context存取這個類別的存取方法。

經由一層層對應管理資料模組的設計,我們面對改變時只需要確認屬於哪一個層次,接著就在屬於那個層次的類別介面修改存取的方式,並且將改變封裝到類別裡頭,由該類別與其繼承的類別共同合作以完成符合該變更的修改;加上Basic Data Model已經實作好存取資料的功能,因而可以只注重存取的控制。

即使全部都只是Data Model,實際在設計與使用時還是能夠依照定義給予不同層次的定義。設計好具有意義的層次後,改奱的影響就很容易界定出範圍來。更重要的一點是藉由Model名稱定義生成Data Model的機制可以讓物件的生成更為機動與快速。

2007年9月20日 星期四

E12 Project Data Model(4)──Model Service

系統裡的Project Data Model可能有很多種,其中某些Data Model的存取需要參考其他Data Model,存取的規則造成了他們之間的關係,而系統內這樣的Data Model關係也可能發生不少。

Project Data Model之間的水平使用關係,我們可以利用Model Service來加以封裝。每一個主要的Data Model(被存取資料的)都定義一個對應的Model Service類別,其他需要參考的Data Model則使用參數的方式傳入呼叫的方法,方法則根據呼叫時的參考規則實作並加以封裝。

一開始受到“Data Model可以含有存取邏輯”的影響,硬要把需要參考其他Data Model的動作寫進主要Data Model裡,卻發現不管怎麼定義都有不適當的地方,最後才終於領悟到要在Data Model之上再加上一層可以存取不同Data Model的Data Service才能夠妥善地處理。

2007年9月19日 星期三

E11 Project Data Model(3)──Context內容的保留與重現

這個功能的開發原本只是選項,因為這個想法與增進開發速度、應付未來改變都沒有什麼關係;但是實作這個功能卻能夠增進偵錯與維護的效益。

有錯誤報告的時候要附上能夠重現錯誤狀況的所有操作步驟,這是所有人的共識;這是因為有固定的步驟可以展現錯誤,在修改之後依舊可以用同樣的操作來判斷是否已經排除問題並檢查是否有其他衍生問題。但是在出現問題的關鍵操作動作之前所有做的事,都只是為了把進入關鍵動作所需要的所有資料準備好而已。

擁有這個功能後,發現錯誤的人可以在錯誤動作的前後都匯出一份包括Context所有資料值的檔案。負責修改問題的人應用工具把資料檔案再回復為Context並呼叫錯誤動作的方法,就可以立即開始測試;甚至有的時候只要觀察Context檔案的內容就可以發現端倪。

如果把匯出匯入的動作再細分為先針對XML字串的話,這將會成為Web Service輸出輸入所用資料模組的XML標準;因為Web Service元件收到XML後也是先重現為Data Model再執行,完成後再將Data Model轉換為XML傳回。

2007年9月18日 星期二

E10 Project Data Model(2)──Context

專案裡使用的Project Data Model實體一定會有很多個,如果要個別記得每一個實體的存在位置,這又是另外一種痛苦。應用集合與物件的想法,所以我們需要為專案定義一個存放所有資料物件的集合區域──Context。

按照使用範圍與生命週期的考量,Context裡至少有與程式共存亡的區域(Class)以及執行功能時才需要的資料區(Class);考慮所有Project Data Model使用與存在的時機定義相對的區域並在裡面宣告放置的變數。由於每個物件的存在都應有它自己的意義,於是符合動態流程的每個時間點都該有對應的使用資料區。

接著由底層的區域開始,每個區域都針對其內部存放的Project Data Model定義存取方法;依此逐步往上,遇到內部有放置其他的區域時也另外定義存取方法。經過一層層的封裝與定義,最後就形成可以從中取得所有runtime資料模組的Context。

全部的程式集合起來會成為系統,所有的資料模組集合起來會成為Context。我們可以把Context想像為一個對應系統的最大Data Model。

2007年9月17日 星期一

E09 Project Data Model(1)──向上的Data Model延伸

雖然Basic Data Model已經涵蓋了我們放置資料的需求,但那只是最基礎的set與get動作;在Data Model需要放置的資料數量很多時,每種資料的存放都會有各自的方式。如果我們不再為專案使用的資料包裝一層方便操作的存取方法,那麼撰寫程式的人必須去記得每一種需要資料的存取法。

抽取與封裝的動作,除了把共用的部分放到通用的地方之外,另一個的功能是把一些較複雜的邏輯隱藏在裡頭,讓使用的人只需要記得呼叫的入口就能正常使用。只記一個入口當然要比記數條規則輕鬆且不易出錯;我們只需要犧牲一點人力去包裝這類的規則,就可以讓所有撰寫程式的人在專案裡所有可套用的地方節省開發的時間,同時又把未來可能改變的影響封裝起來,這鐵定是划算的。

設計時把適合的資料定義在Project Data Model裡,同時為之準備getter與setter存取方法,記得要逐一宣告好而不要為了快而合併或短少。完整定義後的Project Data Model就可以提供給設計人員接著使用,實際上則繼承一個可以對應儲存方式的Basic Data Model直接使用元件提供的存取功能。這麼逐一把所有資料設計存放的資料模組後,就具有一組完全符合專案使用需求的Project Data Model了。

另外有一個重要功能必須同時實作出來:Project Data Model需要根據存放在Basic Data Model的某個值來產生該Project Data Model類別的實體。這種以Factory方式動態產生各種不同Data Model實體的功能將會讓專案的使用更加便利。

2007年9月16日 星期日

E08 Basic Data Model(3)──資料通用編輯器

雖然我們可以直接打開檔案來編輯Data Model的檔案,但是大多使用者都會希望可以有個UI編輯器。使用UI編輯器的幾個好處是用視覺化的畫面增進美觀、限定輸入的選項避免放錯值、用內定的格式存到檔案防止格式錯誤時什麼都讀不到。

雖然檔案的儲存格式有好幾種,但幸運的是我們已經做好對應各種檔案的Parser,而且都同樣地產出Basic Data Model;所以我們只需要做一個可以編輯Basic Data Model的通用編輯器,就能夠處理所有有支援類型的檔案編輯。只需新增一種編輯器就能應付所有資料模組,這種作法可以增進許多開發速度。

編輯器需要的功能大致上是起動、讀檔、顯示資料、編輯資料、存檔與結束,需要根據資料動態產生的是顯示資料與編輯資料。name可以顯示在編輯欄位的提示,value自然是放進欄位的值,我們可以利用註解來放置一些輔助的描述,像詳細提示文字、編輯欄位的種類、合法的輸入值等等,讓編輯器程式處理,如此一來可以讓編輯頁面更實用。

作為使用Basic Data Model與Basic Parser的Component,它的功用是資料模組的通用UI編輯器。現在有三個基本Compoent。

2007年9月15日 星期六

E07 Basic Data Model(2)──向下的基本實作

Basic Data Model做出來後,下一步是依照前面選用的儲存方式,繼承Basic Data Model後逐一定義與設計對應Data Model的介面與實作類別。實作的用意在於把對應的存取方式包裝在getter與setter的方法裡,讓呼叫者毌須知道實際存取的方式而能夠拿到自己想要的資料。

再來要定義一組Basic Parser,它的主要功能是讀取檔案成為資料(load)與將資料儲存到檔案(save)。同時往下要定義各種Data Model的Parser,使用I/O的方法取得檔案的內容,同時依照資料結構放到設計時指定的位置;反之亦然。這麼一來我們已經設計好每一種Data Model對應的存檔與讀檔動作。

Basic Data Model與存取它的Basic Parser是資料模組的根本Component。為了讓其他人可以快速地學習使用,放入元件庫的元件一定要做好API說明與範例程式。好了,現在我們的元件庫裡有兩個基本的Component。

2007年9月14日 星期五

E06 Basic Data Model(1)──資料存放的根本

設計後的實作因為有專案的規定可以遵循,所以可以發揮與需要說明的地方並不多。從這篇以下的內容,將會著眼在使用元件設計與架構設計想法所得到的觀念所能夠衍生出來的實作應用。首先,就從最底層的Data Model看起。

Data Model要可以在所有元件與系統間通用的話,最省事的方法就是定義出一組Basic Data Model的介面與實作,因為這麼一來所有Data Model內容的基本存取方法都會相同而不必另外設計,傳遞之後的資料都可以立即存取。

設計Basic Data Model時,我們要先思考要對應到哪些儲存方式。現在比較通用的儲存方式有XML、text、properties、Excel與Database Table(其他的我不熟),事先分析各種資料儲存的類型與方式,設計出一個符合各種儲存方式的Data Model;有了能夠裝填所有資料的容器後,接下來就是要思考如何把檔案裡的資料裝入容器。

註:我選用的是XML的結構,每個Data Model可以有name、value、comment,同時有一組Attribute與一組child集合;其他種類的資料使用對應的方式放進這些變數之中。

2007年9月13日 星期四

E05 實作的記錄──寫下撰寫程式時的想法

不管是系統層面或是元件設計,設計的人都開立好每個層次的介面規格與類別名稱,實作的人只要依據類別、屬性與方法上的註解寫出符合規格的程式碼即可交差。表面上看起來似乎單調又無聊,但事實上要比表面上的有趣一點。

不管是在系統或是元件,設計的都只是框架與訊息呼叫的順序,實作的人需要用真實的程式碼來實現所有的功能。在使用其它公司提供的元件時,通常會有人先寫好prototype範例,實作時只要依照內容撰寫;但是prototype重視的只是功能的達成,在狀況的控制上相對地不夠週全。在設計時因沒有實際去做,光憑想像也無法妥善地控制好一切;這時就需要實作的人去補齊狀況的控管。

還有另一種情形是設計的人指定了要使用的其他元件,但是並沒有提供範例程式。這個時候實作的人就必須在設計的框架下,找出正常操作元件的確切方法並產生程式。即使只是依照邏輯撰寫程式時,也得依照專案規定,找出系統提供的底層API來呼叫以完成動作。

無論做了什麼動作,實作的人應該在程式之間夾上註解來說明當時的想法、作法、碰過的一些狀況與為何決定這樣實作的經過,往後接手或維護的人才能夠立即知道當初為什麼要這樣做。不用說也知道,當初我在維護系統時看到的鐵定是沒有註解的純粹程式碼,而且沒有人記得為什麼要那麼寫……。

◎系統的目標:

2007年9月12日 星期三

E04 實作的開始──Code Generation

設計的內容裡有許多的Package、Class與Class內的attribute、operation,運用UML工具提供的功能可以直接把設計內容轉變為程式框架,開發人員要做的只是根據開發指引、設計文件與註解內容填入適當的程式碼,通過單元測試之後就可以暫時收工。

雖然好一點的UML工具提供了Design Model與程式碼的雙向同步,但是強烈建議絕對不要在實作階段修改程式後再同步回Design Model。系統的設計是立體的,Use Case與架構設計組成了水平面的X軸與Y軸,專案與元件的向下使用關係則是Z軸,每一個程式、方法或屬性都有它應在的位置與該做的工作。

認同每個物件與方法存在的位置都需要評估,那麼我們可以明白任何的變動也需要先追溯其影響再決定怎麼做才是最適當的。撰寫時雖然可以直接修改,短期內似乎可以解決問題,但沒有人敢保證未來不會發生別的問題。

把設計轉出程式框架之後,我們需要的是日後有改變設計的狀況時,可以保持已經撰寫好的程式碼又能夠把新的設計放進程式的功能。選擇具有這種功能的工具,減少改變設計時的異動會有助於養成先設計再撰寫的習慣。

2007年9月11日 星期二

E03 實作的準則──Coding Standard

在結束設計進入實作之前,為了維持所有撰寫程式的產出有一定的風格與使用規則,這時必須有一份Coding Standard供所有程式人員作為程式實作的遵循規則。

Coding Standard的規定範圍甚廣,從命名規則、匯入宣告、註解內容、撰寫風格、甚至連括號的位置都會有所規範,其用意就在於專案裡任何一個人都能夠立即看懂其他人所寫出來的程式。我們可以想像命名方式不同時另一個人還得多花時間去明白一些很基本的東西,這都會是無形的資源浪費,同步好基本想法後,專案成員就可以投注更多心力在於系統的功能開發。

接著應該會有通用API文件,瞭解並熟悉這些通用API的使用可以快速地組成需要的小功能;在不清楚有哪些通用API的情形下,會時常發現到處都會散落著有同樣功能的小方法,如此一來呼叫的使用關係無法收斂,也降低了程式碼的共用性。

再上一層要有通用元件的使用文件,讓開發人員依照設計的要求傳入正確的物件及變數,並作正確的判斷。API層級的所有文件,都是要讓所有人知道如何使用基本的功能與元件來達成設計的內容。

經由規範與準則,我們可以同步專案中所有人員的基本想法與產出水準。

2007年9月10日 星期一

E02 實作時由下往上

在妥善的設計之後,其實要從中任擇一段開始開發都可以,但是還是按部就班地從最底層開始實作會比較適合。其中一個原因是在我們的設計裡,呼叫都是往下層進行,在確保下層程式都已經完成的同時,我們隨即可以對現在開發的程式進行測試。

不過我認為最重要的原因是由於系統是由上往下的設計,下面的變動影響是往上層擴散。如果一開始就在底層實作時發現設計階段一些忽略的問題,都還來得及馬上修正而不用擔心向上的影響;反之如果從上往下實作,卻在最底層的重要元件發現問題,改完之後望著一大堆受到影響的程式清單,心裡大概是欲哭無淚的感覺。

先寫上層的程式還有一個小缺點,就是在單元測試時如果使用的下層程式還沒準備好,就必須先準備一個假的回應程式。要多做一個物件,那個動作就必須花費一些資源,當然能省的地方就儘量省下來。

2007年9月9日 星期日

E01 使用OOAD開發就可以提升軟體品質?

在軟體業裡似乎流行起魔法,謠傳說只要公司用OOAD技術開發就可以大幅提升軟體品質或是減少開發時間等等的,聽到的人都感覺很興奮。但是OOAD本質上只是一種設計方法,即使應用了方便的工具還是不見得有希望有的成效。

設計最重要的還是心裡的想法,方法與工具只是輔助落實心裡想的使之可以更詳細表達出來,對於設計內容並沒有實質的幫助。一名把整個系統視為一個大Component來開發的人,不管用什麼方式都只會產出得到一個大黑箱的設計,完全不能期待換成OOAD會有什麼不同。這應該是我們最基本的認知。

不管用哪一種方法來設計,確認好Data Model、架構水平層次與垂直使用層次,並加上應付改變的設計;然後經由不斷地review、討論與修正,逐漸修正為最適合這個系統的彈性設計才能提升軟體品質。再經由包裝獨立元件的軟體工廠挑選適合的元件快速組裝底層動作部分,未來可以將設計重心放在商業邏輯的控制,以此來減少日後的開發時間。

提升軟體品質的動作其實是耗費時間的,但是可以省卻的是設計不良時維護的痛苦,若能再加上提供適當的reuse元件來減少底層部分的設計,在第一個專案時或許感覺不到改進,但是從第二個類似專案起就可以開始節省開發投入的資源。

當然,必要的前提是我們得保證軟體設計內容的彈性與品質才行。

2007年9月8日 星期六

D28 細部設計的流程(Model)

在元件設計的階段裡,是將局部的功能需要以類似一個小型系統的方式加以設計。記錄下來的物件關係模型如下面附圖。流程與動作的主軸與基本觀念是一致的,但是Properties的輸入由ComponentImpl來使用;ComponentController使用的Component Model因為封裝而改由ComponentImpl傳來。

◎一個ComponentImpl由一個或一個以上的ComponentController組合而成
◎每個ComponentImpl使用一組Properties與一種ComponentModel
◎每個ComponentControllerl有一個或一個以上的ComponentAction(可能沒有)
◎每個ComponentController有可能傳回Return Code(可能沒有)
◎每個ComponentController都應該定義無法處理的Exception狀況(可能沒有)
◎每個例外狀況可以使用其他的ComponentAction或經由ComponentImpl拋出去註:這裡的Component也有可能是一段程式碼。


每個Component在設計時都有Class Diagram與Sequence Diagram,設計的同時每個Class都要定義屬性與方法,同時附上註解。Class會存在於Package(通常等同於Component的範圍),所以每個Package也會有一張Class Diagram來描述有哪些Class。

追溯關係則至少有三種:Component Controller與Component Action的使用關係。此外,訊息、參數與例外的內容應有清單與影響的關係追溯。
◎系統的目標:

2007年9月7日 星期五

D27 物件關係追溯的極致

現在想像一個Basic Data Model方法的內容需要修改,在我們的設計裡資料模組是所有元件與Controller使用的物件,所以我們必須重新測試系統所有的功能。這樣對嗎?只追溯物件使用的關係,的確只能這樣處理,事實上修改物件的一個方法時,應該只要確認那個方法被哪些物件在哪些方法裡使用,再往上搜尋所得的結果才是真正的影響。

在改變後為了確認所有被影響的動作都是否正常時,會因為只是物件層級的概略追溯而造成錯估真正的影響範圍,會付出比實際影響範圍超出許多的不必要測試之類的資源浪費。一個系統的在測試階段的問題單很可能上千張,如果每個問題的解決都或多或少地浪費了一些資源,累積起來到專案結束時總共有多少呢?

然而記錄得越詳細,所需要的資源的資源也會比較多,這時我們需要便利的追溯工具來減輕負擔。不要使用程式開發工具裡的追溯,因為層次一多,尋找的次數與內容也相對增加很多。在設計的同時其實使用關聯就已經存在於Design Model裡,只要有相對的工具可以便利地列出指定物件的追溯關係,這樣一來就方便許多。

2007年9月6日 星期四

D26 物件關係追溯的意義

每個物件在系統裡都有其存在的意義,也有與其他物件互動的關聯。追溯就是記錄物件與其他物件使用的所有關係。

一個物件所負責的動作或是處理邏輯因需求的變化或是發現錯誤的修正而有所改變時,都有可能連帶到影響使用它的其他物件;影響連帶地使上層物件有可能被改變,又再造成上一層其他物件可能被影響。如此循回下去,找出所有會使用到此物件的連帶關係。我們必須測試過使用關係裡的所有物件,才能保證這次的修改除了解決現有的問題之外,沒有再形成任何其他的問題。

有的時候我們需要把某個功能或物件獨立出來使用,這個情況我們需要知道往下使用了哪些物件,所以要從上往下尋找。以一個物件為中心,向下找出它使用的所有關聯物件,與向上找出會連帶使用它的所有關聯物件,就可以定義出它的影響範圍,這是每位從事軟體工程的人最基本的思考模式。

以自己的經驗來看,身邊的人會認真思考追溯關係的實在很少。工作上接觸的外國人士,像香港與新加坡,對於影響追溯的記錄追蹤相對地積極許多。

2007年9月5日 星期三

D25 追溯關係(3)──Package, Component & Class

在Rose Model裡,只要我們建立了物件與物件之間的關聯,那麼不管是在Rose的畫面或是SoDA產生的文件,都可以追溯到使用自己的物件與自己使用的物件。在Component View裡的所有物件與Diagram也是一樣。

從最上層來看,我們需要有全部的Package List與Package vs Package的水平追溯;接著要有每個Package vs Component的垂直追溯與Component vs Component的水平追溯;再往下還得有Component vs Class與Class vs Class的水平追溯。

對於一個元件來說,把元件內部的Class與Interface同時放到縱軸與橫軸,並於橫軸加上所使用到的其他元件Class與Interface,這是我們對於一個元件內部設計所應該追溯到的關聯範圍。

每一個物件的存在都有他所屬於的地方與他所擁有的物件,找出物件這一類的關聯存在是垂直追溯;物件本身在運作時會使用到的其他物件(應都是同樣層級),找出物件使用關聯的存在則是水平追溯。

2007年9月4日 星期二

D24 Component的使用關係──Component Diagram

設計元件的同時,時常會有動作需要使用其他已經開發完成的元件。雖然在Sequence Diagram與Class Diagram裡記錄了其他元件的Interface,但是若想知道使用一個元件時同時還需要“附帶”加上哪些元件才能運作正常的話,如果沒有記錄下來使用關聯,就絕對不是一時三刻內可以知道的。

想要快速地知道元件的使用關聯,同樣必須付出努力來記錄才能擁有這種效果;作法是在繪製UML Diagram時同時在Component Diagram記錄下元件間的關聯。首先建立放置元件的Package,Package的作用是library的界限,未來放置在同一個Package裡的所有元件實際上都會集合成一個library file 。這個時候先用一張Component Diagram放上所有的Package(先不要有使用關聯)。

接下來依序處理元件。首先要決定元件要放在哪一個Package裡,可以直接在Package節點上New Component並修改名稱。接著在Package裡新增一張Component Diagram,把所有Package裡的元件放進去,再逐一把使用到的其他Package元件放入並拉上使用關聯;與其他Package有關聯時,記得回到上一張圖拉起Package之間的關聯。


依序處理每一個Component與Package直到所有物件的位置與關係都正確為止。

2007年9月3日 星期一

D23 Component的設計產出──Sequence Diagram & Class Diagram

以介面定義作為規格的封裝元件,就像是一個獨立運作的小型系統。設計時以Implementation為起點,將元件的控製邏輯想法,逐一繪製成這兩張UML Diagram。

設計的想法與系統極為類似,我們可以把每個Interface方法視作一個Use Case,依元件每個層次應有的Interface先用Class Diagram標記出來,再用Sequence Diagram串接應該呼叫的方法,依這個原則完成所有方法的流程設計。這裡只需要處理正常執行的流程,因為錯誤的狀況都直接拋出Component Exception。

在設計時最重要的原則,是要把元件設計成絕對獨立的個體,進入Interface之後使用與傳遞的物件,除了執行環境的基本類別之外,就應該只能繼承與使用與開發的系統無關的類別。如此一來可以讓元件與系統之間除了被使用之外,完全沒有關聯存在;這也意味著日後可以把元件提供給任何一個系統來使用,而沒有之前系統的包袱。

在系統設計時如果使用到元件庫裡的元件,完全不用處理細部設計。因為該元件是已經開發完成而且已經通過測試。

2007年9月2日 星期日

D22 清單與項目(3)──集合與物件

物件本來是單獨的個體,但是當一堆相似的物件因為特定的目的而群聚在一起時,他們就應該受到集合管理。

集合是管理所有物件之處,我們需要取得特定物件時直接向對應的集合發出要求;發出的要求應含有想取得物件的篩選條件,集合則依條件傳回所有符合的物件實體;當然也應有列出集合內所有物件的方法。管理的動作還另外包含了新增物件與移除物件,控制者可以依實際狀況來增刪集合內物件的實體。

資源的管理有三個大類:目標與步驟是先有目標再將之切割為多個步驟,所有步驟成果的總和應等於目標的結果。清單與項目是先有項目再將之條列為清單,根據現在的所有項目產生清單的內容。集合與物件則是定義一個集合來管理所有物件,物件的增刪都被動地經由集合來操作。

群組與個體之間會有著使用或管理的動作,這同時表示著它們具有關聯,所以他們相互的影響都需要被追溯以應付未來可能有的任何改變。

2007年9月1日 星期六

D21 令人感到麻煩的設計(6)──解決循環使用的現象

在設計的經驗裡,總是會“不小心”遇到像左圖那樣循環使用的現象,這是需要極力避免的。因為當B改變的時候,要追溯到使用它的A,如果A因為B的改變也作了改變,那麼使用A的B又要再度改變而形成無窮迴圈。

最簡單的解法,就是把A拆成兩個Package,使用B的放在A1,被B使用的集中在A2,這樣就可以讓循環使用的現象變為圖中的單向使用。可是實際遇到的情況大多都因為Package的內聚力過高而無法乾淨地分為兩個Package。

嘗試了幾種有問題的方式後,我提出右圖的解法:在B裡定義一個A2功能的介面(注意:這個介面屬於B),A維持原來使用B的動作,但內部原本要分割成A2的部分使之實作B提供的介面,如此一來A使用B與A實作B都可以是由上往下的使用關係。

2007年8月31日 星期五

D20 令人感到麻煩的設計(5)──Abstract Class的設計

同樣的程式碼抽取到方法讓大家共用,同樣的方法抽取到共同的父類別來使用,這是設計中對於程式碼的reuse。
這是是一個簡單的繼承架構,ClassInterface下有一個DefaultClass作為基本實作類別,另外衍生了三個子類別。理論上DefaultClass應該會做出介面裡定義的全部動作,子類別在方法裡有不同的動作時再override(覆寫)父類別的的方法。

有的時候介面的方法很明顯地是該由各個子類別來決定如何實作,這個時候父類別就不可能有可以共用的方法實作。例如一個交通工具介面裡有move()方法,因為每種交通工具移動的方法不同,所以實作介面的基本類別,move()方法就應該保持為abstract等待子類別來實作。

會有人說:先隨便實作一個方法再由子類別override不是也可以嗎?設計時不該讓物件進行沒有必要的動作,不僅會令物件進行沒有定義的行為,同時不必要的override也讓程式更難追蹤。Abstract Class是無法產生實體的對應,只是對於物件的定義而不會變成物件;在意義上就像生物學裡的分類,界門綱目科屬種各定義了不同所屬物種的特徵,到最後的分類時(種)才有所有符合所有特徵定義的生物。

交通工具類別已經宣告move()為abstract,但是直接把汽車、輪船、飛機等直接繼承交通工具也會有問題,因為子類別裡還是會有多個類別有同樣動作的情形,一有改變就可能牽動到所有子類別。理想的設計是再衍成對應的分類類別,子類別再去繼承分類類別。一組類別要設計成這樣時比起快速地直接使用一個類別,當然會多花很多的時間,不過應做的工作還是該去做好的。

2007年8月30日 星期四

D19 令人感到麻煩的設計(4)──物件關係的影響

在B21裡提到了物件的三個關聯:is、has、use,其中關係最緊密的是is,其次是has、最鬆散的是use。

左圖的專案物件目前繼承CompClass2,在宣告的同時專案物件就永遠是一個CompClass2而不可能會變成其他種類,動作的時候就只能依CompClass2的規範。右圖的專案物件使用一個變數來記住內含的CompClassInterface,可以依需要操作設定的CompClassInterface實作(注意:三種CompClass都可以設定),這就增加了專案物件使用的彈性(Strategy Design Pattern)。

右圖的專案物件本身可以使用前一篇的方法繼承另一個Class,加上混用Wrapper Design Pattern讓專案物件同時實作CompClassInterface,並將實作方法全部直接呼叫strategy設定的CompClass的話,我們就可以擁有身具兩種CompClass的專案元件應用在更多的地方。(必要時還可以增加其他的strategy來擴充功能)

use是在呼叫時傳入物件,物件只在方法中使用而不另外記憶下來;在服務性質的API可以大量使用這種方式。呼叫Web Service時使用的就是這種物件關係。

2007年8月29日 星期三

D18 令人感到麻煩的設計(3)──吸收改變的避震器


上面這張圖的狀況是專案物件會廣泛地使用一個底層API。一開始我們很直覺地使用左圖裡的方式直接呼叫BasicAPI的方法,而且沒有遇到什麼問題;後來我們發現有些連續呼叫數個BasicAPI的方法在專案裡很常發生,需要抽出另外成為固定可用的API,於是我們建立了一個BasicAPIExt1,在遇到需要呼叫特定連續方法時使用新的BasicAPIExt1,其他情況則直接呼叫BasicAPI。

中間的圖我們可以看出,專案物件有些呼叫BasicAPI,有些則呼叫BasicAPIExt1,不僅層次的使用比較雜亂,每個專案物件有使用關聯的物件也變多了。設計該是具有層次與抽出共用部分簡化關係的,所以會用右圖的設計,讓BasicAPIExt1繼承BasicAPI來達到這些目的;而且未來BasicAPI的某些改變只會影響到BasicAPIExt1而不會影響到專案物件。

這個範例應用的範圍不只是API,應該包括專案所有使用(static method)或繼承關係的設計範圍。如果能夠費心把專案與元件之間的使用關係都加上一個類似避震器用途的類別(即使只是沿用也要加)的話,將會更輕易應付突然發生的改變。

2007年8月28日 星期二

D17 理論串接實作的瓶頸(9)──專案思維與元件思維

從系統的角度來看,需要呈現給使用者看到的是View、Model、顯示狀態的Message與處理中記錄的Log,會從系統外部來的有Properties與Model。元件如果像縮小的系統,是不是應該也要考慮這些與輸出入有關的物件呢?

我的答案是否定的。在前面我所切分的元件層次裡,主要有功能與功能的Controller、傳入的Properties、再加上一個Implementation用Factory處理功能的切換與用Wrapper把傳入的基本Model轉變為Component Model。View、Message與Log呢?它們只允許存在於系統?

元件應該是一個獨立的單位,它最重要的工作是完成我們需要的功能,並在無法處理時回報狀況與資訊。View的作用在於執行前輸入資料並於執行後顯示結果,這些資料其實都屬於Model的內容,元件只要能處理Model就可以工作;如果把View寫進元件裡頭,要是下一個系統使用的View不相容時應該又會是一樁慘劇。基於這個想法,元件也不應拋出提示的Dialog,我們可以規定功能的完整執行要進行三次呼叫,由上層的系統來控制每一次呼叫之中要做哪些提示動作。

Log的問題也很類似,把這個系統使用的Log物件傳入元件裡記錄,要是下次用的是另一個種類呢?Message則會牽涉內容的客製化與多國語言的問題,同樣不應該進入到元件裡面處理。每個元件的輸入Properties名稱與執行後的Message內容都應該設定成唯一存在的id,參數與資訊都使用代號後就可以在系統層級儘情地客製化為喜歡的樣子;當然,元件本身應該提供一個預設範本供系統直接使用或參考修改。

2007年8月27日 星期一

D16 Copy-Paste絕對不是Reuse

許多人在面對需要新增一項功能時,如果發現已經存在一個類似的時,心裡想到的必然是重覆使用已存在的,但是需求上有些微的差異必須實作,可是又擔心修改原有的會造成side effect。這時通常祭出的是copy-paste大法,快速“生出”另外一組程式碼,再隨心所欲的修改內容。

以功能需求來說,這樣做絕對是滿足功能的最快方式,同時也可以保證功能的測試很快完成。但是,即使用的是copy-paste,對於系統來說“全部”都是新增的程式碼,根據新增程式碼的作業流程所應該製作的文件、測試與追溯一個都不能少;雖然連這些都可以複製原來的,但是這樣的行為一多,整個系統類似的物件就會以等比級數來成長。

寫程式時若有方法需要擴充功能,大家都知道在同樣的方法裡增加參數讓它可以處理更多樣的情況;功能上的想法也應該是如此,在元件裡加上符合新增需求的方法或參數,努力想辦法讓元件支援更多種的功能。在分析與設計之後,動到的程式碼與copy-paste相差無多,但是只要修改原有文件而不用再“生出”一堆其他的文件。

copy-paste出來的產物,只能應付特定範圍內的“某些指定功能”;經由設計層次定位的元件,卻能夠應付特定範圍內的“所有功能”。在未來開發的時候遇到同樣特定範圍的功能時,前者還需要列出所有元件以供挑選哪一個適合,後者則直接掛上一個元件就滿足。同樣的道理,前者在library裡必須記錄全部的Component,後者就只有一個Component。

copy-paste到底是好是壞?想讓自己產出的元件是什麼樣子?你的決定將會影響到後人對自己的評價。

2007年8月26日 星期日

D15 令人感到麻煩的設計(2)──所有水平層次的設計

在設計每一個層次時,我們同樣也可能會遇到其他人送來的設計圖長得類似下圖。他想表達的是三個不同的元件各有其定義的介面與實作的入口類別。



不管我們怎麼決定設計,其實該有的東西都有的話系統應該都能正常做出來。但設計的目的除了讓每個物件各司其職之外,另一個重要的目的是要去蕪存菁,把重覆的程式碼集中到上一層的類別裡。上面的設計因為實作直接對應介面而沒有任何父類別,因而造成每個實作類別都得存放所有的程式碼在裡頭;那麼上一篇提到的介面基本方法就會到處都是。

理想的設計,還是必須多花一點功夫設計一個BasicCompInterface與BasicCompImpl來收集通用的屬性與方法,實際使用的元件繼承他們之後再撰寫自己的介面與實作。如果元件衍生時有因專案類型而適用在不同行業時,最好針對每一種專有的行業再設計一組對應的介面與實作,如此才能把不同的屬性與方法放置在他們應該存在的層級,而且可以快速篩選出適用特定行業的元件。

2007年8月25日 星期六

D14 理想Component介面的基本

思考到現在,心裡理想的Component介面的基本逐漸地成型。除了本身功能提供的方法之外,我認為要有以下幾種基本的通用方法:

public void setProperties(Properties properties)
public Properties getProperties()
設定參數集合與取得現有參數集合的方法,要不要有存取單一參數的方法則視需求而定。不過既然可以拿到集合,再從中取得指定的都不是問題。提供這組參數存取方法之外,也需要有文件列示所有的參數名稱與其影響。

public ComponentModelInterface getComponentModelInterface(BasicModelInterface basicModel)
Component傳入的Model是這個元件專用的。基於軟體工廠的想法,所以提供一個方法把基本Model傳入後會在裡面自動Wrapper成Component Model,接下來就可以任意地傳入Component裡來執行。

public List getAllMessageIDs()
public String getMessage(String messageID)
Component的訊息的內容應該由文件交待全部有哪些,或者有範例檔提供全部的訊息代號與預設的訊息內容供系統複製後修改。如果不想花時間另做文件的話,可以提供像這樣的API取得所有的訊息代號與其內容再存放到系統也不錯。

ComponentException
例外要使用Component自己專用的,每個具有無法處理或回復狀況的方法都應該定義成拋出Component Exception。

2007年8月24日 星期五

D13 令人感到麻煩的設計(1)──Data Model的設計

直覺地把系統的動作寫成程式是最快的開發方式,在執行上也很可能比較快。設計雖然也同樣地把同樣的系統動作寫為程式,但是多了很多與處理層次有關的類別,不僅在開發上相對緩慢,執行上也會因為呼叫的路徑變長而變慢些。


想像一下,我們是印表機延伸工具的開發廠商。依前面所談的,公司已經準備好Base Model作為基本資料類別,現在第一版系統要支援三款印表機。在一開始的Data Model設計有人提出了像上圖的方案,身為Leader的你應該通過這個Data Model設計嗎?

這個設計基本上當然可以讓系統正常執行(就算只用Basic Model也可以,但那會一團亂),然而設計的目標是讓系統的程式分門別類地形成層次。這裡的問題在於印表機延伸工具的系統,會定義出一些自己專用的資料與存取方法,以這個設計來看,那些專用的方法都必須實作在三組Data Model裡,不管是個別使用的或者是三種用法都相同的。

如果我們曉得要把重覆使用的程式區段抽取為方法來使用是正確的方式,那麼把重覆使用的方法再抽取到一個類別來reuse也是應該做的事情。雖然設計因此多加了一個層次,但是每個物件各司其職不作重覆的事,是OOAD所要表達的一個重要精神。

2007年8月23日 星期四

D12 IF介面像插座THAN Data Model是電力規格

在C07曾用機器人的可置換手臂來說明介面的用途,不過目前Interface最常比喻為插頭;不管各式各樣的電器,只要插頭符合插座的介面就可以插上去使用。現在世界上有四種最常見的插座規格,即使是種類不同的插頭,加上所謂的轉換器(Adapter Design Pattern)後還是可以插進去使用。

這是以可見的外觀來說明介面的用途,其實再深一層來說,插頭插上後還有在線路裡流動的電力;唯有提供電壓、電流的種類完全吻合的插座,才能夠正常地驅動電器。以元件的觀點來看,除了介面必須吻合之外,在介面上傳輸的Data Model也必須要能溝通才能動作。

外在的介面定義與內在的處理資料類型,這正是元件封裝之後留存給外界使用的規格。有時設計者為了偷懶,外部看得到的介面與資料定義得很詳細,封裝後看不到的內部就設計為寫死的黑箱,使用的時候因為功能都相同而沒任何感覺,可是一旦有問題或是需要改變,就讓接手的人痛苦萬分。這樣的行為,是不是“圖一己之便利,留後患給旁人”呢?

2007年8月22日 星期三

D11 Component的設計(5)──Exception & Properties

Component裡既然有自己的控制邏輯,那麼期待產生元件設計之初沒有預期到的狀況也是應該的。預期中的狀況可以在Component Controller裡判斷並控制,預期之外或無法處理的狀況就應該拋出Exception告知更上層的控制以便因應。

拋出的Exception應該是這個Component專有的,如此一來可以在層層呼叫的使用時,立即判斷出是在哪個Component裡出現的狀況,進而取得Exception裡的資訊作對應的處理。我們不該傳回基本型態的Exception,也不應與其他Component共用Exception,因為會搞不清楚到底是誰拋出同時也無法取到任何更進一步的資訊,尤其在共用Exception時更會加重使用的關聯。

Component的參數有些人會設計為Component特有的定義檔,在生成時讀取內容並設定。這種方式在設計上很直覺,但衍生的問題是每個Compoent都會擁有至少一個設定檔,當系統擁有上百個有設定檔的Component後,使用者若要調整一些設定就得逐一找到存放的地方打開編輯並儲存。

理想的作法是將所有Component的改變參數都設計傳入類似Properties的集合物件,每個Component的每個參數都擁有唯一的名稱作識別,使用只要建構時傳入或呼叫設定的方法就可以改變Component的設定。所有Component的Properties存放就經由系統統一管理,在需要設定Component時從集中管理的地方找到對應的Properties並傳入即可。

把Component的設定集中到系統管理的最大好處,其實是我們可以針對整個系統的可調整參數設計一個方便使用者編輯參數內容的編輯程式,就類似Windows的控制台的設計概念。

2007年8月21日 星期二

D10 理論串接實作的瓶頸(8)──實現軟體工廠的機會

前面提過所有功能的目的都在於處理資料,以封裝Component的概念來說,唯一的輸入與輸出物件就是Component Model。有很多人說過軟體工廠是不易實現的理想,假使工廠不管生產什麼樣的元件都是處理基本型態相同的Component Model,這樣一來是不是有機會實現呢?

上一篇提到在Model本質不同時有三種資料轉換的方式,每一種都需要另外撰寫程式才能讓資料在不同模組之間轉換:第一種必須寫存取資料的實作,第二種必須撰寫Wrapper程式,第三種則必須寫搬動資料的動作。如果,我是說如果,一間公司裡所有Component Model與Data Model的根本都是相同性質的基本資料類別(比如說是Map),每個Component的方法都附有一個方法將傳入的基本資料類別,包裝為Component Model後傳回再傳入Component處理,處理時對基本資料類別的所有操作都可以自動反應回系統。

2006年開發系統時加上了這個Common Model的想法,向下對應數種不同型態檔案讀回的內容,實際把檔案讀入後變為Common Model,實作時都是操作Common Model,使用者根據檔案類型呼叫對應的Parser後都是Common Model。Common Model再往上可分支實作出不同類型的Data Model給不同的系統使用,但是底層的處理都是通用的。利用這個概念應用在Model與Controller上,我讓兩個有點類似的系統共用了相同的處理核心,使得Use Case的再使用率達到70%以上。(註)

開發第二個系統的快速(花費人力大約佔第一個系統的五分之一)讓我對於軟體工廠的思維有相當大的信心。往後的專案只要先定義好公司使用的基本資料類別,我相信軟體元件化的理想是可以落實的。

註:開發的兩個系統是可以同時使用的,使用者可以自由切換到任一個系統執行任一個功能而不需重新啟動程式。

2007年8月20日 星期一

D09 Component的設計(4)──Model

與系統一樣Component應該有自己的Model。理由很簡單,如果Component操作時使用的Model屬於別的元件,那麼Component間會有使用關聯;如果Component使用的Model是簡單型的集合物件,又會因為加上基本存取動作而使得程式碼變得較難讀懂。

Component的定義與Class一樣需要高聚合與低耦合的封裝,為了減低與其他Component的關聯,定義出屬於自己的Model是勢在必行的。在設計時根據Component Controller的使用決定Model提供的方法並定義在ComponentModelInterface裡,即使是其他地方使用的Model只要實作這個Component指定的ComponentModelInterface就允許傳進來使用。

Component同時應該準備好ComponentModelInterface的基本實作給無法提供Model實作的程式使用,有些設計者會將許多Component集中使用同一種實作的Model而沒有定義ComponentModelInterface,這樣使得Component與Model緊密結合而無法改變使用其他實作的Model。

傳入Component的Model大致上有三類作法:第一種是直接讓系統使用的Data Model實作Component定義的Model Interface;第二種是使用Wrapper Design Pattern在生成ComponentModel的實作時把系統Data Mdeol包裝在裡頭;第三種則是設計一個轉換類別,在使用Component之前把Data Model的內容搬到ComponentModel並在使用後搬回來。

2007年8月19日 星期日

D08 Component的設計(3)──Controller & Action之二

Component的設計與系統一樣,方法進入後會由負責執行流程的Controller來操作實際的動作來完成功能,而Controller流程裡分解動作的實作都放置在Action。控制不變而動作有變時就更換不同的Action,控制改變而動作不變時就更換不同的Controller,兩者都改變時就把Controller與Action都更換掉。

經由這樣的設計,我們可以達成在Component不變的情況下,只要輸入不同的參數就能夠改變Component內部的運作。另一個好處是,當Controller與Action有新增的狀況時,改變都封裝在Component裡而不影響系統;如果在系統上使用Factory來切換元件的話,改變就必須由系統層面來處理。

元件的Controller裡如果使用了其他的元件而且會拋出其他元件的Exception的話,務必記得要在這裡捕捉下來並轉成自己的Exception丟出去。我們應該讓下層的動作只傳到自己這一層,上一層只會收到這層丟出的東西;否則下面所有的物件全都往上丟,就會造成上一層與下一層、下下一層、下下下一層……的所有元件都產生使用的關聯,這是設計時絕對要避免的。

2007年8月18日 星期六

D07 Component的設計(2)──Controller & Action之一

先用我經手過的印表模組的例子說明Controller與Action的差別。

在Interface上定義了print(String data),只要把列印字串傳進Component就會在印表機印出資料。但是印表機的實際列印最多會是由進紙、列印、退紙三個動作組合的,根據印表機的不同又有列印前是否自動進紙與列印後是否自動退紙的些微差異。

根據上面的描述,應該把印表機的動作(進紙、列印、退紙)定義為三個ComponentActionInterface的方法並加以實作,再於上一層ComponentController做一個對應印表機的實作,在print(String data)依印表機特性決定如何呼叫ComponentActionInterface。最後在Implementation裡做好變換的機制,依實際連接的印表機生成對應的Controller與Action就能夠正常使用。

千萬不要把Controller寫成與印表機一對一的對應,印表機可是有數百種機型的。以列印動作來講,列印的動作是絕對要執行到的,其間的差異就只有是否自動進紙與是否自動退紙,總計四種組合。只要設定印表機機型對應的控制方式,不就可以省卻非常多的控制程式碼了嗎?

2007年8月17日 星期五

D06 Component的設計(1)──Implementation


這張圖是我所認定一個元件應具有的完整層次。Component內部的設計同樣應用MVC的想法來決定它的層次。由於Component不需要View,而且處理的Model較為單純,所以切割的層次同樣地比較簡單。

一個完整的元件就等同於一個JDK的Package,應該要有屬於自己的定義Interface、實作Class、處理的Model與拋出的Exception,經由封裝這些種類的物件在Component裡,我們讓Component成為完全與其他Component絕對無關的獨立物件,在使用與抽換上可以不需去擔心它的關連而直接使用。

Interface只是定義而沒有任何程式,因此在進入Component的一開始,我們就需要一個Class的存在來實作Interface裡的定義,Implementation的工作便是如此。我把Implementation定義為Component內部Factory Pattern的實作,經由傳入的參數自動決定接下來應該由哪些程式來實作,如此可以減少系統的設計負擔。如果這個Component的使用必定要經過Factory的方式,我們可以把每次都需要的部分封裝起來。

Implementation的作用只是Component的入口,我們設計時可以簡單讓Component Controller去實作Component Interface,那麼Implementation的責任就可以簡單到只是以Factory的方式叫起適當的實作,然後把每個介面的方法對應到Controller的方法而已。

2007年8月16日 星期四

D05 做事的方法(7)──組織的窗口

各位應該都到戶政機關去申請資料過,現在的政府機關都設置了服務櫃台,民眾只要攜帶必要的資料到櫃台就有服務人員處理好,並提供民眾想拿到的資料。

在這個故事裡,民眾的角色是Controller,政府機關是Component,服務櫃台則是Component Interface。政府機關對民眾來說是完全封閉的黑盒子,民眾想要存取政府機關的任何資料只能透過服務櫃台。櫃台那裡定義好政府機關所有提供的服務事項(Interface methods),同時也定義了申請每一個服務事項時民眾應事先準備好的資料(傳入的物件)以及辦理後可以得到的文件或戶籍狀態的改變(執行的結果)。

政府機關將自己所有的功能封裝起來不讓任何民眾存取,只允許經由窗口來操作,這也是Component最重要的精神。應沒有人看過民眾去政府機關辦事時,服務人員會對民眾說:“你把這個資料放在那張桌子上,再走到盡頭的櫃子打開第三個抽屜就有你的資料”這樣的事吧?Component的內容不應給外部程式直接存取,否則會造成管理上的重大問題。

讓一個組織與外界所有組織完全隔離,只留下規定的窗口進行事先定義好的工作,這是讓工作易於管理的根本。

2007年8月15日 星期三

D04 Component的需求規格與設計

架構設計完成後,可以得到所有新增元件的清單與每個元件應有的屬性與方法,這些便是對Component的功能需求。

我們把視野縮小到Component,可以感覺它就像一個縮小的系統那樣有限定的範圍,Interface的設定就等同於對於Component的功能需求。有人認為只要定義好Interface,Component要怎麼設計就不需要管,只要符合規格並通過測試就好;這個想法在系統層級時已經討論過,在這裡基於“每一個程式的存在都有它的意義“的想法,Component與系統一樣都是需要設計的。

將Component視為積木並沒有什麼不對,在有不同需要的時候整個置換成另一個。但是我們已經知道在前面提到的印表機案例裡,整個抽換的方式會造成的幾個問題:傳入的Model只符合特定實作、Controller與動作無法分割而有過多重覆的程式碼、支援新的實作時只能copy-paste再修改。

Component的reuse是提升系統開發效率其中的一項指標,但我不認為reuse是整個Component拔掉再換一個,而是使用同一個Component並使用不同的傳入來改變內部的行為。就像一塊具有螺旋漿的積木,使用者可以用手撥動令它旋轉,而不是轉到不同方向時就拿另一塊積木來取代。設計時儘量將變動封裝在Component內部,因應系統需求而需要更換不同Component的設計則在系統上實現。

選用適當的元件用適當的方式快速堆疊出整個系統是我們對軟體工廠的期待;介面的存在提供了如同積木般連接的規格,但是Component並不是只是用積木的方式接起來就可以,在介面上傳遞的資料才是它的精神。

2007年8月14日 星期二

D03 你會是什麼樣的神?

曾有人比喻說,設計系統的感覺有如創造世界的神一般,可以全憑個人的想法決定這個世界的模樣。現在想像你真的是一個萬能的神,第一個任務是任意佈置四尺魚缸,最低需求是要能有20條熱帶魚活在裡面。手邊有許多種類的水草、裝飾與魚類可以任意取用,這時你會怎麼決定這個水中世界呢?

“系統是滿足客戶需求的最小設計。“如果你的心裡響起了這句話,那麼這個魚缸裡就只會有20條顏色亂配的魚與產生足夠氧氣的雜亂水草被丟在裡面。用心的人會去規劃要有哪些種類、哪些顏色的魚,也會選用適當的細砂與裝飾,另外還會佈置水草的層次,說不定還會另外放入一些貝殼小蝦之類原先不在需求裡的項目。一切多出的投入都只為了讓這個魚缸的外觀與運作更加美好。

創造的世界要更好必然會有更多的工作項目必須去完成,每一個工作項目無可避免地需要時間與資源投入。特別做出的項目未必其他人察覺得到,但是那些項目卻經過計算而使得那個世界變得更不易出問題且好用。不管是神還是人,應該都會為自己即將做的一切想好未來所有的可能吧?

同樣的想法也對應到系統的設計上:你會把程式井井有條地規畫好並使之正常運作,甚至提供更多樣化的選項功能,還是把全部程式塞進一個大黑箱裡只負責功能正常就好?這正是你的抉擇!

2007年8月13日 星期一

D02 垂直設計有兩層──需求邏輯與動作邏輯

需求的收集是針對使用者對於系統的要求,這些都是使用者在商業領域裡的要求;作架構設計的目的是根據使用者的功能需求,去分析並設計出符合功能需求的操作流程與結果的對應程式。當然,架構設計時可以決定所有的程式都在這個層次實作,但是這樣一來所有的程式碼都與這次系統的需求綁在一起,未來需要拆離時又會是一件浩大的工程。

抽取共用的部分是設計者都有的習慣,從最底層開始是把共用程式碼抽出成共用API,同時將一些特定意義的動作封裝成元件,元件再經由系統的設計使用在Use Case裡,最後所有的Use Case集合成為系統。在系統每一小格裡的程式,在使用的層面上同樣有很多層次需要設計。

對一個公司而言,元件不是針對特定系統設計而應該是通用型態的,平時應受到管理並且可以很快地查詢到所有元件的定義。在設計系統的時候根據功能需求的內容挑選適用的元件,如果應該屬於元件的動作而找不到的話,就應該依照通用原則定義元件的Interface並在這個階段作細部設計。

無論是元件還是API,如果只是對應到系統的商業領域而沒有辦法做成其他系統通用的時候,他們應該存在於通用元件庫之上的系統元件庫,未來設計要應用在這個商業領域的系統都可以優先在這裡尋找看有沒有更適用的元件。

這個概念的垂直架構大略如圖所示,最上面的專案是之前架構設計階段的產出,使用時上層可使用下方任一層裡的元件或API,下層則不得使用上面層次的物件以免造成迴圈式的關聯。

2007年8月12日 星期日

D01 功能為經,層次是緯──定位系統的所有程式

對於系統而言,功能需求是一定要完成的事項。若把系統視為一個長方形的圖形,我們可以想像所有Use Case列示在左邊,再將長方形依Use Case的數量水平等分,每一個小的長方形就約略等於完成Use Case的程式。如果只把完成Use Case視為工作目標,那麼每一個小長方形都是密閉的黑箱,裡頭設計的品質無從得知,因而亳無取代性可言。
在架構設計時,我們規定了MVC等等的層次,要求每個Use Case都必須經由特定的層次做特定的事來完成功能。把層次依序放到系統長方形的上方,並將長方形垂直等分,我們可以看到系統就像豆腐一樣被切得井井有條。
再來在幾個重要的層次之間,甚至設計時認為同一層次中應該再切分數個合作的層次的地方,加上Façade Pattern,就會成為下面的圖。集合為Façade的好處很類似定義Interface,使得進入下一個層次時必定得經過這道關卡,如此一來無論在硬體上或模組上都使得切割變得容易許多。
最後,我們設計的所有程式都會落在系統長方形的任何一個小格裡,而且也應該在小格裡。到此,所有的程式與它內部使用的其他程式都有自己放置的地方與應盡的責任,加上每個小格裡對外的地方都設計了Interface,被封裝後的小格同樣可以快速地抽換規格相同但功能有差異的其他程式。

2007年8月11日 星期六

C30 架構設計的流程(Model)

在需求階段裡,藉由訪談客戶獲取系統需求加以分析並記錄。記錄下來的物件關係模型如下面附圖。圖中物件間的關係如下:與A19的基本概念模型比較,會發現他們的本質上是相同的。

◎一個Use Case Flow由一個或一個以上的Service組合而成
◎每個Service有一個或一個以上的Properties與Input
◎每個Service有可能傳回Return Code(可能沒有)
◎每個Component都應該定義無法處理的Exception狀況(可能沒有)
◎每個例外狀況都要有一個或一個以上以上可能的其他Service
◎一個其他Service由一個或一個以上的Component組合而成註:這裡的Component也有可能是一段程式碼。


每個Use Case Realization在設計時都有Class Diagram與Sequence Diagram,設計的同時每個Class都要定義屬性與方法,同時附上註解。Class會存在於Package,所以每個Package也會有一張Class Diagram來描述有哪些Class。

追溯關係則至少有三種:Use Case與系統Class、系統Class彼此間、系統Class與元件Class的使用關係。此外,訊息與參數的內容應有清單與影響的關係追溯。

2007年8月10日 星期五

C29 理論串接實作的瓶頸(7)──Use Case vs. Class & Class vs. Class的追溯

很多人在一聽到要做這兩層的追溯關係時頭都很痛,因為一個系統動不動就幾百個甚至上千個類別,Use Case也差不多都近百個起跳。光是Use Case對Class就已經是一百對一千的表格了,更何況Class對Class的一千對一千大型表格呢?而且,所有的關連都建立在程式碼裡頭。

如果有這樣的想法,那很肯定這些人是沒有設計層次概念的。雖然實際使用的類別有一千個,但是經由層次的定義,我們可以劃分哪些屬於系統架構設計層次的類別,哪些屬於更下層元件層次的類別。如此一來,Use Case要找的是與架構設計類別的垂直關係(A),架構設計再找出與元件類別的垂直關係(B),另外再加上架構設計類別裡彼此的水平使用關係(C)而已。

A與B的關聯在建立Use Case Realization與繪製裡面的圖表時已經將之設定在rose model裡,經由SoDA可以快速找到。雖然C的關聯也同時被放在圖表裡,但是我們可以經由Package名稱的篩選區隔出系統與元件的區別。系統設計的類別會放在與系統相關的Package裡,元件則基於全公司共用的規則放在不同等級的Package之下,由這個差異我們可以定出類別所屬的不同層級。

應用層次的落差使用較多層次的對應關係來達到像粽子一樣,提起Use Case後可以往下找出所有有關的類別是這裡的關鍵;這樣一來就可以免除開頭所說的那種勾選超大型表格的惡夢。

2007年8月9日 星期四

C28 追溯關係(2)──Use Case vs. Class & Class vs. Class

每個Use Case的功能都是由一群Class合作完成的,這表示著Use Case與Class之間存在著使用的關聯;也就是說在這裡同樣得做好使用關係的追溯。反過來看時,Class參與了哪些Use Case的完成也是必須追溯的關聯。

在架構設計的時候,產生的物件都屬於前面架構各層級的設計,對應的是需求分析的結果,除了使用元件的Interface之外,其他的Class都是與系統直接相關的物件。對於分層設計的概念來說,元件與Service是屬於不同層級的物件,所以不建議追溯時混在一起。

既然設計分了層次,追溯就無可避免地也分對應的層次。在Use Case對Class這層的關係就對應到與系統需求有關的Class;在更為精細的追溯時,追溯的對象會細分到Class裡的變數與方法,這樣可以在修正與變更時能夠更詳細地定位哪些地方需要重新測試。

在SoDA的應用上,我們可以取得所有的Use Case Realization,進而拿到裡頭所有的Class Diagram與Sequence Diagram,再依序條列出圖裡所用到的全部Class,很容易地就可以取得每一個Use Case有關聯的所有Class。

再下一層是系統Class對應使用元件Class的追溯關係。在這裡我們必須要能知道每個系統Class使用了哪些元件Class,同樣地也應該知道元件被應用在哪些系統程式裡。表面上雖然是平等的Class vs. Class,但是在設計層次上其實是上層系統程式與下層使用元件的關聯。

2007年8月8日 星期三

C27 設計元件的放置架構記錄

即使是在同一個Use Case裡,在不同的Tier裡都至少會經由一個以上的Service來控制當時系統應有的動作。在我的設計裡,Component是只能被Service所呼叫的元件,所以在設計Service物件的同時要記錄在哪一個Tier各使用了哪些Component。

記錄的意義同樣在分析關聯,我們可以看出Component被哪些Use Case的哪一段Service所使用。從Tier的觀點來看,每一個Tier都知道總共使用了哪些Component,同時我們知道每個Component各屬於哪一個library,收集起來並加上系統本身在那個Tier應有的程式,得到的就是在那部電腦上需要額外安裝的檔案。

在這個時候,應該產出一張表格註明每一部硬體的規格,使用的作業系統,安裝的應用程式,使用的Component Library與系統預計產出的程式。未來安裝各部分的硬體時,就依照這個表格來製作安裝手冊並安裝之;日後有人問任何硬體所需要的規格與軟體時就可以立即提供。

不過還是要記得,有任何的變更時,這份文件也是必須要同步更新才能隨時得到最正確的內容。




◎系統的目標

2007年8月7日 星期二

C26 Activity Diagram設計產出(2)──Class Diagram

每個Use Case Realization裡也該有Class Diagram,在Sequence Diagram裡為Interface與Class拉上關聯的同時,也該把那個Interface與Class放進這張圖裡。從Class Diagram裡我們可以很快地得知每個Use Case使用的所有Interface、Class與彼此間的關聯。

如果可能的話,努力將每一個Tier(每個Tier對應到一部電腦)裡所用到的類別都作成一張Class Diagram以顯示其架構,同時把Interface、Class之間的繼承與使用關係都放置在圖表裡。

此時的Class Diagram是一部電腦裡必須要有的所有元件介面,在所有Interface都已經定義、實作Class設計好大概並決定好所屬的Package後(方法還可以增減或變更,元件名稱與位置若有增減或改變就得相對地修正),接下來還要考慮哪些意義相似的Package要被包裹成一包library。切分library的基本想法留待C27再作說明。

以靜態的角度來看,最小的單位是Interface與實作的Class,這個物件可在Class裡描述其繼承關係、說明與所屬的所有方法,如果是Service層級者則拉出一張圖來表示它所有的關聯;Interface與Class在建立時就放置在其所屬的Package裡。再往上的單位是Package,由於Rose Model裡可以取得Package裡所有的物件,所以可由SoDA產生Package裡的物件清單而不見要有圖;設計時如果把Package的範圍等同於Component的範圍就可以省卻一張圖。

要有哪些Class Diagram除了決定什麼情況下該有之外,同時也根據什麼地方需要特別說明就多加一張圖註明。

[這裡應該有一張Class Diagram]

2007年8月6日 星期一

C25 Activity Diagram設計產出(1)──Sequence Diagram

在Use Case Realization裡首先要做的是根據Activity Diagram從功能的開始,逐步敲定每個步驟如何在系統裡實作:判斷是否可以進入功能的處理、處理時一層層地決定現在到達的層次、在該層次裡由哪些元件做事、經由哪些元件的方法、應該傳遞些什麼等等。這些Use Case Realization的想法記錄在UML裡,是靜態的Class Diagram與動態的Sequence Diagram。

對於每個Use Case Realization首先要建立屬於它的Sequence Diagram,根據Activity Diagram裡的Activity,逐步在這兩張圖表加上應有的元素。設計一個動作時都有同樣的三件事情要做:決定由哪個元件來做(Component Name)、決定由元件的哪個方法做(Component Method)、決定做的同時要傳入什麼與傳回什麼(Method Parameters & Return Objects)。

如果找得到已經存在的元件與方法可以使用,只要將元件放進Sequence Diagram並拉上呼叫的方法關聯。如果沒有可使用的則必須依照所需的呼叫方法,看是要新增一個功能元件並加上對應方法,或是挑選一個已存在的功能元件只加上對應方法,新增之後再把元件放入並拉上關聯。

以上的設計依據之前分層的方式都定義在元件所屬的Interface上,同時為了方便日後的追溯最好在元件與方法的註解都註明是哪個Use Case所使用的。使用以上的原則,為Use Case裡所有的劇本建立起Sequence Diagram;依此類推直到所有Use Case Realization都處理完成。

[應該有一張 Sequence Diagram]

2007年8月5日 星期日

C24 理論串接實作的瓶頸(6)──Properties, Message, Log

在設計系統的時候,有幾個項目是貫穿全部功能都需要的,像是訊息、記錄以及參數等等。這些項目如果在設計的時候沒有周全的考慮,同樣會有使用時機與層次不一致的影響。

Message:在功能處理的流程裡,處理的經過與結果在必要的時機都需要回饋讓使用者知道。需要設計的有顯示訊息的機制與訊息對照表。系統可以設計一個自己專用的訊息機制,在設計的時最好以提供給其他系統使用為基準,作出通用的元件。

Log:使用時機與設計方式都與訊息雷同,是在處理經過中記錄下系統的必要內容。通常出現的時機與訊息差不多;訊息是該時間點讓使用者知道系統處在什麼狀況下與可以作什麼動作,記錄則是讓使用者能夠查詢在那個狀況下,系統內部的狀態如何,必要時可以追蹤問題的發生原因。

Properties:參數的設定應由系統統一管理,在初始化或是即將進入該段處理的時候使用參數決定處理的方式。常看到的設計是元件裡自行讀取自己用的參數檔案,以元件的立場來看封裝參數是可以理解的;但從系統的角度上來看過多的設定會讓系統的設定失去一致性。所以我會將參數的管理放在系統層級,控制元件的參數再使用Properties物件傳入。

開發系統的極致,是讓未來的開發或維護,在沒有改變設計邏輯情況下只要更改外部檔案的定義就能產生執行參數的效果,不需要再更動任何程式。通用設定如果存在於一致的層級,編輯與維護都會比較容易;更理想的是在提供的參數夠多讓系統更趨於成熟時,就可以提供外部參數檔的編輯器,甚至設計SA Tool之類的產品,在訪談時所作的記錄可以自動產生基本的框架與設計以節省大量重覆開發的時間。

2007年8月4日 星期六

C23 做事的方法(6)──察言觀色、見機行事

小朋友們與爸爸開心地互相逗弄著,家裡充滿了歡樂的笑聲;忽然間電話響了,老板打來跟爸爸說明天到公司辦資遣手續。掛掉電話後,小朋友繼續想跟爸爸玩,結果被爸爸大罵一頓,結束了快樂的時光。

小朋友們做的動作是前後一致的,為什麼會得到不同的反應?很明顯地是因為有一通電話對爸爸傳遞指令造成了改變,因而使他的心情變化進而對同樣的事件有不同的對待。小朋友像使用者,進行同樣的操作動作;電話就像是參數的改變,讓爸爸像系統的處理般與之前有所差異。

在生活裡有兩個方向可以考慮:當別人的反應模式改變時自己該如何面對,與如何利用方法來改變一個人的行為。古人訓示裡的察言觀色與見機行事是教我們如何觀察他人的改變並因應行動;某個功能本來可以印表的,但剛剛有人暫時把印表機拔走,如果此時使用者還是硬逼成系統要列印,那麼這個使用者通常會被歸類為“白目”型。

當希望讓某人做出自己期望中的改變時,我們會設計讓那人照我們的想法去做,古代流傳的說客故事呈現的是這個事實。若期望在某個功能裡可以依照特別的想法去改變處理流程時,我們可以安排參數的存在來造成這樣的影響。

見機行事,機可以是既成事實的反應動作,也可以是我們期望要發生的反應。無論如何,只要有變化存在就必須投入相對的努力與資源總是不爭的事實。

2007年8月3日 星期五

C22 Model的設計(1)──Model的存取


這張圖是我所認定整個系統具有的完整層次。Model的部分是以淡紫色所顯示的部分。

為了支援各種不同的Model實作,首先要定義的是Model的存取介面,這個介面定義的是Model自身內部資料的存取方式與邏輯;再上一層會有Model Service,裡面定義了跨Model存取邏輯的封裝。

如果有個戶政機關想統計轄區裡所有人的平均年齡,那麼會在代表人的Model裡定義取得年齡的方法,交由上一層的Controller計算平均年齡;如果戶政機關想統計的是所有家庭夫妻差距的歲數,這時就得在Service定義一個方法,分開從Model各取得的夫與妻的年齡再傳回差距的歲數。

縱觀View Service、Controller Service與Model Service,其實我們可以發現功能分析時確定要做的每一步驟都會在這幾個層次裡設計,在設計的同時必須確定每一個動作要如何實作,是否要使用元件。在我的想法裡,Service裡應放著系統的處理邏輯,每個動作都該放進元件裡實作(很簡單的動作則另外拉個API),如此可以明確分隔出二者的意義;此時每個使用到的元件介面就必須在架構設計階段內同時設計與定義。

在這個設計步驟完成時,整個系統的功能設計就告一段落;每一個功能、每一個步驟、每一個動作的處理邏輯都應該包含在內。至於如何達成處理動作,就交由元件來完成。

2007年8月2日 星期四

C21 做人的方法(5)──人正是Controller

環顧辦公室的四周,你會發現能夠依著自己意志取用物品的只有人類而已(帶寵物上班的地方除外);辦公室的環境能否維持整齊清潔,其實也就跟人類息息相關。

對應到系統來看,辦公室裡的物品可以視為資料,而人類就是可以隨意控制物品取放與移動的Controller,人能夠隨意地改變物品的所在位置。如果沒有規範,物品會在被使用後被丟到一個可能永遠找不到的地方;再過段時間後,等到不在位置上的物品且不該在位置上的垃圾一多,辦公室就顯得髒亂。

具有“從哪裡拿到的就放回那裡,不該放在那裡的物品不會放”這種美德的人實在不多,很多的人只為了貪圖自己的方便,以最快的方式取得要用的物品,使用後就隨手一放就走了,接下來的人就深受其害。常有人嘆息管理不易,但用另一個方向思考,只要每個人都做好自己應做的事情,放好該放的東西,整個團隊不就已經完全正常運作了嗎?

我們要記得自己是唯一控制經手物品的Controller,必須在對的時間用正確的動作取得該用的資源,並在使用之後立即將之回歸到原來的地方或該去的地方。如果沒把自己經手的物品回歸到它應該放置的地方,不是另一個人要付出代價讓它歸位就是環境要承受它不在位子上帶來的問題。遇到事情時思考施與受雙方面,牢記不要為他人或大環境帶來麻煩,常懷有這樣的想法後就會自然而然地把經手的所有事物作最佳的安置。

請記得對人而言,經手的資源除了物品外,還有自己應記得的一切與該去做的的一切;也就是說要妥善安排與自己相關的所有人、事、時、地、物。

2007年8月1日 星期三

C20 理論串接實作的瓶頸(5)──Controller≠Component

Controller是系統的靈魂,意即是最重要的部分,但是在許多介紹OOAD技術的網頁裡,Controller就直接被定義成一個Component。這樣的作法,讓我頭痛地摸索很長的一陣子都找不到出口。直到參考了幾篇提到Business Logic與Function Logic應分開設計的網頁後,才終於明白應該要有的理想層次。

先以一個例子來說,我們的系統需要在幾種不同的印表機上列印結果,我們的系統有一套產生列印內容的處理,另外要有驅動印表機的機制。在最初的需求裡只支援一款印表機,所以當時的設計定義一個Interface,再把列印內容的處理與驅動印表機機制寫成直接使用類別(相當於視作一個Component)。

後來因其他專案的需求,必須支援其他兩款印表機。最快的作法就是把原來的類別再複製一組,再修改差異的地方,但是這樣雖然快卻會在系統裡出現數組幾乎相同的程式碼,除了造成維護的負擔也顯示出設計上的問題──幾乎沒有設計而只有實作。

後來還是決議把處理內容的邏輯與印表機控制拆開,在中間插入一個控制印表機用的Interface;這樣就成為三組印表機功能實作對應三款印表機,但是內容處理的部分就只要一組。處理內容屬於Business Logic必須在架構設計時處理,印表機功能屬於Function Logic(因為每種印表機的控制都有差異)是Component細部設計的範圍。這樣的作法才完全符合我的架構設計理念。

省卻設計的層次雖然能夠節省初次開發的時間,但也僅只於此一利,之後的任何結構上的需求變更都將會凸顯其害。

2007年7月31日 星期二

C19 Controller的設計(1)──Business Logic


這張圖是我所認定整個系統具有的完整層次。Controller的部分是以淡藍色所顯示的部分。

在Controller裡所要設計的是輸入結束後的系統處理,通稱為Business Logic的部分。第一個進入的Controller是對應Use Case的Activity Diagram的控制物件,本身只負責流程該換到哪個Activity執行而不要有任何其他的實作摻雜在裡頭。

第二層的Activity Controller裡是Activity應有的邏輯控制,在設計的時候同樣先用文字簡單表達處理的步驟,每一個小步驟都對應到一個Controller Service裡的方法。同樣地這裡純粹是控制,不要有其他的處理。有些系統的開發會把這兩層的控制包裝成Operation Flow與Operation Step的交易流程套件來減少額外的開發。

實作的功能都放在Controller Service這一層裡。比對View Service來看,我們可以發現Service才是系統真正放置功能的地方,唯有在這裡才允許存取或操作到不同層級的物件。在Service裡都是處理動作的實作,每個動作的設計都必須決定是要使用現有的Component、自己設計新的適用Component或是自己寫程式在方法裡處理。(這裡的Component必須與Business Logic無關)在確認Service動作的同時也必須要設計或定義使用Component的介面。

驅動程式各部件運作的關鍵層次,就是Service;經由這裡存取Model與View,並達成與外部的連接,一切功能需求裡提到的系統互動動作,大多會在這個層次實現。

2007年7月30日 星期一

C18 View的設計(3)──進入Controller

這層設計的一開始,要先決定View進入Controller的對應事件與傳遞的物件。在一開始的基礎關係,每個功能會有自己的View,自己的Controller,如果不去抽取相同的部分就會得到前面提到的大學生程式類型,造成每個功能都有一大堆重覆的程式碼。我們可以在View與Controller之間使用Façade Design Pattern作為唯一入口,在執行事件的Service裡把View的欄位與值在listener包裝成適當Model傳入Controller。

以Façade類別作為分界點,無論使用哪一種GUI元件開發顯示畫面,只要在進入執行時傳入指定的Model,後面的Controller都可以正常地處理;反過來看,只要Controller都符合傳入Model的介面,把畫面的Listener組成該Controller所需的Model就可以呼叫它。這便是用來應付可能發生的變化的設計應對。

許多經驗較少的人把交易的邏輯寫在顯示的類別裡,在處理資料的時候直接對顯示物件操作。由於指定物件本身的類別名稱,就會造成處理與顯示之間的密合而無法隨意更換,這樣的設計就像是一體成型的機器人手臂;因此我們需要在意義不同的層次上加工製作隔開兩邊的介面物件,藉此來達到可以抽換另一端實際執行物件的活動機制。

2007年7月29日 星期日

C17 做人的方法(4)──人會是Listener

在忙碌的工作裡,我們需要安排自己的工作項目與時間分配。手邊有哪些工作,每個工作的開始日與完成日都要牢記;還要記得所有會議的時間與地點,會議中進行哪些討論,要先準備哪些物品。在工作安排初期與會議敲定的瞬間,心裡都知道有那些事的存在,但是時間點到的時候能保證全部都記得嗎?

任何事都可能會忘記,一旦忘記沒做好接下來可能有重大的影響,許多人會用筆記本記錄未來要處理的所有項目,每天不斷地檢查清單的內容看有哪些項目是現在要做的。這像是使用polling的呼叫模式,每隔一段時間就必須記得去檢查,有狀態的改變時就去處理;但也會有一些時候檢查了也沒有要做的事浪費檢查的時間,或是在該去檢查的時間點沒去檢查而錯過時機。

我喜歡用PDA記錄工作項目與行事曆。該在什麼時間準備什麼樣的資料與做什麼事,全都記錄在行事曆中並設定鬧鐘提醒,時間點一到PDA就會自動提醒該做事項的內容。平常要記的事情已經夠多了,再加上這類瑣碎的事情實在會煩死人;使用行事曆後就能放空這些小事來專心工作,等到PDA提醒該做事的時候,再參照工作項目的內容去處理要做的事實在是輕鬆多了。

面對雜事的時候可以只是單純的Listener等候通知,但是處理工作事務的時候就不能被動成這個樣子。老板們總希望每位員工都是運作良好的Controller,能夠把手邊所有的工作項目理想地安排並完全地掌握所有事項的進度與狀況;這也是每個人都應該做到的基本能力。

2007年7月28日 星期六

C16 View的設計(2)──事件與處理

畫面在顯示之後就等待使用者的輸入與操作。所有程式語言工具對於畫面操作事件都有詳細的規畫與定義,所以大家對Listener介面的使用都很熟悉。這裡想要討論的是Listener的實作與事件對應處理邏輯的關係。

由於操作事件會跟隨著畫面作反應,所以把Listener的實作放在畫面呈現的類別裡是直覺的設計想法。我們發現有時同樣的事件與處理大量地重覆出現在很多畫面(例如系統規定Enter鍵是執行功能的熱鍵),在這樣的情形下相信所有的人會把Listener拉出到一個專門的Class來處理同樣的事件。

現在在這裡最常遇到的問題,是在拉出Listener後仍然把事件發生的對應處理寫在Listener裡面,造成難以切割的密合狀態。這時只要事件與處理邏輯之間的對應關係有所改變,都必須搬動整段程式碼才能達成;就算把這些處理提出到獨立的方法已經有做抽取動作,但也不算是正解。因為,無論如何都會動到程式碼。

如果認真“回憶”的話,我們在需求階段製作的Activity Diagram有分User與System的Swimlane,從User指向System的線條就是event,進去後系統的連鎖Activity正是系統要對應處理邏輯。我們要注意的是,反應由一堆Activity依序組成,每個Activity對應到一個ViewActivity物件的話,管理執行順序的工作交由一個ViewFlow物件處理,Activity的處理內容再交由View Service處理。

右上角的藍色框就是對應每一個event所應該設計的反應機制,當需要再往後執行功能邏輯時,會再往後面的層次傳遞。


2007年7月27日 星期五

C15 做人的方法(3)──把用過的資源放到該放的地方

宣告記憶體佔用,並在使用後釋放乾淨,這種維持系統運作正常的原則,同樣也是人類在日常生活中對於環境應該遵守的準則。

要記得我們多做了不該做的或少做了應該做的事時,周遭的人事物必定會受到相對的影響。例如拿出來用的物品在使用後要放回原處,這樣所有人才能照SOP規定的步驟在該拿到物品的地方取得,今天我們拿了物品不放回去,必然會造成應該取得物品的動作拿不到,同時不該有那項物品的地方多了這件物品。這便是相對的影響。

現在常看到很多人在路上隨手丟煙蒂、紙屑,丟棄後還能夠輕鬆自在地離去,從環境的角度上來看那個地方就多了一項破壞景觀的物品;隨處丟棄不想養的寵物也是這樣,把自己應該處理的事推託給別人或是大環境裡,個人所減少的負擔就這麼加重在其他人的身上。把物品在正確的時間點放到它該存在的地方,同樣是我教育小孩的信條。

每個元件如同個人一般把自己經手過的物件歸回到處理前的狀況,那個系統就會如同大環境一樣得以長久地良好存在。裡頭有任何一個元件沒有做好,就會有相對的小小影響;現在的硬體等級都很好,也許要經過很久才會出現問題,但因資源取得容易就認為可以隨便取用且亂丟終究是錯誤的行為。

保證每個元件處理好經手的資源,是維持系統穩定的必要條件;同樣地,人對於地球也應該抱持同樣的想法,才能讓生存的環境維持機能。至少,立即隨手把自己取用的物品放回原處,可以讓自己居住的場所保持整齊清潔,帶給身在其中的人較好的心情。

2007年7月26日 星期四

C14 View的設計(1)──起始與結束


這張圖是我所認定整個系統具有的完整層次。View的部分是以淡綠色所顯示的部分。

在架構設計的時期,第一步設計的對象是架構好上方所有的Interface,接著就思考每個部分的實作與對應使用的Component Interface,Component的實作則會在細部設計階段才需要處理。在這個階段,設計者應決定每一層Interface所應存放的位置,同時決定每一個功能在每一個Interface裡所對應的方法,以及方法裡應該作出的反應。

首先,在起動系統或是準備功能執行狀態的時候會有一個起動功能Controller負責產生顯示給使用者操作的畫面。畫面大多會以GUI(Graphic User Interface)呈現,裡頭必須包含功能所需之資料對應欄位(有些資料是經由計算或另外擷取的)。絕大多數的View只會有一種,這時可以省略掉Interface的定義;但是如果系統打算採用Multi-Channel的設計,就必須定義介面提供更換不同種類的View來存取畫面上的資料。

設計任何一個層次的時候,除了起動時自身的生成,同時也要注意功能完成後擁有物件的釋放。重覆不斷地使用資源卻沒有完全釋放的功能,會造成硬體的Memory Leakage而逐漸佔用系統記憶體,終至記憶體不足以宣告生成物件而導致系統停擺。在網路上所搜尋到的教學網頁全部都是教大家如何使用物件,還沒看到過同時教大家如何正確地完全釋放物件。結束時釋放的原則是以生成的方法順序倒過來逐一實行。

2007年7月25日 星期三

C13 小小的改變,大大的費時

公司的系統需要使用其他廠商的硬體來擴充功能,因而要我設計一個元件來對應這些功能。需求是在畫面上操作某些動作時,把資料變成XML字串送給其他系統,回應的結果再顯示到畫面上。

一開始沒想太多,只把送出的XML作成Model物件,Controller裡就直接在需要顯示的地方使用View的物件(公司的View經過包裝,使用自行開發的API),只用了幾天的時間很快地把功能做好並測試完成。過了幾天,主管說這個元件希望可以單獨銷售使用,View的部分希望可以同時支援標準的swing,為了做出這個功能我花了四個小時把Controller裡的View拆出並定義Interface。

直接把Controller與View分開並定義介面所花的時間應該只比原先直接使用的設計多一個小時,但是我花了整整四個小時做這個動作。而且在拆解的過程裡程式碼大量地被分割,連帶地所有功能全部要再重測與修正,總共用了六個小時左右,實在是得不償失。

類似的經驗在維護列印的元件上。當初寫這個功能的人看需求只要使用一款印表機,所以在列印的邏輯裡直接使用印表機物件;現在因專案的緣故必須支援三款不同的印表機,為了重覆使用印表邏輯,我必須花很大的功夫拆出邏輯與印表機功能物件並在其中定義介面。當然拆解後重新測試元件全部功能的動作還是免不了的。

當你多遇上幾次這種重拆重測的事,浪費許多時間重做之後,如果你是那個負責拆解與維護的人,相信你會更明白為每一個層次定義介面的重要性。

2007年7月24日 星期二

C12 應付變化的設計──Design Patterns

系統設計時常有需要變化的地方,累積了多次的開發經驗後,有人統計分析出一些較常見且需要的變化,並且設計出解決對應問題的機制,這些機制被稱為Design Patterns。

Design Patterns是很熱門的話題,搜尋一下網路可以找到許多相關的網頁。Design Patterns集結了前人的智慧解決系統設計上常見的需求,讓系統更具有抽換元件的彈性,但是增加彈性的代價是使用更多的Class來達成功能。因此也有Anti-Pattern的聲浪出現,希望設計時不要動不動就套用Design Patterns而造成設計上的負擔。

在應該變化的地方才使用Design Patterns是目前較中肯的使用時機,分析使用者所有需求的內容來決定哪些地方該使用Design Patterns。這樣的想法是合理的,如同記錄的內容需要平衡,設計的內容也同樣需要平衡;但是需求都可能變更,沒有人知道現在確定不變的事在未來會不會永遠不變。

我所採用的設計原則是:確定會變的地方套用Design Pattern,有可能會變但現在不變的地方定義Interface而不使用Class。設計時定義Interface再實作所花的時間大約是直接使用Class的三倍,但是把已經寫好的直接使用Class原件拆出Interface與實作差不多要花直接使用Class的九倍時間,而且還要另外再加上重新測試的時間。

就像記錄應該記下所有的事,但是內容應該簡單不要花太多時間,在設計的時候也應該為所有應該切割的地方定義介面預留未來的改變,但是不需要全部都套用Design Pattern。

2007年7月23日 星期一

C11 小朋友的親屬稱謂表

低年級的小朋友前陣子在苦背親屬稱謂表,一大堆叔、伯、姑、嫂、公、婆之類的名稱一下就讓小朋友頭昏腦脹,更何況還得去記得“爸爸的爸爸是爺爺“,”媽媽的姐姐是阿姨“等等這樣的人物關係。

教了幾種關係後,發現小朋友已經無法再多記,同時也感覺這樣的記法實在太耗費腦力;轉念一想,這個問題好像可以用OO的理念來解決耶。“人”肯定是獨立的個體,“稱謂”不正是從自己看出去後人與人之間關係總計後的結果嗎?只要定義好“人”這個類別,同時適當地決定與其他“人的關聯,那麼從關係求得”稱謂“是順理成章的。

“人”都有一位父親與一位母親(在這裡不討論例外情形)的長輩關係,同時有兄、弟、姐、妹四種同輩集合、子與女兩種晚輩集合、以及一位配偶關係。接下來從自己開始擺好課本所教的層數再拉上與人的對應關係,往長輩一層叫“父母層”、兩層是“公婆層”;父親的兄弟姐妹層的展開是伯、叔與姑姑,他們的配偶稱為伯母、叔母與姑丈;母親的兄弟姐妹層展開是舅與姨,他們的配偶稱為舅母與姨丈,依此類推……。

原先只能硬背的親屬關係在適當地切割獨立元件後,成為有條理可依循的關係圖表,相形之下學習者的可接受度與理解能力大幅地提升了。明明是相同的東西,經過設計與規畫再加以適量的說明,就可以把整個觀念傳遞給原先不清楚的人,這也是我們應該努力達成的方向。