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,方能進而提升品質。