Original Note

3.4 Code Refactoring - Read

3.4 Code Refactoring(整理版)

原始笔记: Code Refactoring.md 原始教程: 3.4 Code Refactoring created: 2026-07-27 16:19 整理说明: 本版本基于原笔记当前工作区内容,整理测试先行、保持外部行为、纯函数与副作用等已有主题,不引入原笔记未涉及的教程章节。

内容简要概括

代码重构是在不改变外部行为的前提下改善内部结构,因此需要先用测试记录当前行为,再小步修改并持续验证。把复杂计算写成纯函数、把不可避免的输入输出限制在简单的胶水代码中,可以显著提升代码的可理解性、可测试性和可复用性。

Code RefactoringRegression Test、外部行为、自动化测试、纯函数、Pure Function、副作用、全局变量、胶水代码、可测试性

目录


1. 什么是代码重构

代码重构是改善现有代码内部结构,同时保持外部可观察行为不变的过程。常见目标包括:

  • 提高可读性;
  • 降低维护成本;
  • 消除重复;
  • 简化条件和控制流;
  • 让代码更容易测试;
  • 为后续修改建立更清晰的结构。

重构与新增功能的区别在于:重构关注“代码内部如何组织”,不应改变用户或调用者看到的结果。

2. 重构前先建立测试保护

面对需要修改的现有代码,可以遵循以下过程:

  1. 确保已有测试能够验证当前行为;
  2. 执行重构;
  3. 再次运行测试,确认代码行为与重构前一致。
记录当前行为
→ 小步重构
→ 重新验证行为

2.1 为什么先写测试

重构要求外部行为不变,但没有测试时,很难可靠地判断修改是否引入了意外变化。即使是重命名、提取函数或调整条件这样的局部操作,也可能破坏调用关系或边界行为。

2.2 Regression Test

为了保护重构而记录当前输出的测试,可以作为 Regression Test。它的首要目的不是证明当前实现一定正确,而是在结构变化后发现已有行为是否发生回退。

因此,需要区分两个问题:

  • 正确性测试:当前行为本身是否正确;
  • 回归测试:修改后是否仍保持已经记录的行为。

重构时应保持测试快速反馈,并把大改动拆成能够独立验证的小步骤。

3. 纯函数

一个函数要称为纯函数,通常需要同时满足两个条件:

  1. 相同输入始终得到相同输出;
  2. 不产生副作用。

3.1 条件一:相同输入始终得到相同输出

def square(x):
    return x * x

无论调用多少次:

square(3)  # 始终返回 9

结果只取决于参数 x,不依赖时间、随机数、全局变量或文件内容。

下面这个函数不是纯函数:

tax_rate = 0.1

def calculate_price(price):
    return price * (1 + tax_rate)

虽然函数很简单,但结果依赖外部全局变量 tax_rate。如果全局变量发生变化,相同的 price 会得到不同结果。

可以把依赖显式变成参数:

def calculate_price(price, tax_rate):
    return price * (1 + tax_rate)

这样,结果需要的全部信息都来自输入参数。

3.2 条件二:不产生副作用

纯函数只通过返回值表达结果,不修改函数外部可观察的状态。

例如:

def doubled(values):
    return [value * 2 for value in values]

该函数创建并返回新列表,不修改传入的 values

3.3 纯函数的优势

纯函数更容易:

  • 理解:只需关注输入、函数体和返回值;
  • 测试:构造输入后直接断言输出,不必准备外部状态;
  • 复用:调用者不需要预先配置隐式依赖;
  • 组合:多个小函数可以连接成清晰的数据处理流程。

复杂的计算、分析和数学逻辑尤其适合写成纯函数。

4. 非纯函数与副作用

副作用是指函数除了返回结果,还改变了函数外部可观察的状态,例如:

  • 修改全局变量;
  • 修改传入的可变对象;
  • 打印内容;
  • 写文件;
  • 修改数据库;
  • 绘图;
  • 发送网络请求。

下面的函数直接修改传入列表:

def append_total(values):
    values.append(sum(values))

调用后,调用者持有的原列表也发生变化,因此该函数具有副作用。

非纯代码并不等于错误代码。真实程序必须读取输入、保存结果、绘制图形或调用外部服务。关键是让这些副作用保持明确,并避免与复杂业务逻辑混在一起。

5. 推荐的程序结构

良好的设计通常把程序分成两类代码:

非纯代码:获取输入
    ↓
纯函数:处理核心逻辑
    ↓
非纯代码:输出或保存结果

例如:

def compute_average(values):
    return sum(values) / len(values)

def main(input_path):
    values = read_values(input_path)
    average = compute_average(values)
    print(average)

在这个结构中:

  • read_values() 负责文件输入,是非纯代码;
  • compute_average() 负责核心计算,是纯函数;
  • print() 负责输出,也是副作用;
  • 复杂且需要充分测试的部分集中在纯函数中。

这种分层使测试可以直接覆盖核心逻辑,而不需要在每个测试中创建文件或捕获输出。

6. 核心思想

纯函数并不是要求整个程序完全没有副作用,而是:

把复杂、关键、需要大量测试的逻辑尽量写成纯函数,把不可避免的输入输出操作限制在少量简单的胶水代码中。

结合测试先行,重构可以形成稳定循环:

用测试固定当前行为
→ 提取纯逻辑
→ 隔离副作用
→ 运行测试
→ 继续小步改善结构

这样既能改善内部设计,也能持续证明外部行为没有被意外改变。