測試與程式碼品質

修改舊程式碼之前,先用測試固定既有行為

執行折扣函式的九個邊界案例,區分保留行為的重構與業務政策變更。透過負金額反例,確認測試通過並不等於既有規則正確。

閱讀 2 分鐘
修改舊程式碼之前,先用測試固定既有行為 封面圖

安全修改舊程式碼的第一步,是記錄目前結果並分開處理結構與政策變更。特徵測試描述的是既有行為,並不保證這項業務規則正確。

原理與設計判斷

重構是在維持可觀察行為的前提下改變內部結構。規則不清楚時,除了正常輸入,也要記錄分支邊界、缺值與失敗路徑。此處的既有函式會在顧客類型為B且金額為正時扣除30。 第一手資料

重現程式碼

這個為實驗設計的折扣函式,改用折扣運算式實作。九個案例涵蓋-1、0、1、29、30、31、100與其他顧客類型。預期值另外寫在表內,不是再次呼叫舊函式來產生,因此能獨立檢查明確列出的契約。

這個獨立範例已於 2026-10-23 使用 Apple Swift 6.4,以 Swift 6 語言模式在 arm64 macOS 目標環境執行。不需要網路或額外套件。

下載完整 Swift 檔案

請在下載檔案的資料夾執行。檢查不符時,precondition 會中止程式。請勿使用 -Ounchecked,因為它會移除這些檢查。

swift -swift-version 6 legacy-code-maintenance-problems.swift
func legacyPrice(kind: String, amount: Int) -> Int {
    if kind == "B" && amount > 0 { return amount - 30 }
    return amount
}

func refactoredPrice(kind: String, amount: Int) -> Int {
    let discount = kind == "B" && amount > 0 ? 30 : 0
    return amount - discount
}

let cases: [(String, Int, Int)] = [
    ("B", -1, -1), ("B", 0, 0), ("B", 1, -29),
    ("B", 29, -1), ("B", 30, 0), ("B", 31, 1),
    ("B", 100, 70), ("A", 100, 100), ("A", 0, 0)
]
var clampDifferences = 0
for (kind, amount, expected) in cases {
    precondition(legacyPrice(kind: kind, amount: amount) == expected)
    precondition(refactoredPrice(kind: kind, amount: amount) == expected)
    let changed = kind == "B" && amount > 0 ? max(0, amount - 30) : amount
    if changed != expected { clampDifferences += 1 }
}
precondition(clampDifferences == 2)
print("cases=9 preserved=9 clamp-differences=2")

執行結果與解讀

cases=9 preserved=9 clamp-differences=2

兩種實作都符合九個預期結果。若把負的總額調整為0,金額1與29的結果就會改變。clamp-differences=2不是掩蓋測試失敗,而是另外確認這項政策變更會改動兩個既有結果。

適用範圍與限制

這不表示應允許負的收費金額。實際折扣政策須向負責人確認。本例只用小整數,沒有驗證稅金、貨幣四捨五入、溢位或資料庫副作用。九個案例也無法證明所有輸入都等價。

接下來要確認的事

實務上先檢查呼叫端與儲存資料,補充觀察案例。若應禁止負金額,先議定新的預期值,再另外修改。若資料庫讓測試難以隔離,可接著參考儲存介面的依賴注入實驗。

延伸實驗

來源與驗證

  • Definition of RefactoringMartin Fowler · 作者原文 · 查核 2026年10月23日依據: 實作契約與說明的依據。執行輸出另外透過本文所附實驗確認。