「若愚人能堅持其愚行,他終將成為智者。」——布雷克(William Blake)
如果你想把下一個專案做得比上一個好,不妨先理解:為什麼它注定會比上一個更難。即使上個專案是場愚行,更該堅持——不是堅持完成或證明它,而是堅持從中學習。
為何情況越來越糟?#
軟體測試之所以越來越難,根本原因在於我們越來越貪心。我們看見電腦無限的潛力,所以每當成功做出有價值的東西,停下來感謝一微秒後,就立刻要求「更多」!
系統因此隨時間越長越大。五十年前用 87,000 位元組記憶體的程式算「龐大」;今天作者寫這段文字的文書處理器用了約 8.7 億位元組,是當年「龐大」的一萬倍。
但故事不只是「大一萬倍」。作者以二戰後化工業的教訓類比:當時盛行「規模經濟」——越大越便宜。現實卻相反,因為更大的廠房:
- 失敗點更多(number-of-failures)
- 更難釘定失敗原因(time-to-pinpoint)
- 更難在不產生副作用下修復(time-to-fix)
- 每次停機檢修損失的生產力更大(lost-opportunity cost)
這些因素相乘,使規模的「成本」超過了規模的「經濟」。化工公司因而放棄興建最大的廠房、轉向小廠。同樣的問題——軟體呢?這四點軟體統統適用。
讓系統盡可能小(但不能更小)#
控制需求是管理工作,主要在開發專案之外。做不好就是管理的失敗。需求失控太常見,業界甚至給了它好幾個名字:需求滲漏(leakage)、需求蔓延(creep)、需求漂移(drift)。
同意「再加一樣東西就好」太容易了——尤其當你沒有流程去估計這項增添對「錯誤成本」的非線性影響時。
控制需求成長之所以更難,是因為它要求決策者區分:
- 「真正必要」的東西
- 「有價值、但價值不值得額外成本與風險」的東西
也存在「需求發現(requirements discovery)」:測試讓開發者驚覺「欸,這真的是個需求!」。我們稱為滲漏/蔓延/漂移的,很多其實是辨認出某個被埋藏的假設、某個早期需求流程漏掉的需求。
開發者常犯的錯是「想在軟體裡處理每一種可能情況」。能用軟體做任何事,不代表你該在同一個系統裡用軟體做所有事。
作者的客戶依 15,000 份租約計算礦權權利金,其中七份極古老、例外條件繁多,導致軟體專案卡在測試。作者建議:這七份暫時照舊「手動」計算。光這一改就讓系統規模減半、開發工作量降為十分之一。不是每件事都得用電腦做,至少不必第一輪就做。
讓你對「系統」的模型保持寬廣#
你也許成功做出一個小應用,卻因為它依附的執行環境、瀏覽器、作業系統或網路又大又複雜而出問題。更麻煩的是與應用互動的「人類」——那是所有系統中最棘手的。要警覺你開發的簡單系統如何與龐大、極度複雜的系統交纏。
以清晰介面、隔離的元件漸進式建構#
程式的「大小」不只是行數(前文用此簡化只為說明)。兩個物理大小相同的程式,內部複雜度可能天差地遠,而複雜度往往是測試難度的主導因素。控制複雜度的方法之一是漸進式建構:
- 每塊都建好、測好、修好,再做下一塊
- 許多「敏捷(agile)」流程都採此哲學,基礎是把每塊做得夠小,讓完成品高機率不留 bug
- 因為你越確定新 bug 不在「已完成」的部分,就越能簡化下一個元件的 bug 釘定
搭配漸進式建構的是「測試先行」——每個元件動工前先建立驗收測試。新元件通過後才加入系統,再以「調查式」而非「確認式」的方式積極測試。
漸進式開發在「重要性先行」引導下還有額外好處:若遇到意外必須提前停手,你至少已在有限時間內做完了最重要的事,仍能如期交付有用(雖未完整)的東西。有時系統上線後,你甚至會發現那些額外部分根本不值得加,從而讓系統比原以為的更小。
減少「進入」系統的 bug 數量#
測試難度不只看你「移除」多少 bug,還看「何時」移除。一般而言,越早除掉 bug,成本越低。
人們常混淆「bug 被發現的時間」與「bug 被放進去的時間」。開發後期測出的 bug,可能在更早任何階段就被放進去了。「bug(蟲)」這個字不幸地暗示錯誤是神祕地「爬」進產品——但它們不是爬進來的,是「被人放進來」的,在任何時間。
這一切說明:測試必須及早開始、全程維持。漸進式建構與測試先行是其中一種方式,但其他方式對「只相信跑程式才是測試」的人是看不見的——下一章將說明為何那種看法是錯的。
小結#
雖然組織與執行不良的測試確實會拖長工作,但隨著產品越大越複雜,測試與修復本就有「內在的系統動力」使其更耗時。理解這些動力,就有辦法在一定程度上反制它們。
常見錯誤#
以下是面對「測試越來越難」時最常見的失誤。
- 低估老舊、修修補補的程式碼之複雜度:作者以家中六十年老水管比喻——水管工原以為通水槽很簡單,卻花了兩小時才搞懂如何改裝老管路。老舊補丁程式碼同樣令人困惑
- 不保持工具鋒利:複雜度不只來自你以為在建造的東西,也來自你「建造其上」的東西。編譯器、網頁平台、共用資料庫裡的 bug 就是你系統裡的 bug,且比你直接放進去的更隱蔽。趕著交付時拖延工具更新,是在招來日後一堆測試問題
- 不允許討論、更別說衡量這些事:每當你聽到「這個不能談」,就該停下手邊一切去談它;「這個無法衡量」也一樣
- 不隨當前經驗調整流程資料:作者沒見過不含意外的開發或維護專案——這正是測試無法事先規劃到完美、必須是探索性的原因之一
- 用早期結果當作後期結果的指標:關於初始案例你永遠知道一件事——依定義,它們與後期案例不同
- 把測試人員當成「阻擋交付的壞人」:好壞與否,他們只是資訊的提供者,不做交付決策;若他們做了,那壞人是他們的主管
- 測試人員自視為「品質警察」:就算他們是警察,也絕不是法官、陪審團或立法者