論壇文章
除了測試之外?

如果您像我一樣是五年級生的話,也許都還記得以前流行過的一種遊戲,叫做電子雞。長相像是一個小鑰匙圈,上頭有一個小小的螢幕,裡頭住著一隻醜醜的電子雞。從電池放進去的時候,他會先從一顆蛋開始,然後孵化成小雞,隨著主人持續的餵養,陪他玩遊戲而逐漸長大。電子雞的遊戲很特別,他沒有開關,你不能今天比較忙就先把他關起來,等到有空的時候再打開來餵餵食物,玩一玩。當電子雞時間到了肚子餓的時候,他會嗶嗶的叫,如果你沒有趕快滿足他的需求,可能過一陣子之後,你會發現你的雞生病了,甚至入土為安了。在做軟體專案時,有些部分跟養電子雞有點像,你不能夠今天想做就做一點,不想做就不做,當你沒有好好照顧你的專案時,可能他就會變成慘案。但軟體專案的開發其實比養電子雞困難多了,我們也很期望能夠像養電子雞一樣,當時程有問題的時候、當程式品質有問題的時候、當需求開始發散的時候,就能嗶嗶叫的提醒您,而非等到專案到了後期的時候才發現到處都是問題。

在製造業中,品質的把關是測試,藉由不同的測試,讓製造出來的產品能夠符合品質的要求。但是在軟體業中,單靠程式開發完成後的測試其實已經太晚了。軟體開發與製造業最大不同點在於軟體開發的過程不像設計後的大量製造,而是類似一種研發與設計的過程,當研發與設計完成時,產品也就完成了。在研發與設計的過程中產品會不斷的改變與修正,而非像製造業的生產線一樣,重點在於大量的產出一樣的東西,提高生產中的不變性,增加控制。有人說控制變動是軟體專案當中最大的挑戰,如何在變動中又能夠有好品質,則是軟體專案成功與否的關鍵。

雖然傳統瀑布式的生命週期告訴我們軟體開發要先經過詳細的需求分析、確認、設計,全都無誤後才能進行開發。但是對於現代軟體專案需求不斷改變的環境下,瀑布式軟體開發已經無法適應這樣的要求,取而代之的是反覆式的開發方式。反覆式開發強調在軟體開發的過程中其實是一段段反覆的分析、設計、開發的過程,開發的過程在整個軟體發展中是一直存在的,所以測試的過程也應發生在整個生命週期才對。但是隨著系統在每個反覆中逐漸長大,可能原本測試通過的程式碼到後面的反覆中又被不同的需求修改到,甚至可能是沒有改到的部分也有可能被修改的部分所影響。所以隨著每個反覆中系統的成長,我們測試的範圍與成本也將越來越大,甚至會遠遠超過專案團隊所能負擔的範圍。所以在近代的軟體開發中也就開始強調持續性的自動化建置與測試(Continue Integration),在開發的過程中除了系統的程式本身,也要同時撰寫測試程式,用來確認系統本身是正確的。當每次有新的程式或修改時,就應該執行所有的測試程式,確保新的程式或修改沒有影響到原本已完成的部分。

感覺好像有了自動化測試之後,軟體品質便沒有太大的問題了,然而在實際的經驗中,我們發現單只有執行每日建置與自動化測試其實還不夠。如果程式碼的品質沒有好好維持,有可能當專案開發到某一個階段的時候,任何的修改都會冒出一堆的錯誤,甚至於修改錯誤時都會再造成新的錯誤,讓修改的成本越來越高。事實上,軟體的維護行為是在第一行程式碼產生的時刻就已經開始了,一個很糟糕的系統,也是從第一行糟糕的程式碼開始的,但問題在於我們如何早期發現程式碼開始有維護的問題呢?相信大家一定都聽過「溫水煮青蛙」的故事,將青蛙放在溫水中逐漸加熱,等到青蛙發現溫度已經太高時,卻也已經失去跳離的力量,只能在鍋中逐漸被煮熟。當一行糟糕的程式碼發生你不去注意他,兩行、三行四行,等到有一天你發現的時候已經來不及了,系統中到處都是難以維護的程式,要處理哪一個都於事無補,要不就是翻掉重寫,要不就是挖東牆補西牆,並期待自己不要成為壓垮駱駝的最後一根稻草,那種感覺相信對於大多數的軟體人員來說並不陌生。

其實這樣問題的嚴重性還不止於此,在軟體開發的過程中經常喜歡舉的另一個理論就是「破窗理論」,美國的犯罪專家發現一個地區的犯罪率高低跟該地區的破窗有相當大的關係。當一個地區有破窗,或小的犯罪行為沒有馬上被制止或處理,很快的在該地區的犯罪率就會呈現快速的成長。這個原因在於人性的觀點,當你發現大家程式都寫的很差,就算你能夠多花點時間把程式寫好,你也會認為沒有這個需要,反正不差我一個。於是整個團隊就陷入一種壞的循環,大家都是只求目的不擇手段,表面上看起來可能單元測試都通過,但是實際上系統的品質卻已經快速的惡化。

一個老掉牙的笑話是這麼說的,有一個年輕人去問一個人瑞關於長壽的秘訣,人瑞回答他說:「只要持續呼吸不要斷氣就好」。但我們都知道老化的過程其實並不是在最後一刻斷氣時才發生,死亡只是老化後的必然結果,老化的過程發生在每一天之中,長壽的秘訣其實是在「延遲老化的速度」,而訣竅就是每天都要維持健康的好習慣。就如同避免專案變成慘案而結束的方法,絕對不是只要一直做下去就好,而是要讓專案在進行的過程中都能維持好的品質。也像「溫水煮青蛙」的故事,如果我們能夠一直維持水溫不上升,那麼青蛙就可以在當中活的很好。如果我們拿「溫水養青蛙」來比擬軟體專案的進行,又要讓青蛙長大,又要避免水溫的上升,才能讓軟體專案順利的開發與完成。


結合CCnet 與Source Monitor 進行每日自動化程式碼複雜度掃瞄

用什麼方式來監控軟體程式碼的品質呢?有許多的指標可以監控,在這邊我要說明的指標叫做「cyclomatic complexity」,他是透過計算一個Method 所有可能的執行路徑來決定,該路徑取決於if、for、while、and、or、else 等等有邏輯判斷的地方,當一個方法當中有越多的邏輯判斷也表示他的複雜度越高,修改與維護就會越難。有許多的工具能幫您自動的計算這個數值,目前叡揚使用的是一個叫做SourceMonitor的Freeware,他可以透過掃瞄程式碼而得到許多有用的指標,例如上面提到的Complexity, Statements, Percent Comment Lines, Methods per Class, Block Depth 等等。想知道大家所寫程式碼的複雜度?簡單,只要拿SourceMonitor 來跑跑看就知道了!然後把SourceMonitor 跟目前叡揚使用的Continue Integration Server - CCNet合體,每天進行自動化的掃瞄程式碼,回報當下前15大複雜的Method,我們就能夠在第一扇窗戶破掉的時候,馬上修理好,而不是等到回天乏術的時候才來檢討是誰的錯誤時,最後才發現其實每個人都是兇手。

Continue Integration Server就像是一隻萬用溫度計,每天監控青蛙的鍋(窩),是否溫度有上升,然後馬上像電子雞一樣,一旦上升就嗶嗶叫!不只是嗶給開發人員聽,給專案經理聽,給主管聽,甚至於還可以給客戶聽。SourceMonitor只能夠檢查程式碼複雜度等資訊,對確保軟體專案品質其實並不足夠。好在CCNet 不只可以跟SourceMonitor合體,還能夠跟自動化測試、靜態程式碼檢視、測試涵蓋度等工具結合。由於CCNet本身是一套OpenSource,他主要整合的對象也都是以OpenSource居多,但在實際的運用中可能有些更進階的功能是需要一些商用軟體才能夠達到更好的效果,例如資訊安全靜態程式碼審查、自訂程式碼命名原則、記憶體缺失等等。還好CCNet本身的設計十分有彈性,靠著一些XML 的修改,撰寫一些Script的程式就能夠跟不同的工具整合,在公司的實際運作中我們也成功且持續地把叡揚所代理的世界級工具都整合進專案日常開發的持續建構過程中,例如:Fortify的資安程式碼審查、DevPartner的Static Code Analysis、Memory Analysis、Performance Analysis等等。

雖然這樣的機制聽起來很棒,對於管理人員或客戶來說都會覺得,對!應該就要這樣做,但是在實際推動時,一開始開發人員可能會蠻反對這樣的方式,也可能會找很多理由來拒絕使用,畢竟沒有人喜歡把自己的缺點攤在陽光下,而且自動化測試程式的撰寫也是要時間的。要撰寫高品質的程式本身也是要付出成本的,可能在整體開發的時程與人力規劃上也需要搭配才能夠完成。一昧的要求更多的工作卻沒有給予相對的資源就像揠苗助長一樣,表面上好像很有成效,但實際上卻是徒勞無功。在實際的經驗中導入這樣的每日自動化建構過程其實是需要逐步進行的,在專案一開始,從對開發人員Effort最小的複雜度分析,到資訊安全程式碼掃瞄,慢慢地到自動化單元測試,程式碼涵蓋度分析等,搭配相關的訓練,讓軟體專案的開發人員也像溫水煮青蛙一樣,不知不覺就養成了好的習慣與作法。

當然有一部份的品質問題不是自動化工具能夠找出來的,所以專家的Review也很重要,但是至少使用自動化的方式可以讓我們隨時保持系統品質的透明度,當你認認真真地重視品質問題,不允許第一扇破窗的發生,這樣一來所有的專案成員也都能夠好好地維持目前的品質。就讓我們利用自動化的工具,督促我們自己做一個更好的軟體開發人員,用力的呼吸每一口氣,活到120歲!


為你的專案青蛙準備一個又健康又安全的鍋(窩)