测试与代码质量

修改旧代码之前,先用测试固定已有行为

运行折扣函数的九个边界用例,区分保留行为的重构与业务规则变更。通过负金额反例,确认测试通过并不等于旧规则正确。

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日依据: 实现契约与说明的依据。运行输出另由本文所附实验确认。