在 1/9 在新竹舉辦的敏捷開發方法分享會,當同人分享到 XP Refactoring 實務的經驗時,台下有一位聽者剛好也是我目前的同事提出一個問題:該由誰來決定何時應該重構的問題。同人當時回應重構多半發生在軟體架構的設計上,一般開發應用程式的程式員通常比較不太會有機會重構。在專案每天早會上,團隊各個成員會報告他們目前進行的工作狀態,當同人發現他們遇到架構面上的問題,我便會著手進行架構的重構以避免系統發生疊床架屋的現象。
同事好奇重構的決定是否有客觀的標準,同人表示這部分多半還是個人主觀的經驗居多。在同事後來開車載我回台北的路上,我們再次談到決定重構的時機。同事覺得重構的時機似乎不是一件容易掌握的事,同人進一步地解釋,當時我們在應用程式的開發沒有太多重構的機會最主要的原因,是因為在架構上力求簡潔而單純的設計概念,使得應用程式的開發已經變得很簡單,實在不太需要運用重構用來增加設計的彈性。
趁這個機會,同人向同事強調架構的彈性不應該以需求不得改變為前提,而是要能夠因應「有限度」的變化而發展而不斷地調整及演進。也就是好的架構並非從恒久不變的核心來出發,而是要先去識別出問題的輪廓才找得到適用的核心。同人經常在軟體開發的實務中看到,人們花費了太多的心力來堅持不變的核心,到最後才會發現原來問題是出在自己對問題假設錯誤。
那麼,在軟體開發的過程中,有沒有方法可以避免我們浪費心力在無謂的堅持上,然後用比較簡單而又有效率的方式來完成我們的工作呢?經過與同事上面的對話,同人想到運用到我在分享會中所提到的觀念與實務,可以很輕易地掌握設計演進的節奏。藉由此篇文章分享出來,也算當做同人在 1/9 敏捷開發分享會後的一個註腳吧。
設計演進的基準
軟體開發與其它的產品開發有一個很大的不同,在於軟體通常很難在一開始就定義出明確的需求規格,取而代之的是軟體的發展方向,是用來解決利害關係人在真實世界所面臨的問題。這也是使用 IPO 傳統目標導向來開發軟體經常遇到的困難,當需求的改變不可能不經常發生的時候,軟體開發在品質文化上就不應該採用「照章行事」的模式,而是應該建立具有回饋機制的開發系統來把穩軟體的開發方向。
可以「把穩方向」的軟體開發系統應該具有什麼樣的回饋機制呢?根據溫伯格在他的軟體管理學的觀點,管理者賴以把穩方向的回饋機制,必須是可以直接及穩定的觀察專案目前的狀況、並且比較專案目標與現況的差異、然後以後續如何減少差異為目標改變或調整計劃、最後再根據計劃採取行動來改善專案狀況。其中有關於直接而穩定的觀察,直接代表肉眼可直接觀察的專案結果,穩定代表不同觀察者每次觀察的結果都相同。
相信從以上的觀點,我們可以清楚看到「把穩方向」的品質文化與敏捷開發調整式規劃的關連。規劃的目的不是為了得到一個巨細彌遺的計劃讓我們照表操課,而是指引一個達到目標的概略方向,然後因應專案實際現狀來調整計劃,來使我們不致迷航。基於這樣的觀念,運用 TDD(測試驅動開發)剛好可以提供對專案進行直接及穩定的觀察。
TDD 改變我們對解決問題的假設,不假設用什麼方法來解決問題,而是假設問題情境來思考各種可能的方法,並發展出最經濟的解決方案。假設方法如何解決問題並不是不好,只是這樣很容易讓開發者把他所熟悉的方法當成黃金錘,但最後所開發出來的軟體卻不見得符合使用者實際的需要,而且通常要花費很長的時間才會發現以上的落差,因此不會有足夠時間和資源來符合使用者的需求。
如果能儘早驗證開發的成果是否符合實際的需要,開發者就可以在早期得到使用者的回饋,進而調整努力方向來改善開發成果。傳統的開發方法沒有辦法做到早期回饋,是因為使用者要等到軟體開發出來才能接觸到系統,而且通常他們缺乏軟體開發的專業知識,所以在這之前他們是很難給予開發者有效的回饋。TDD 的開發思維則是促使開發者從思考軟體的使用情境出發,不要太早接觸繁複而細節的設計或實作,而是因應實際的需要而定義出界面規格,然後依據這些規格來決定該如何驗證問題能夠被解決。
因此,TDD 可以直接而穩定的在早期觀察開發狀態,提供設計演進的基準。這樣的基準可以讓開發者在開發過程中,直接面對目標而開發系統而不致迷航。也就是因應現實問題情境的需要,開發者未必有足夠的時間與心力來把設計做到盡善盡美,而是嘗試定義出最主要的功能需求,先採用最簡單的方式來滿足它們,然後再視使用者回饋的實際需要增添或修正功能,必要的時候甚至可以進行重構來維持設計簡潔與完整。換言之,TDD 是用來使開發者面對目標,讓開發範圍不要無謂擴張的一項有力工具。
延緩設計的決策
然而,當開發者採用了 TDD 的開發模式之後,是否正意味著我們儘快將使用者需要的功能實作出來,並不需要進行太多的設計工作,是否代表設計對使用 XP 實務的開發者來說是不重要的呢。但依照同人自身的經驗來看,對於使用 XP 實務的開發者而言,設計並非不重要,而是留下為實際的問題改進設計的彈性。
TDD 並非不做設計,而是把做更周詳的設計的時間延後到可以得到更佳設計決策的時候。或者更根本地來說,TDD 本身就是一種設計手法;而不是因為它以寫測試案例開始,而就把它當做開發的測試過程,這樣的誤解反而違反 TDD 的基本精神。
就設計的觀念來看,設計概念的完整性會直接影響設計的良窳。因此開發者應該盡全力來找出解決問題最重要的概念,同時隱藏或略除不必要的實作細節,來使設計更容易了解與實作。這也就是設計關鍵在於抽象化的道理,但問題是在對問題認識未盡全面以及成本或時程的限制,開發者通常沒辦法在第一時間找出解決問題最適當的核心概念。
因此,TDD 在還沒寫實際的程式之前,先撰寫測試案例。其所關注的問題並不是有效率的測試,而是務實的設計。先以測試案例的方法來識別出系統的大致輪廓,目的是以解決問題為前提,把問題的範圍限制在開發者可以全局掌控的情況下發展解決方案。而不是為了解決方案的堅持而使問題發散,最後反而使問題失焦而終致失控局面的發生。
如此,縱使軟體開發的變化是難以預測的,但只要每一次的變化都可以將廣大的可能性,限制在某一部份,那麼開發者就可以在系統的穩定與彈性之間維持良好的動態平衡;既不會讓需求的變化造成設計的崩壞,也不會因為技術的限制而造成設計的僵化。
穩定的設計可以在環境改變的情況下,不致使系統失去控制,彈性則是可以適應需求的變化而改進系統的設計。期待設計在一開始一次到位,這通常是不切實際的期待,還不如面對現實,先用簡單的方式滿足需求,然後隨著對問題的更深入理解,自然而然地演進出可以適應變化的設計。
這樣的觀念是把軟體開發的焦點放在系統邊界,然後隨著環境變化而逐步演進核心的設計,與傳統機械觀點的隱喻所不同的是,軟體開發不是努力去製造一些東西,而是運用生物演化觀點的隱喻:軟體開發是為了改變一些事情而努力。TDD 與 Refactoring 的搭配,正是促成軟體開發演化出複雜適應性以適應變化的實務方法。它們可用來避免軟體設計求道之過,所謂「道可致而不可求」不去強求而自然得到,才是真正的致呀。
提早整合的行動
其實軟體開發專案要把穩方向是很困難的,溫伯格認為主要的原因多半是管理者介入無效的管理作為;沒辦法掌握好「動作要小,行動要快」的原則,結果更增加專案的複雜度與風險,使得問題更加難以處理。
因此,如果使用 TDD 與 Refactoring 這兩項實務,可以讓我們具體地用測試案例來直接而穩定地觀察開發成果,運用簡單的設計來解決問題,又可以在必要的時候施重構來改善設計,以增加適應變化的彈性。那這樣還有沒有可能沒辦法掌握好設計演進的節奏呢?同人認為唯一的可能就是開發者沒有提早整合的行動,例如沒有在早期接受回饋以調整測試案例與重構,使得行動太慢,動作太大。
為什麼開發者會沒有提早整合呢?理論上,當開發者開發的程式完成 TDD 的測試程式之後,照理要進行清理程式碼的動作。比如說進行重構來去消除程式碼的重覆性或使程式碼看起來更簡潔而易懂。這樣可以讓程式碼在每一次的修正之後,都還是最乾淨的情況下,所以是不大可能會增加程式碼複雜度而造成未來難以維護的問題。
然而,在考量專案實際的問題下,開發者不見得每一次都會有足夠的時間來整理他的程式碼,使之形成良好的程式寫作風格與結構。而且管理者也很難知道開發者到有沒有力行這樣的原則,除非能夠對每一支程式來進行 Peer Review,否則只有「完成功能之後必須清理程式碼」的說法,而沒有為提昇程式碼品質而在每支程式交付時,提供改善回饋的具體措施,對專案其實並沒有太大的助益。
可能有人會認為搭檔編程的實務,就是讓每一次的程式開發都提供回饋。基本上同人並不反對這樣的說法,但要這樣做必須注意開發團隊的文化是否能夠支持這樣的做法。
同人以前曾有專案成員調動到使用搭檔編程的專案團隊,雖然看起來搭檔編程似乎讓她能力有所成長,但我也發現到她也出現適應不良的現象而心生抗拒,而當同人與他當時的主管當時的關心,也很難讓他願意分享自己的心情。由此看來,似乎還是有人習慣自己一個人寫程式,而不喜歡由兩個人共同開發一支程式,而且開發資源的分配者也不見得能夠支持這樣的觀念。
當然,要針對每一支程式做到 Peer Review,在實務上還是有很大的困難的。同人認為問題並不是 review code 要花費多少時間,而是專案需不需要針對程式碼進行儘早的回饋,以在最適當的時機採取最適當的行動?這其實是取決於專案願意花費多大的代價來提昇軟體的品質,不然當品質不合乎需求時,專案所節省下來的預防與檢驗成本將會為造成更大的失敗成本。
要落實提早整合的行動,專案團隊每天早上的 Daily stand-up 會議與採用 Daily build 持續整合的機制,是讓開發團隊的反應能力可以「與時俱進」的回饋活動。就像同人在分享會所分享的實務,在 Daily stand-up 會議得到開發實際碰到的問題,其實是幫我們識別關鍵設計概念的機會。由於過去問題尚未出現或對它們還不太明瞭,所以我們不需要或沒辦法進行設計的深入考量。而在 Daily stand-up 會議中,我們可以及時得到用來演進設計的資訊,並討論如何在後續的計劃中調整設計。
至於 Daily build,每天都會整合出可以實際運作的軟體。在 build 軟體的過程中,會自動執行 TDD 的自動化測試,以確保系統功能都是正常的。如果在 build 的過程中發生問題,馬上會立即通知相關的開發者立即解決問題。這樣可以在每天都讓每個開發小組都能密切地保持緊密的溝通與整合,發現問題立即處理而不需要等問題擴大之後才動大刀。
Daily stand-up 會議與 Daily build 的活動,讓每天都持續溝通與持續整合,每次都讓專案都朝向目標前進一小步,更重要的是,讓成員與時俱進地掌握設計演進的節奏!