大模型辅助技术栈迁移:如何让20万行代码行为分毫不差
这次我们来看一个很有意思的技术命题把 20 万行存量代码完整迁移到另一套技术栈并且要求行为分毫不差。题目里的主角不是某个重构框架也不是某种自动化迁移工具而是 Claude 和 GPT 这类大模型辅助编码工具。结果很直接如果你把整个仓库直接丢给模型让它们“一次性重写”它们真的会懵。这里的“懵”不是说模型写不出代码而是它们很难在有限上下文里同时把握住全局状态、边界条件、错误处理、日志文本、数据格式这些隐性行为。语法层面的翻译很容易行为层面的还原才是难点。对于金融系统、交易链路、协议解析、计费模块这类“错了就要出事故”的老代码迁移失败的代价远高于重写本身。这篇文章会围绕这样一个场景展开存量代码规模大约 20 万行目标技术栈已经定好对外接口和业务行为不能变Claude / GPT 负责辅助产出代码人工负责设计基线、验证行为和兜底审查。我会拆解为什么整仓重写会失败、正确做法是什么、行为验证怎么做、批量迁移怎么管理以及遇到问题怎么排查。如果你正在做技术栈迁移或者想在真实项目里用大模型辅助重构这篇文章可以直接当作落地参考。1. 核心问题速览先把这次要做的事拆成一张表后面所有步骤都围绕这张表展开。问题项说明迁移对象约 20 万行存量代码包含业务逻辑、对外接口、数据读写、日志与异常处理迁移目标换到另一套技术栈例如 Java 到 Go、Python 到 Java、Node.js 到 Rust 等核心要求行为分毫不差同一输入得到同一输出接口协议、数据格式、错误码、时序行为保持一致AI 辅助工具Claude / GPT 系列模型负责代码生成、解释片段、辅助审查、生成测试样例主要风险行为漂移、隐性依赖、上下文窗口溢出、测试覆盖不足、批量任务中断正确路径模块拆分、行为基线先行、小步迁移、差分验证、灰度切换错误路径整仓直接重写、只看编译通过、只对比核心路径、没有历史行为基线这张表明确了整个工程的边界模型是辅助角色行为基线是验收标准模块拆分是执行单元。2. 为什么“整仓重写”会让 Claude / GPT 直接懵掉先说结论把 20 万行代码一次性塞进对话要求模型“换个技术栈重写且行为完全一致”这件事本身就不符合大模型当前的能力边界。问题不出在模型智商而出在任务设计。2.1 上下文不是越大越好大模型对单次输入的上下文长度有限即使能支持很长的上下文窗口真正有效的信息密度也会随长度下降。20 万行代码全部丢进去模型真正能“专注”的往往是最近出现的内容。业务模块之间的调用关系、共享的配置项、藏在某个古老工具类里的全局状态这些信息要么被截断要么被淹没在大量无关代码里。结果就是模型生成的代码“看起来像”但一跑就漏行为。更好的做法是让模型一次只处理一个模块把模块的入口、出口、依赖接口、已知边界条件写成精确说明而不是把整个仓库当成一个黑盒。2.2 行为往往藏在边界里技术栈迁移最容易出现的问题不是主流程跑不通而是边界行为对不上。旧系统可能依赖了 Java 的默认字符集、C 语言的整数溢出规则、Python 的浮点精度、某个第三方库的历史 bug甚至依赖操作系统的时区设置。这些行为没有写进需求文档只存在于代码运行结果里。如果只让模型“照着功能重写”它基本不可能主动复刻这些细节。唯一可靠的解法是先建立行为基线用真实的输入输出把这些隐性行为固定下来再让模型在基线约束内生成新代码。2.3 模型擅长生成代码不擅长替你验证行为Claude 和 GPT 在生成代码、解释代码、补测试样例方面效率确实高但“行为是否完全一致”是一个需要执行程序才能回答的问题。模型无法可靠地告诉你“我生成的代码和旧系统在 1000 条边界输入下表现完全相同”它只能给你一个合理的推测。所以正确分工是模型负责“从模块说明生成新代码”测试框架和差分脚本负责“验证新旧行为是否一致”人工负责“设计测试覆盖和审查关键逻辑”。谁也别替谁干活。3. 适用场景与使用边界3.1 哪些场景需要“行为分毫不差”这种要求通常出现在存量系统迁移里典型的例子包括交易系统或账务系统历史订单、对账逻辑、冲正规则必须完全一致通信协议栈或报文解析模块字段偏移、字节序、错误码不能变对接外部系统的 API 服务请求参数、响应结构、状态码一点都不能动报表或批处理任务输出文件格式、字段顺序、汇总口径必须和旧版一致长期运行的定时任务日志格式、埋点数据、重试策略影响下游监控。这类场景里业务逻辑本身不一定复杂但历史积累的隐藏约束非常多。新代码“功能正确”还不够必须和旧代码“行为等价”。3.2 哪些场景不适合让 AI 直接改代码如果旧系统没有测试、没有文档、没有可运行的构建环境第一步要做的是补基线而不是让模型开写。模型在没有行为约束的情况下自由发挥只会产出“看起来合理但没人敢上线”的新代码。也不要让模型直接处理密钥、证书、生产环境地址、用户隐私数据。喂给外部模型 API 的代码片段必须经过脱敏凡是涉及敏感信息的仓库最好使用本地模型或者先做字段替换。3.3 合规与数据安全边界使用 Claude / GPT 辅助编码时要注意以下几点代码仓库如果是公司私有资产上传前需要确认数据合规要求不要直接把包含 API Key、数据库密码、真实手机号、身份证号的测试数据放进 prompt模型生成的代码默认只是建议版权归属和引入依赖的许可证需要人工审查对外接口、协议、密钥相关的代码迁移后要重新走安全评审。4. 迁移前置先建立行为基线这一步是整个工程的地基优先级高于任何模型调用。4.1 代码盘点和模块划分先把 20 万行代码按模块拆分而不是按文件数量切。划分原则是每个模块有清晰的入口和出口可以独立编译、独立测试、独立描述行为。拆完之后确认模块之间的依赖关系优先迁移被依赖最少的叶子模块再逐层向上。可以用如下清单做盘点# 统计代码行数按目录汇总 find src -type f \( -name *.java -o -name *.go \) | xargs wc -l | sort -rn # 找到模块入口和对外接口 grep -r public interface\|func\|def src/ --include*.java --include*.go | head # 梳理模块依赖关系 # 这里建议用 IDE 或静态分析工具导出调用关系图不需要一次把所有依赖关系都画出来但至少要画出被迁移模块的上下游。没有依赖图模型生成代码时很难判断该保留哪些接口。4.2 建立行为基线行为基线不是“跑一遍主流程截图”而是可重复执行的验证资产。至少要做三件事第一整理现有测试用例梳理出哪些是单元测试、哪些是集成测试、哪些是手工回归用例。第二为没有测试的核心逻辑补充“输入输出样本”也就是 golden fixture。每一条样本包含输入、期望输出、关键中间状态。跑旧系统时把这些 fixture 自动归档迁移后一键对拍。第三记录非功能性行为包括返回码、日志格式、超时时间、重试次数、数据排序方式、时区处理方式。这一步直接决定后面验证阶段的效果。基线越完整Claude / GPT 的行为约束越清晰。4.3 搭建新旧两套可运行环境迁移期间需要同时运行旧系统和目标系统所以部署环境必须支持两套并存。建议准备migration: source_repo: ./legacy_codebase target_repo: ./new_codebase languages: from: Java 8 to: Go 1.21 modules: - name: billing path: billing depends_on: [] - name: account path: account depends_on: [billing] verify: golden_dir: ./golden diff_timeout: 30 batch: concurrency: 2 retries: 3这份配置是给整个迁移工程用的模型只负责其中一个环节也就是“根据模块说明生成目标代码”。5. 模块级迁移Claude / GPT 的正确打开方式5.1 给模型的输入结构每次调用 Claude / GPT不要给太多代码而是给一个结构化的任务包。下面这个 prompt 模板可以直接改写成项目里的标准化 prompt你在帮助我把一个 Java 存量模块迁移到 Go要求行为完全一致。 模块名称billing 模块边界 - 对外只暴露 Calc(OrderDTO) ReturnDTO 这一个方法 - 只依赖 account.AccountService - 不允许依赖其他 Java 内部类 关键行为要求 1. 输入金额单位是“分”输出单位是“元”保留两位小数 2. 折扣比例超过 0.3 时错误码返回 E1001message 必须是 discount too large 3. 空订单号直接返回 nil 4. 外部异常一律包装成 BizError不抛出运行时异常 5. 日志时间统一使用 UTC格式为 2006-01-02T15:04:05Z 请生成 Go 代码 - 文件位于 internal/billing/service.go - 方法签名已由接口定义给出不要改动 - 先列出你识别出的边界条件再写代码 注意如果某些行为无法从描述中确定请列出具体问题不要猜。这个 prompt 的设计思路是把模块边界、行为约束、输出格式、不确定点全部说清楚。模型一旦开始猜测接口细节就需要停下来提问或明确标注风险点这比让它多写一堆代码更有价值。5.2 人工审查清单模型生成代码后进入人工审查环节。重点看这些位置对外接口签名是否和既定接口一致错误码和错误信息是否逐字匹配数值计算是否保留了旧系统的精度规则并发和锁的粒度是否一致日志、埋点、Metrics 是否被原样保留外部依赖是否新增了未授权的第三方库。审查不一定要逐行读但至少要做“差异审查”把模型生成的代码和旧代码的关键逻辑按模块对照用 diff 辅助找出模型自主发挥的地方。5.3 循环迭代的节奏一次生成大概率不过。更现实的节奏是模型生成 - 编译 - 单测 - 差分对拍 - 把失败信息回投给模型 - 模型修复 - 再验证。把每一次验证失败的错误信息、测试输入、期望输出、实际输出打包给模型模型往往能准确修掉问题而不是笼统地重新生成。6. 行为验证把“分毫不差”翻译成测试断言迁移是否成功的唯一标准是新系统在相同输入下产生和旧系统完全相同的行为。这个标准必须用代码执行来验证。6.1 差分测试差分测试的核心思路是同一份输入分别调用旧系统和新系统比较输出。下面是一个通用脚本模板实际项目里需要替换两条调用路径import json import subprocess def run_legacy(input_data: dict) - dict: result subprocess.run( [java, -jar, legacy.jar], inputjson.dumps(input_data), capture_outputTrue, textTrue, timeout30 ) return json.loads(result.stdout) def run_new(input_data: dict) - dict: result subprocess.run( [./new_billing], inputjson.dumps(input_data), capture_outputTrue, textTrue, timeout30 ) return json.loads(result.stdout) def compare(input_data: dict) - None: old run_legacy(input_data) new run_new(input_data) if old ! new: print(fDIFF input{input_data}) print(flegacy{old}) print(fnew{new}) # 读取 golden fixtures 批量对拍 with open(golden/billing_fixtures.json, r, encodingutf-8) as f: fixtures json.load(f) for case in fixtures: compare(case)这里有一个关键细节float 精度、字段顺序、大小写、时区差异都可能导致“看起来一样但比较失败”。所以 compare 函数里建议先做规范化例如统一 dict 的 key 顺序、统一时间格式、对浮点数做小数点后 10 位的近似比较。6.2 golden 回归差分测试是“新旧对打”golden 回归是“新打存档”。把旧系统的输出保存为 golden 文件迁移后新系统的输出直接和 golden 文件比对。推荐两者都做有旧环境时优先做差分测试省事且覆盖全旧环境下线后用 golden regression 继续保护行为不被后续改动破坏。# 运行 golden 测试输出差异 ./new_billing --input ./golden/billing_case_001.json --expected ./golden/billing_case_001.out6.3 边界用例优先级不要只测正常路径。下面这些边界条件必须出现在验证清单里空对象、空字符串、空列表数值边界0、负数、最大值、最小值、超精度值重复请求、并发请求、超时请求异常输入错误格式、未知枚举、指数级数值排序与分页多条数据处理时顺序是否一致环境相关行为时区、字符编码、换行符、浮点舍入。这些用例数量不需要特别多但覆盖面要广。每一条边界用例一旦通过都要提交进代码库里成为长期回归资产。7. 批量迁移与进度管理20 万行代码不可能一次迁移完必须设计成批量任务。这里的批量不是让模型一口气处理整个仓库而是让模型一个模块一个模块完成再由工程脚本统一管理。7.1 任务队列用一个简单的任务清单来管理模块迁移进度import time tasks [ {module: billing, status: done, verified: True}, {module: account, status: in_progress, verified: False}, {module: ledger, status: todo, depends_on: [billing, account]}, ] for task in tasks: if task[status] done and task[verified]: continue print(fprocessing: {task[module]}) # 这里调用模型 API 的代码由外部脚本负责 # 之后执行编译、单测、差分测试 # 全部通过后再更新任务状态 time.sleep(1)任务队列里至少要有模块名、依赖、状态、验证结果、负责人、备注这几列。批量迁移过程中最怕“卡在某个模块不动”所以每个模块都设置超时时间和失败重试次数。7.2 依赖顺序决定批次先迁移最底层、最稳定的模块比如工具库、数据访问层、日期计算、金额计算。这些模块行为明确、测试容易写适合让模型先练手。上层业务模块依赖下层模块的新接口迁移时就不需要同时理解新旧两套系统的接口差异。7.3 调用模型 API 的工程化设计如果用云端 Claude / GPT API 做批量辅助生成要注意这些问题接口调用有频率限制批量任务要设计请求队列避免瞬时请求过多超时和失败要自动重试重试之间加退避间隔每一次请求的 prompt 和返回结果要落盘存档方便追溯消费成本按 token 统计每个模块处理前估算一次上下文规模避免把整个仓库反复塞进对话。这里没有统一的标准参数实际限流值和计费规则要以你使用的服务商文档为准。稳妥的做法是把模型 API 调用封装成独立服务和迁移流水线解耦单独配置限流和重试。8. 成本、节奏与资源观察迁移工程同样需要观察“资源消耗”只是这里观察的不是显存和带宽而是 token 成本、耗时、人工审查时间。8.1 关键指标指标观察方式用途单模块 token 消耗调用 API 时记录 input/output token 数预判成本、优化 prompt单模块生成耗时开始调用到返回完整代码的时间评估批量任务总时长验证耗时编译、单测、差分测试的执行时间找验证瓶颈人工审查耗时每个模块 review 花费的时间判断模块拆分粒度是否合理验证失败率首轮生成通过率、修复轮数评估 prompt 质量和模块复杂度这些指标不需要一开始就精确统计但要从第一个模块就记录。有了第一批数据就能估算后续 20 万行代码的迁移总量和成本。8.2 降低成本的技巧只迁移活跃代码用覆盖率工具找出从未被调用的死代码先标记不迁移合并重复实现同一个逻辑在旧系统里出现多次迁移前先归并压缩给模型的上下文只保留模块接口和关键实现不要贴整个调用链把常见的错误修复信息整理成规范文档减少模型反复试错。9. 常见问题与排查方法迁移过程中会遇到一批典型问题下面这张表可以当作排查入口问题现象可能原因排查方式解决方案模型生成的代码编译不过依赖缺失、版本不匹配、接口签名不一致查看编译器报错检查模块依赖清单把编译错误作为 prompt 内容回投给模型或人工补齐依赖声明接口行为不一致没有先建立行为基线对比新旧系统输出补充 golden fixture建立差分测试后再让模型改代码中文乱码或字符异常字符编码不一致检查文件编码、数据库连接编码统一使用 UTF-8并在测试用例里加入中文输入样本排序结果不同依赖数据库默认排序或集合无序性对比排序列和稳定排序规则显式指定排序字段删除依赖默认排序的代码浮点结果差一位浮点精度、编译器优化、运算顺序不同输出末尾几位数值检查运算顺序使用近似断言必要时统一计算精度规则上下文窗口溢出模块拆分不够细检查每次 prompt 的 token 数继续拆分模块把公共依赖抽成独立接口文档批量任务卡住限流、网络超时、进程残留查看任务日志和 API 调用记录增加重试机制和退避间隔任务状态落盘以便断点恢复模型“自由发挥”改了接口prompt 没有强调接口约束对比输出接口和既定接口定义在 prompt 里写明“接口签名不可改动”并增加签名校验脚本排查时始终记住一个原则先确认是哪一层出了问题。是生成代码本身错了还是 prompt 给的边界不够还是验证脚本比较逻辑写错了。很多“模型写错”的问题最后发现是 prompt 没有把行为基线描述清楚。10. 最佳实践与合规建议把迁移工程跑完一轮之后总结出的最佳实践是下面这些。第一先小后大。第一次迁移不要选 20 万行里的核心交易模块而是选一个边角模块把 prompt 模板、验证脚本、任务管理流程全部跑通再扩大范围。最小可运行配置一定要保留任何一次 prompt 调整都不能破坏已验证的模块。第二新旧系统并行。新模块没有通过差分验证之前旧系统不要下线。切换用灰度方式先放流量到新系统对比线上日志和指标确认行为一致后再逐步切量。第三留全套行为日志。迁移期间所有模块的输入、输出、验证结果、人工决策都要有记录。行为一致性不只是“这次跑赢了”还要能让后续维护者在三个月后依然知道为什么保留了这个错误码。第四合规审查前置。涉及未公开业务逻辑、用户数据、核心密钥的代码不能直接丢给外部模型。先做脱敏再生成再人工审查敏感字段。模型生成的第三方依赖也要做许可证检查避免引入和公司规范冲突的开源协议。第五建立行为契约文档。每个模块迁移完成后把它的行为约束整理成一份契约包含接口定义、边界条件、错误码、日志格式。后续如果还要继续改技术栈这份契约可以直接给模型用。11. 总结与下一步回到开头那个 20 万行代码迁移的命题真正的难点不是“让 Claude / GPT 写代码”而是如何给模型一个可验证的行为边界。整仓重写会懵是因为任务超出了单次生成能够可靠控制的粒度模块级迁移、行为基线先行、差分验证兜底这套流程能让模型在它擅长的代码生成环节发挥作用同时把风险控制在可检查的范围内。如果你准备在自己的项目里启动类似迁移第一步不是打开模型对话而是先盘点代码、建立行为基线、搭好新旧两套环境。第一个模块跑通之后再让 Claude / GPT 参与生成和修复。最容易踩的坑也明确不建基线就让模型自由发挥结果只能是编译通过但行为对不上。后续可以继续扩展的方向包括把 prompt 模板和验证脚本做成统一的半自动迁移流水线接入 CI 后每次提交都自动跑差分测试也可以用大模型辅助生成边界测试用例反向补充基线的覆盖缺口。迁移本身是一次性的但留下的行为基线和验证资产会一直保护这套系统。