筆者除了社群活動以外,有時候會受朋友的邀約到他們公司或單位去辦活動,主要還是跟單元測試與軟體有關的工作坊(順便業配)。有一次經驗讓我印象深刻。
在所有活動還沒開始之前,這位同學就先舉手了:「我看了很多資料,發現測試對程式開發本身沒什麼幫助。」
好小子,看來是被主管指定來參加的。不過無妨,這種一開始對測試與架想設計存疑的同學,筆者也見過很多,於是活動就開始了。
那是一場 Workshop,內容在讓參加者透過一些軟體設計的原理,在測試的保護下,一步步把自己剛剛親手製造出來的大泥團,重構成鬆動耦合的樣子。我的想法很簡單:「程式是你寫的,重構也是你自己動手做的。這麼複雜的邏輯你都能重構得這麼好,那回到你的工作中,那些東西應該肯定有救才對。」
沒想到最後的 Q&A 環節這位同學又舉手了:「你說的是修改方便,但我一開始就把需求完全確定好,結構整個設計好,並且要客戶簽名確認,那以後不就不用再修改了嗎?為什麼我非得學測試與重構不可?」
嗯… 我也不方便多說什麼,反正我該教的都教了,我只能說,他現在的主管真的把他保護得很好,祝福他能一直得到想同規格的保護直到退休…
本篇的內容,整理自我去年在社群裡的一個演講中的一小段。過去我不曾認真分析過為何單元測試對我來說這麼重要,一開始說真的,就只是公司規定要做而做的。直到後來也是因為體驗它帶來的好處,而一直持續研究至今,卻從未從「邏輯」上著手探討其原理。而接下來我要探討的內容,才是我自己從業多年,第一次用比較理論的方式,來分析單元測試對開發者的重要性。
系統思考是一種以整體、動態去思考問題的思維模式。坊間聊系統思考的書與文章很多,而筆者今天要拿來分析的方法,主要來自管理大師彼得聖吉的「第五項修練(全新修訂版):學習型組織的藝術與實務」與軟體大師溫伯格的「溫伯格的軟體管理學:系統化思考(第1卷)」兩本大作。
筆者姑且簡單介紹在我們待會討論的過程中,會用到的一些觀念。
這會兒,我們想要解決工作中最討厭的問題:「沒時間」。
還記得之前那位因為想花兩個月不接單光重構,被拒絕後開始擺爛,最後丟工作的 RD 嗎?
各位試想,他為什麼會想重構?其實也不難理解,對吧?因為好的架構的一個重要因素就是簡潔,而簡潔的程式本來就好改,好改的程式,改起來當然輕鬆又快,花的時間自然就少,而我們開發程式最討厭的「沒時間」問題自然也就迎刃而解了,合理吧?
如上圖,紅色箭頭代表「反向的力量」,也就是說:「系統簡潔度越高,我們沒時間的問題越能得到緩解」。
光有系統簡潔度不夠,這整個系統要能持續運作,背後肯定是有個因素在影響著系統簡潔度。而這個因素還要受「沒時間」的影響,這樣一來才能在這個系統中形成迴路。為什麼這個迴路這麼重要,因為找到迴路,我們才能解釋眼前這個真實世界中,正在發生的這個不斷循環的事情。
「那我們就來寫單元測試吧!」
這個想法不錯!大家都知道,人類很難在系統開發的第一天就為整個系統做出所有適合的技術決策,因此,好的架構是重構出來的,而輔助重構又是單元測試天生擅長的工作,因此,上圖的「?」處,填上「單元測試」,再適合不過了。
接著用黑色箭頭表示「正向的力量」,也就是一邊提升另一邊也會提升,如上圖,這三個因素的關係延著箭頭繞了一圈,形成一個迴路。在這一個迴路中,一開始我們發現一個現象叫做「沒時間」,於是有紀律地開始做「單元測試」。有了單元測試,就有勇氣重構。重構多了,系統簡潔度自然就提升,而系統簡潔度提升,沒時間的問題自然獲得緩解。
我們從上升的「沒時間」出發,繞了一圈回來,「沒時間」的現象就得到緩解,這樣的迴路,我們就稱為「平衡迴路」。為了強調,我們在中間加了一個「B」字樣,取其 Balance 之意。
看,多好!接下來的事,就只要確保「越沒時間,越要寫單元測試」,一切就搞定了。
…嗎?
要知道,單元測試對系統簡潔度的影響,是需要時間與紀律來促成的。也就是說,你不會今天寫了一個單元測試,明天你的整個系統就會變成 Clean Code,好好改好棒棒,沒有這種事。首先遇到功能有變,架構已不適合時,要在測試的保護下,勇於重構,再來遇到舊的程式需要改動時,也要先補上測試以免舊功能被改壞,最後還要全部開發者都要嚴守紀律,在測試亮紅燈時,不論這個錯誤是誰造成的,除了要放下工作優先處理以外,在錯誤修復前不要往下寫新 code,以免製造更多不確定對錯的半成品。
這樣,你的系統簡潔度才能慢慢提升,留意「慢慢」兩字。這也顯示在上圖中的「Delay」字樣上。
有在職場待過的朋友都知道,幾乎所有老闆,對員工的工作,都會有三個基本要求:
你搞一個單元測試,讓他慢慢發酵,慢慢影響,還要紀律的配合,那要我等到什麼時候?更何況,寫程式都沒時間了,哪來美國時間寫測試?
於是,幾乎沒有一個正常的老闆會拿自己的時間開玩笑,正常的老闆都會想找更快的選擇。
如上圖,我們在沒時間時,也可以選擇不走下面那條路,而另闢蹊徑,例如:「把測試交給專責的 QA」。把測試交給專責的 QA 真的快很多,因為,你今天把工作從 RD 身上抽走,他的工作馬上就會變少,時間馬上就會變多。也就是,這是一件「今天做,今天就會有效」的事情。比起拿單元測試來塞給 RD,又要等它慢慢發酵,後者明顯快多了吧!
事實上,事情並非這麼簡單,別忘了我們活在一個複雜的環境中,任何決策都可能有意想不到的副作用。今天老闆發現 RD 沒時間,於是叫他們不要測試了,把測試工作交給 QA 做,的確是能馬上讓 RD 看起來時間變多了,但是,「確保程式正確」本來是 RD 的工作,你把它們抽走,空下來的時間變多,這會讓外界對 RD 的工作量與開發速度,有了截然不同的評估(或者可以說是幻想)。
試想,假設你是老闆,看到原本要天天加班的 RD,現在突然所有工作都可以提早做完,你會做什麼?你會誇獎他們好棒棒?一人放三天榮譽假?
不會吧?你會做什麼?
對了,你會給他們更多工作!
於是,如上圖,因為對 RD 速度不切實際的幻想,而得到更多工作的 RD,會造成比原來更大的時程壓力。如此一來,就更沒時間寫單元測試,當然也就更遑論改善系統簡潔度了。
這個意料之外的影響,我們從沒時間出發往上分工作給 QA,本想直球對決,直接造一個平衡迴路來緩和沒時間的問題,卻一個不小心,製造出一個往左的力量,造成對單元測試的負面影響,最後對原始的沒時間的問題反而繞出了一個「增強迴路」(圖中以「R」來表示 Reinforcement 的意思),也就是讓原本就在變嚴重的問題更嚴重,RD 更沒時間了!
這樣的系統,在彼得聖吉的「第五項修練」一書中,被定義為常見的「捨本逐末」基模。這代表我們面對問題,選擇了解決症狀,卻忽略了強身健體,不可不慎。
當開發者沒時間、工作做不完,這時我們做出不正確的選擇,所造成的後果,不只是「捨本逐末」而已,篇幅有限,我們下回分曉。
謎之音:「一葉蔽目,不見泰山;兩豆塞耳,不聞雷霆。」