天天看點

從“調試器焦慮症”到“測試驅動強迫症”

多年以前,我曾經參與過一個政府項目的開發。一天傍晚,我和另一位同僚正在這個政府機關的機房裡做例行檢查。一切看起來都運作正常,我們打算做完檢查就下班回家。這時客戶走了進來。

“網站上有一個資料顯示得不太好看,你們能不能調一調?”他問我們。

看起來不是一件難事,那又何必要拖到明天呢?我們打開筆記本電腦,調出源代碼。不過是改變一個資料的顯示,三下五除二弄好了就部署上去,客戶一定很開心,我們這樣想着。但很快我們就發現事情沒有那麼簡單:客戶需要的資料實際上涉及到背景的一系列計算,而我們現有的領域對象并不能很容易地完成這樣的計算。幸運的是,我這位同僚素有“快手”美譽,寫起代碼來真是不含糊。幾分鐘功夫,他就修改了以前的領域對象,算出了客戶想要的資料。

編譯,打包,釋出——倒黴。網站上出現了好幾處古怪的資料,即使不運作QA測試腳本我也能看出,一定有什麼東西被我們弄壞了。“沒問題,”這位同僚自信地說,“分分鐘搞定。”隻見他滑鼠如飛,在Eclipse裡打上幾個斷點,緊跟着就進入了調試視圖。一行行代碼被高亮顯示,一串串上下文資訊出現在檢視視窗上。他的臉上露出胸有成竹的表情,那是高手宣告“一切盡在掌握”的表情。一個小問題,幾分鐘就可以搞定,隻不過再一次展現高手的功力而已,不是嗎?

三十分鐘過去了,他的表情變得緊張。修改過的代碼越來越多,而出現的問題似乎在随着代碼的修改量成正比上升。“真的沒問題嗎?”身為初哥的我怯怯地問道。“放心。”簡練有力的回答,然後是……又一個三十分鐘。他的額頭開始滲出密密的汗珠,而我已經跟不上他的思維了——我甚至懷疑他是否還清楚自己的思維,因為他在調試器中的操作越來越頻繁地出錯。噢,天啊,我們怎麼會落到這步田地?

最終客戶拯救了我們——雖然當他走進機房、詫異地說“你們怎麼還沒弄好”時我尴尬得無地自容,但當他說出“明天再來弄吧”我還是長舒了一口氣。說來也怪,第二天我們輕輕松松就把這個修改完成了,前後真的隻用了十分鐘。誰知道呢,人都有趕上這寸勁的時候。

也許是這一次的經曆讓我印象太過深刻,從那以後,我一看見調試器就有點手發抖心發怵。這時讀者要問了:你身為程式員有這種心理障礙,不就好像大廚握不住菜刀、俠客抓不緊寶劍麼?你還怎麼能吃這碗飯呢?原因是,自從那一次刻骨銘心的經曆之後,我就一直堅持着……

測試驅動開發

這是個酷詞,換成它的英文縮寫(TDD)就更顯得酷,是以人們都喜歡把它挂在嘴邊,還喜歡把它說得倍兒複雜、倍兒深奧。其實在我看來,測試驅動開發這件事非常簡單,總共就3步:

  1. 紅:動手做任何一件事之前,先寫一個測試來描述你将要做的事情;既然你還沒有做這件事,是以測試無法通過,測試狀态為紅色。
  2. 綠:寫盡可能簡單的代碼,讓測試通過,狀态條變成綠色。
  3. 掃掃地:剛才寫的代碼夠不夠漂亮?看着不爽的話就把它整理一下(更酷的說法叫“重構”),整理完之後測試狀态應該仍然是綠色。

(有人跳起來喊了:難道這就是你的工作方式嗎?别說沒有做設計了,連實作都是“盡可能簡單的”。你要怎麼保證以後的可擴充性呢?

對于這個問題的标準答案是:以後的事以後再說。任何更進階更複雜的設計,你都有可能不需要它。用客戶花錢的時間來做一個可能不需要的功能或者設計,說得重了就是缺乏職業道德。可不要忽視了簡單的力量:越簡單的代碼越不會出錯,越簡單的代碼越容易擴充。)

“一紅,一綠,掃掃地”。這幾年的程式人生,我就念着這句口訣,一小步一小步走了過來。奇妙得很,這一路上,還真是沒怎麼開過調試器。要知道個中緣由,得從一種傳染性的心理疾病說起……

Test Infected

這是Erich Gamma大師發明的詞,照我了解就是不問三七二十一,每天嘴裡不停念叨着“一紅,一綠,掃掃地”……的強迫症早期症狀(例如像我這樣^_^)。據我所知,很多人像我一樣,主動選擇患上“測試驅動強迫症”(很酷的縮寫叫TDO),就是為了逃避“調試器焦慮綜合症”(也有一個很酷的縮寫叫DAS)。一項非官方調查資料顯示,程式員每次打開調試器所需的時間平均是4分鐘——“這不可能!”有人喊道,“我的調試器隻要1秒鐘就能跳出來。”沒錯,但你需要時間來設定斷點、執行程式、找到适當的運作上下文、檢視需要的變量……更要命的是,中間一個誤操作就會讓你這次調試付諸東流。所有這些讓調試器成了一個相當耗費時間的工具。

(另一項調查資料顯示,在沒有實施TDD的情況下,程式員每小時平均打開調試器8次。啊哈……)

于是問題來了:我們為什麼需要調試器?因為我們要知道程式運作中的狀态。很好。然後下一個問題是:我們為什麼要知道程式運作中的狀态?呃……因為程式子產品内部太複雜,以至于我們無法一目了然地看懂,不是嗎?因為程式子產品之間有太多的耦合,以至于一處修改很容易引發各處無法預知的變動,不是嗎?承認吧,爛代碼是需要調試的第一原因,并且我們都會寫出爛代碼。

而“測試驅動強迫症”的一大好處是它讓你更難寫出爛代碼——因為你首先就很難給過于複雜、或者與别的子產品有太多耦合的程式子產品編寫測試。還記得IoC模式嗎?(對那些錯過三年前的Spring熱潮的讀者說抱歉^_^)對于TDO患者而言這個模式——至少在使用Java時——簡直是順理成章、不言自明的,否則你怎麼能把一堆糾結不清的對象摘得幹幹淨淨分别測試呢?至于複雜得難以一眼看清的方法,TDO患者們更是想都不會想要寫出這種東西來,因為那意味着他們必須首先寫出一個同樣複雜(甚至更加複雜)的測試。誰會跟自己過不去呢?

直接的結果是,作為一種強迫症,TDO強迫你寫出短小易懂、功能内聚、耦合松散的代碼,并把你從DAS的痛苦解救出來。并且很多TDO患者曾經是DAS患者這一事實有效地促進了TDO對于代碼品質的提升作用:這些人在遇到複雜的情況時,會優先選擇把現有代碼重構得清晰易懂,以盡量避免打開調試器。

TDO症狀分析

(以下是對TDO患者病情的一些分析,我希望讀者能了解一個強迫症患者的苦衷……)

複雜的操作——例如與外部系統之間的I/O——是人們打開調試器的一個理由,但這些操作恰恰是需要并且應該被良好封裝起來的。如果不能有效地用mock對象或者測試沙箱來編寫測試,你可能面臨一個比DAS更嚴重的問題:你的系統很可能正在與目前使用的作業系統、甚至目前使用的這台機器緊密耦合。TDO強迫你在自己的系統和外部系統之間劃清界限,這對于未來的部署和移植都有很大的幫助。

更複雜的操作——例如多線程和事務——是打開調試器的更強烈的理由。但有趣的是,這些操作往往很難調試(如果不是根本無法調試的話)。反倒是通過精心編寫測試,你可以控制程式以你希望的方式精确運作,進而有效地診斷和解決問題——并保證同樣的問題不再出現,因為測試會不斷被運作。

很多程式設計語言——例如JavaScript、Ruby和PHP——不像Java那麼容易調試。就拿JavaScript來說,要調試它需要用到Firebug之類華麗的工具。(順便說一句,我願意出10塊錢跟任何人賭他不喜歡用Firebug來調試。)在這種情況下,你通常會傾向于把每個子產品的複雜度都控制在可以用一條輸出語句看清其内部狀态的程度,并依靠測試來單獨診斷每個子產品。

TDO患者還傾向于讓測試在失敗時輸出盡可能有意義的資訊,例如“expected: 100, actual: 99”就比“false is not true”要好。(讀者可以試着研究一下你的測試架構,看它在什麼情況下會輸出這兩種失敗資訊,這有助于你了解TDO患者的心理狀态。)

頻繁運作所有測試是TDO患者的症狀之一:如果不能每過15分鐘就把所有測試都運作一遍并看到綠條,他們就會表現出輕度躁狂症狀。出于對自己脆弱心理的保護,他們會小心地避免在測試中執行網絡操作或資料庫操作,其副作用是把對外部系統的依賴有效地封裝起來,進而有效地減少需要打開調試器的機會。

當然在确實有必要的時候(這種情況極其罕見),TDO患者還是會打開調試器。不過正如前面說過的,很多TDO患者曾經是DAS患者,是以他們會讓自己在DAS發作之前離開調試器,并通過編寫更多的測試、做更多的重構來避免再次打開調試器。在DAS無可避免地即将發作之前——這大概是進入調試器5分鐘之後——他們會撤銷剛才所做的一切修改,從前一個綠條的狀态開始重新做這一次的任務。

所有人都知道,如果沒有麻煩,我們是不會打開調試器的。不過在一個TDO患者看來,如果打開調試器,那就意味着你真的有麻煩了。即便你——暫時地——還不是DAS患者,也許你也應該記住這個忠告。

繼續閱讀