三周重构 30 万行 C:编码 Agent 真正证明了什么

2026-09-30 18 预计阅读时间: 1 分钟
来源: infoq.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

CodeScene 公布的一项案例显示,编码 Agent 在三周内重构了约 30 万行 C 代码,Token 成本约为 4,000 美元。整个过程通过逐帧回放测试验证,Agent 还在执行过程中积累了一套针对该代码库的重构配方。

这些数字足够醒目,但更值得工程团队关注的不是“Agent 一次能改多少行”,而是:在什么验证条件下,大规模自动修改才值得信任?

30 万行是工作量指标,不是质量指标

代码行数可以描述变更规模,却无法单独证明重构成功。机械格式化、批量重命名、提取函数和重新组织模块,都可能产生很大的 diff;真正关键的是系统行为有没有保持不变,以及代码是否因此更容易继续修改。

评估这类项目时,至少要把几个维度拆开:

  • 变更范围:Agent 是遍历了 30 万行代码,还是实质性修改了 30 万行?
  • 任务类型:修改主要是规则明确的机械重构,还是涉及架构边界、并发、内存生命周期等高风险问题?
  • 验证强度:逐帧回放覆盖了多少输入、平台和异常路径?
  • 人工投入:Token 成本之外,测试框架建设、代码审查、失败归因和返工用了多少工程时间?
  • 长期收益:重构后复杂度、缺陷率、构建时间和后续变更成本是否下降?

因此,“30 万行、三周、4,000 美元”更像吞吐量数据,而不是完整的投资回报结论。它证明了 Agent 可以在强约束环境中执行大规模修改,但不能仅凭这些数字推导出修改是自主完成的、适用于所有 C 项目,或者总成本只有 Token 账单。

回放测试不是辅助工具,而是这套系统的核心

这个案例最重要的工程细节,是逐帧回放验证。对于图形、视频、游戏、仿真或其他按帧推进的程序,回放框架可以把历史输入重新送入基线版本和候选版本,再比较每一帧的输出。

这相当于为 Agent 建立一条可执行的边界:代码内部可以改变,但可观察行为必须保持一致。Agent 的价值与验证系统的能力是相乘关系,而不是彼此独立:

可接受的自动化变更规模 ≈ Agent 的修改能力 × 验证反馈的速度与覆盖率

不过,逐帧一致也有边界。它通常无法自动证明:

  • 未被录制的输入仍然正确;
  • 不同编译器、CPU 架构或操作系统上的行为一致;
  • 内存安全、数据竞争和未定义行为已经消失;
  • 性能、延迟和峰值内存没有退化;
  • 新结构确实比旧结构更容易维护。

稳健的流水线应把回放测试与单元测试、模糊测试、Sanitizer、静态分析和性能基准组合起来,而不是把一次视觉或字节级对比当成全部证明。

可以这样实践:给 C 重构建立最小回放闸门

下面是一个可复制的简化示例。它并非案例中的实际测试框架,而是演示如何让基线程序与重构后的程序处理同一组“帧”,并逐字节比较输出。

先创建基线实现 baseline.c:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
    if (argc != 2) return 2;
    int frame = atoi(argv[1]);

    printf("P2\n16 16\n255\n");
    for (int y = 0; y < 16; y++) {
        for (int x = 0; x < 16; x++) {
            int pixel = (x * 17 + y * 13 + frame * 7) % 256;
            printf("%d%c", pixel, x == 15 ? '\n' : ' ');
        }
    }
    return 0;
}

再创建经过函数提取的候选实现 candidate.c:

#include <stdio.h>
#include <stdlib.h>

static int pixel_at(int x, int y, int frame) {
    return (x * 17 + y * 13 + frame * 7) % 256;
}

static void render_frame(int frame) {
    puts("P2");
    puts("16 16");
    puts("255");

    for (int y = 0; y < 16; y++) {
        for (int x = 0; x < 16; x++) {
            printf("%d%c", pixel_at(x, y, frame), x == 15 ? '\n' : ' ');
        }
    }
}

int main(int argc, char **argv) {
    if (argc != 2) return 2;
    render_frame(atoi(argv[1]));
    return 0;
}

创建 replay.py,对 120 帧执行确定性回放:

from pathlib import Path
import hashlib
import subprocess
import sys

FRAMES = range(120)


def run(binary: str, frame: int) -> bytes:
    return subprocess.check_output([binary, str(frame)], timeout=2)


for frame in FRAMES:
    expected = run("./baseline", frame)
    actual = run("./candidate", frame)

    if expected != actual:
        Path("expected.pgm").write_bytes(expected)
        Path("actual.pgm").write_bytes(actual)
        print(f"Mismatch at frame {frame}", file=sys.stderr)
        sys.exit(1)

    digest = hashlib.sha256(actual).hexdigest()[:12]
    print(f"frame={frame:03d} sha256={digest}")

print("Replay passed: all frames are byte-identical")

编译并运行:

cc -std=c11 -O2 -Wall -Wextra -Werror baseline.c -o baseline
cc -std=c11 -O2 -Wall -Wextra -Werror candidate.c -o candidate
python3 replay.py

# 再补一轮内存与未定义行为检查
cc -std=c11 -O1 -g -fsanitize=address,undefined candidate.c -o candidate-asan
for frame in 0 1 2 30 60 119; do
  ./candidate-asan "$frame" >/dev/null
done

在真实项目中,可以把帧号替换为录制的输入事件、网络包、文件或随机种子。若输出含时间戳、线程调度结果或浮点抖动,则应先定义规范化规则和容差,避免测试既频繁误报,又悄悄放过真正的行为变化。

Agent 的“配方库”可能比单次重构更有价值

案例中另一个值得关注的产物,是 Agent 逐步形成了代码库专属的操作配方。这类配方可以记录:

  • 哪些目录允许修改,哪些模块必须保持 ABI 稳定;
  • 某种代码异味应使用什么重构步骤;
  • 每类修改完成后必须运行哪些测试;
  • 哪些宏、所有权约定或错误处理模式不能机械替换;
  • 失败后如何缩小 diff、恢复现场并重新执行。

可以把这些规则放入版本库,例如:

name: extract-render-helper
scope:
  include: ["src/render/**/*.c"]
  exclude: ["src/render/compat/**"]
constraints:
  - "Do not change exported symbols"
  - "Preserve integer arithmetic and evaluation order"
  - "Keep each patch below 500 changed lines"
validation:
  - "make replay-test"
  - "make test-asan"
  - "make benchmark-render"
rollback_on:
  - "any frame mismatch"
  - "performance regression above 3 percent"

这段 YAML 只是可采用的项目约定,并非特定 Agent 的标准格式。关键是把提示词、修改边界和验证命令变成可审查、可版本化的工程资产,而不是留在一次性的聊天记录里。

采用前应检查什么

这项案例较有说服力地说明:当任务可以拆成重复、局部且可验证的步骤时,编码 Agent 能把大型遗留代码重构从手工作业转变为受控流水线。但它没有证明验证基础薄弱的项目也能获得同样结果。

开始类似尝试时,建议按以下顺序推进:

  1. 先选择行为稳定、输入可回放、失败可回滚的模块。
  2. 建立基线,包括测试通过率、性能、二进制大小和静态分析结果。
  3. 将每次变更限制为小批次,不要让 Agent 一次生成无法审查的巨型补丁。
  4. 把 Token、人工审查、测试基础设施和返工时间一起计入成本。
  5. 保存有效的重构配方,并记录失败配方及其触发条件。
  6. 用缺陷率、复杂度和后续修改时间衡量长期价值,而不只统计修改行数。

真正可复制的结论不是“Agent 能在三周内改 30 万行代码”,而是“足够强的验证闭环,可以让 Agent 安全地尝试远超人工节奏的大规模变更”。在这类系统里,Agent 是执行器,回放框架和工程约束才是信任的来源。


相关推荐