最近更新: 2009-12-23

軟體開發之建置風險的故事

在很久很久以前…,天之聲「等等,不是才上個月的事嗎?」 嗯,說故事嘛,慣例是要用「很久很久以前」開場的。 有一件軟體開發專案,在開發過程中,發生了許多的問題,導致測試與部署時期一再出錯。 而那些問題,有一大部份都可歸類為「建置風險」。

建置風險所帶來的重複性,會嚴重削弱我們的軟體開發生產力。但建置風險總是如野草一般,在不起眼的裂縫處萌芽,除之不盡,燒之不絕。這類的問題點,不在架構設計與程式碼內部,而發生在程式外部的運作環境。經常被忽略,而且副作用會延遲發生,往往到部署階段才出問題,導致驗收延期。

專案概觀

此軟體專案的概觀如下:

  1. 利用 HTTP/web service 架構中央控管的企業內部PC環境。
  2. 中央管理功能切割成數個部份,例如帳號管理(AD)、裝置管理(device)、軟體管理(software)、即時訊息(IM),分別設計對應的 web service 負責。
  3. 管理中心透過中央管理UI向那些 service 發出新增、修改、刪除等維護工作。使用者PC則向那些 service 查詢本機可用的資源狀態。
  4. 除了舊有的 AD server 是 Windows Server 外,其他機器皆運行 Ubuntu 。所有系統都部署在客戶處,位於客戶的防火牆內部。
  5. 開發工具是 J2EE + Spring + Hibernate 框架。

開發人員配置

  1. 程序員5名,以下以甲、乙、丙、丁、戊代稱。各據一張工作桌,負責一項自己的軟體設計工作。
  2. 系統管理人員1名,以下簡稱MIS。負責主機的系統安裝與部署。部署時期於客戶處駐廠。

故事內容

專案開始時,程序員各自取得 Eclipse 套件,有人下載 Eclipse gaileo 版本,有人安裝 Ubuntu 的套件,有人從他原有的 Eclipse 環境中執行更新動作。[風險點]

程序員又從前一個採用 J2EE + Spring framework 的專案 repository 中提出當時的組態文件,例如 build.xml, spring-config.xml, web.xml 等,作為新專案的組態文件草稿。[風險點]

客戶購置了4台新的服務主機。MIS 用 Ubuntu server 版的預設值,安裝在那4台主機。又用公司專有的 Linux desktop (based on Ubuntu desktop) 安裝了1台客戶端模擬PC。

甲在自己負責的服務主機上,用 apt-get 安裝 OpenJRE、Postgresql 、Jetty、乙在自己負責的服務主機上,安裝了 SunJRE、Postgresql,到 Jetty 官網上下載了 Jetty 安裝。丙在自己負責的服務主機上,從公司內部的deb主機下載了 JRE 和 Jetty 安裝。丁也是透過不同途徑取得並安裝了 JRE, Postgresql, Jetty。[風險點]

專案看似平靜的進行一個月後,那4台服務主機打包搬入客戶的機房內了。這是預定事項。因為要配合客戶的要求,再加上規劃時程原先就有些趕。因此原本就計劃著先完成一個大致可用的雛型後安裝在服務主機中。等主機搬到客戶處之後,仍然繼續開發工作,再把後續完成的項目,封裝為 deb 包去更新客戶處的服務主機內的軟體。

另一方面,那4台服務主機的軟體項目,都需要一套認證機制,而這個認證工作所需的 package 由乙負責開發。再透過 Spring framework 的 Interceptor 機制插入。這個設計方式是好的,但是使用的方式並不理想。這個認證 package 並沒有被包成一個 jar ,所以其他程序員是自行從 repository 取出源碼,再複製到自己的軟體項目的工作區內。亦即認證 package 並非以 jar 的形式匯入,而是源碼形式匯入。[風險點]

服務主機搬到客戶的防火牆內了,但是開發工作仍然要繼續進行。於是程序員分別在自己的電腦上,用 Ubuntu 官方光碟在虛擬機上安裝了 ubuntu guest 。也仍然和先前一樣,各自透過不同的透徑安裝了 JRE, Postgresql, Jetty。然後因為MIS到客戶處駐廠了,所以虛擬機內的網路環境,是程序員自己設定的。[風險點]

後續的開發工作結果要打包成 deb ,交給 MIS 去客戶處更新。為了統一管理 deb ,為資深的甲負責進行打包工作。事實上,也只有甲的電腦上有安裝打包 deb 的工具。到了要部署更新 deb 包的前幾天,就看到大家站在甲的工作桌旁,等著打包自己的 deb 。再拿 deb 包回自己的電腦上測試更新動作。[風險點]

當 MIS 拿著打包好的 deb 包回客戶的機房內進行更新動作後,先前潛伏的風險一一爆發。更新程序不動作、服務沒有啟動、組態要修改等等。每個軟體項目的安裝程序都用手動方式,逐一調整更新。然而駐廠的 MIS 只有一位,主機又位在防火牆內部。整個安裝程序的調整動作,都是由 MIS 和程序員透過電話進行的。

在更新時也發現那四個人的工作項目所匯入的認證package的版本並不相同。而且認證 package 有過一次大修改,大改後的組態文件名稱與內容已經變動了。因此那四個人的工作項目,就出現了兩種版本的認證模組的組態文件,更添困擾。

屋漏偏逢連夜雨,軟體開發過程最不想聽到的一句話傳來了,「客戶增加需求」。程序員一邊要修正原有的BUG以及安裝程序,另一邊又要撰寫新需求的功能。大家修正後的項目,又要再透過 MIS 在客戶的主機上實際安裝測試。於是大家就像是 Windows3.1 時代的工作行程在搶 CPU 資源一樣,每個人的修正項目都搶著要 MIS 安裝測試。MIS 分身乏術,程序員也在加班枯等 MIS 回應。[風險點]

檢視風險

故事內容中的風險點,我分為5類進行檢視。

  • 開發環境的建置風險
  • 測試平台的建置風險
  • 套件相依性的建置風險
  • 部署程序的建置風險
  • 修正項目應排程

開發環境的建置風險

如果開發團隊決定採用同一套 IDE 工具、同一套 Framework 。那麼我們最好先配置好一個基礎的 IDE 與 Framework ,設置一個作為每個軟體項目共同起點的初始內容。將它們包裝在一起,讓團隊成員下載安裝。不要讓團隊成員各自透過不同管道取得軟體安裝。

以此故事為例,我們應該先用 Eclipse 建立一個專案 dynamic web project,設置好基礎的 web.xml, spring-config.xml, build.xml ,並寫好一個基本的 web service servlet (hello world servlet 是個好主意)。再利用 eclipse 將這個 hello world service 專案匯出,簽入 repository 。而 eclipse 的整個目錄(包含下載的plugin)則 tar 成一個檔案,放上公司內部的文件共享區。團隊成員從文件共享區下載已經包含開發過程所需的 Eclipse 套裝軟體,從 repository 取出 hello world service 專案,大家以相同的開發環境為出發點進行實際的開發工作。

Eclipse 最棒的事之一就是有豐富的外掛程式生態環境。Eclipse 最糟之事之一就是外掛程式生態環境豐富! 不同團隊成員下載不同版本的外掛程式。一般而言,這不是問題。但是外掛程式版本間偶爾會有不相容性存在,而突然間,你就會碰上無法重製的建置。這是標準化問題。

《程式設計師提昇生產力秘笈》,Neal Ford,O'Reilly中譯版

不是只有原始碼才需要版本控制。你應該把所有跟編譯、組態、部署、測試與執行系統所需的相關檔案都納入版本控制。

有些軟體公司可能會簽一份長達十年的維護合約,然而你可能不到十年就因為接了新的專案而更換新工具了,因此,保留一份當時的建置與測試環境是非常值得的。

《軟體工程與Microsoft Visual Studio Team System》,Sam Guckenheimer,碁峰中譯版

測試平台的建置風險

如果系統架構明確地需求某些軟體套件被預先安裝在作業平台內,那麼最好列出系統軟體需求表(software required list),交由系統管理人員在一台主機上安裝一個包含所有需求軟體的作業平台。再製作一個該作業平台的影像檔 (虛擬機影像或系統還原影像),用影像檔去複製其他的主機內容。 當我們的專案需要增加新的測試主機時,不論是安裝在實體機器,或是安裝在程序員的虛擬機中,都應統一使用這個作業平台影像檔安裝。

當你有多種組態需要測試,輪流切換不同的測試環境將會耗掉大量的時間。一般而言,你必須清理每一台測試機器,包括將作業系統還原、安裝必要元件、然後重新設定組態。如果你有很多種組態要輪流測試,那麼光是做這些準備工作就會花掉很多時間,因而大幅壓縮你實際能夠執行測試的時間。

為各種測試組態分別建立不同的虛擬機器意味著只需要花一次的工夫,而不用每換一種測試組態就要重複安裝設定一次。

《軟體工程與Microsoft Visual Studio Team System》,Sam Guckenheimer,碁峰中譯版

使用虛擬化機制讓專案之相依性標準化。

從乾淨安裝的作業系統建立開發環境,把作業系統、工具、辦公室軟體套件之間隱藏的相依性沖刷掉。

《程式設計師提昇生產力秘笈》,Neal Ford,O'Reilly中譯版

系統軟體需求表與專案用作業平台影像檔的維護工作應該會持續到結案為止。隨著軟體開發工作的進展,可能會有其他的軟體套件被所有工作項目使用。這些軟體套件經過評估後,應該要補充列入系統軟體需求表,交由系統管理人員處理。系統管理人員使用作業平台影像檔還原一台主機後,安裝新補充的軟體套件,再製作新的作業平台影像檔。然後把其他人的測試主機也用新的影像檔重灌。

如果工作項目所需的額外軟體套件並未列入系統軟體需求表(亦即作業平台影像檔中沒有安裝該軟體),那麼負責該工作項目的程序員就必須將額外軟體套件的安裝動作寫入工作項目的安裝程序內。這部份還會在「部署程序的建置風險」一節說明。

套件相依性的建置風險

如果工作項目中有共同的元件係相依於某個工作項目的內容。那麼要儘早將該元件封裝為可共用格式(C/C++ 產生 shared object/DLL,Java產生 jar),讓其他工作項目匯入封裝後的文件,而不是直接複製源碼過來。該元件每次更新後,都要產生新版的共用格式文件存入文件共享區。引用該元件的工作項目,在建置文件(Makefile, build.xml)中也要檢查該元件的相依狀態,確保建置時匯入的是最新版的元件。

在部署階段時,該共用元件應該獨立製作一個 deb 包,將它加入其他軟體項目 deb 包的相依套件清單。

雖然 Java 的 package 機制很方便,但是仍應避免直接匯入源碼的方式。共用的 package 儘早製成 jar ,以 jar 匯入。

部署程序的建置風險

部署程序的建置又可以分兩部份討論。一、工作項目打包成 deb 。二、安裝程序的測試。

工作項目打包成 deb

如果計劃將工作項目打包成一個一個的 deb 包,那麼可以考慮設置一個獨立的 Build 機。如果公司內部已經有一套運作中的每日建置機制 (Daily/Nightly builds),恭喜你們,你們應該已經避過這個風險了。如果公司內部還沒有建立每日建置機制,那麼是時候推動了。

Nightly builds are a good thing, they provide immediate feedback to developers if they broke the build. Having a nightly build means that the software stable and is likely to build for new users. Software that is not built regularly is difficult to release. McCarthy says "If you build it, it will ship".

Nightly builds at GSRC

負責每日建置工作的主機,應該獨立運作。安裝在虛擬機上是個好主意,虛擬機可以儲存快照(snapshot),快速還原。每日建置工作的主機每天都會定時從 repository 匯出最新版的工作內容,執行所有測試方案(All test suites),最後執行建置與打包工作。而且應該定期還原系統環境(虛擬機快照可以快速達成),以確保工作項目在一個乾淨的環境也可以完成測試與打包動作。

標準化建置機器不應該包括你用於建立專案的開發工具,而只有建置應用程式所需之程式庫和其他軟體框架。這樣可以防止微妙的工具相依性爬進你的建置流程內。

如果你可以在獨立機器上,以單一命令建置應用程式,顯然你的組態正確。

《程式設計師提昇生產力秘笈》,Neal Ford,O'Reilly中譯版

每日建置工作的主機安裝在獨立主機上的另一個好處是,需要測試打包工作的程序員,可以自己到該主機前執行測試與打包工作,而不用大家圍在某人的桌旁等他做。

少了自動化建置系統,版本控制就不算完整。建置系統不只要能自動編譯程式,還要能自動追蹤和測試編譯過的二進位檔案,而且在建置時必須盡量抓出所有的錯誤,以便在執行後續的測試之前更正這些錯誤。這種方法可以確保測試時間(特別是人力時間)得到妥善的運用。

《軟體工程與Microsoft Visual Studio Team System》,Sam Guckenheimer,碁峰中譯版
安裝程序的測試

在檢視測試平台的建置風險時,曾強調應定期用影像檔重灌或還原所有人的測試主機。當說到「重灌系統」時,我們應該已經想到一件事,那就是:「又要重裝軟體了」。我們也可以想像,當系統管理人員來到程序員面前,對他說:「老兄,我要還原你的測試主機了」之後,程序員一定會抱怨「案子很忙耶,你現在還原的話,我又要重新把我的軟體安裝回去」。事實上,這個動作的目的,就在於及早抓出安裝程序的問題。

定期用影像檔重灌所有人的測試主機,迫使所有人定期地檢視自己的工作項目的安裝程序是不是完善的。如果重灌測試主機後,我自己都不能用安裝程序快速地將工作項目安裝到測試主機上的話,那麼這個安裝程序是不完善的,必須要補強。

如果我們在測試主機上執行安裝程序都會發生狀況,那麼正式部署時就一定會出問題。與其等到部署時才修正,不如及早在開發階段找到問題。

修正項目應排程

故事中的專案有一個特殊狀況,那就是主機部署在客戶的防火牆內部,因此每個程序員的修正項目都搶著要駐廠的 MIS 安裝測試。MIS 分身乏術,程序員也在加班枯等 MIS 回應。這雖然是特殊狀況,但也不算罕見。而且有個通用的策略,那就是修正項目應排程。

身為一個程序員,對於作業系統的行程多工機制應該有所概念。而修正項目的排程工作,實際上就是應用行程多工機制。每個修正項目都要先評估其優先權,按照優先權與提出時間排入 MIS 的工作佇列中,並設定每個工作的佔用時間。當這個工作的佔用時間用盡時,不論有無完成,MIS 就回應目前的結果給程序員,然後進行下一個工作。

如果一個修正項目在佔用時間內沒有完成,那麼程序員也要就已得到的回應內容調整修正項目,重新評估優先權後再排入 MIS 的工作佇列中。

實務上,修正項目的排程機制不太可能運行得那麼完美。駐廠的MIS也可能一直被客戶要求處理其他相關事項,以至於不能按照預定工作時程處理修正項目。但是重點在於,我們要儘量讓每個修正項目的結果,在可預期的時間內回饋給程序員。避免讓一些修正項目一再佔用 MIS 的工作時間,導致其他修正項目無法得到回饋,程序員枯等回應。

後記

Glenn Vanderburg calls repetition "the single most diminishing force in software development."

人類的記憶相當不可靠。根據心理學研究,人們在回憶過去的痛苦時期時,仍然會記得當時比較快樂的事,而淡忘痛苦的事。在軟體開發過程中,儘管我們遭遇了許多大大小小的問題,但是事後回想時,往往只記得成功的事而忘了失敗的事。想在事後正確地回想我們在開發過程中做過的蠢事,並不容易。我在這篇故事中,也許還漏掉了一些風險問題。

建置風險所帶來的重複性,會嚴重削弱我們的軟體開發生產力。但建置風險總是如野草一般,在不起眼的裂縫處萌芽,除之不盡,燒之不絕。建置風險的問題點,不在架構設計與程式碼內部,而發生在程式外部的運作環境,因此更是難以除錯。其副作用會延遲發生,往往到部署階段才出問題,導致驗收延期。

想要徹底消除建置風險,或許根本是件不可能的任務。但是每當你消除了一項建置風險,所帶來的好處總是高於成本。所以我們仍然要對這件事保持關注:當你在進行某些組態建置的動作時,先緩下手,看看其他人是不是也在做同樣的工作。這可能就是一個建置風險點。

相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/11157957.html

樂多舊回應
chlo.out@csie.nctu.edu.tw(fcamel) (#comment-20275865)
Fri, 08 Jan 2010 20:18:30 +0800
學到不少,多謝分享。
neowaiter@msn.com(martin) (#comment-21388891)
Wed, 10 Nov 2010 10:44:56 +0800
這是非常好的經驗分享