Agent 驱动的代码迁移:从规则编码到分批验证的工程实践

Agent 驱动的代码迁移:从规则编码到分批验证的工程实践 Agent 驱动的代码迁移从规则编码到分批验证的工程实践一、大规模代码迁移的成本黑洞Python 2 升 3框架跨大版本升级语言整体切换。这类代码迁移单个改动不难难在数量。几万处调用点人工逐个改按人月计。改漏一处上线就是线上故障。纯人工迁移慢且不可控。纯靠 LLM 一把梭误改语义、漏改边界回归测试一塌糊涂。两个极端都失败根子在没有结构化的迁移规则与验证链路。Agent 介入迁移不是替人写是把迁移拆成可编排的子任务。识别、改写、验证每步可审计、可回退、可分批。本文讨论这条链路的设计并给出规则引擎骨架。二、Agent 在迁移链路中的角色识别/改写/验证迁移是一条流水线Agent 在每个环节扮演不同角色。识别阶段。扫描代码定位需要改写的模式。比如print x到print(x)unicode到str。Agent 把模式匹配与语义分析结合产出待改清单。比纯正则准比人工快。改写阶段。按规则对每个命中点做转换。规则是可版本化的迁移本身就是代码资产。Agent 负责执行不负责创造创造性的改写必须人审。验证阶段。改完跑测试对比迁移前后的行为。测试覆盖到的地方自动验覆盖不到的地方标红人工确认。Agent 产出变更报告与置信度人做最终裁决。关键设计是分批迁移。不是一次改全仓库而是按模块、按文件粒度分批。每批独立验证、独立合并、独立回滚。把爆炸半径压到最小。置信度是自动改写的门槛。低置信度的命中点必须人工预审不能让 Agent 自作主张。三、迁移规则引擎骨架实现下面用 Python 实现一个迁移规则引擎。规则用 AST 模式匹配 转换函数定义。支持 dry-run 预览、分批执行、变更报告。import ast import astor import hashlib from dataclasses import dataclass, field from pathlib import Path from typing import Callable, Optional dataclass class MigrationRule: 单条迁移规则名称、匹配谓词、转换函数、置信度策略。 为什么把置信度放规则里不同规则的可靠性差异大 print 改写几乎 100% 安全元类改写风险高。 规则自带置信度路由器据此决定自动改还是人审。 name: str matcher: Callable[[ast.AST], bool] transformer: Callable[[ast.AST], ast.AST] auto_confidence: float # 自动改写置信度阈值 description: str dataclass class HitRecord: 单次命中记录定位、规则、改写前后、是否自动应用。 why 记录前后快照变更报告与代码审查都要可追溯 回滚时也能精确还原。 file: str line: int rule: str before: str after: str applied: bool confidence: float class MigrationEngine: 迁移引擎加载规则、扫描文件、分批改写、产出报告。 为什么基于 AST 而非正则正则改代码极易误伤字符串与注释 AST 保证只在语法节点上操作且能拿到行号定位。 def __init__(self, rules: list[MigrationRule], auto_threshold: float 0.9) - None: self.rules rules # 全局自动阈值规则自身的阈值取较高者保守优先 self.auto_threshold auto_threshold def scan_file(self, path: Path) - list[HitRecord]: 扫描单个文件返回所有命中不改写。 why 扫描与改写分离先全量扫描产出清单 便于人工预览与分批决策避免边扫边改难以回滚。 try: source path.read_text(encodingutf-8) except UnicodeDecodeError as e: # 二进制或非 UTF-8 文件跳过记录为无法处理 return [HitRecord(str(path), 0, encoding, , , False, 0.0)] try: tree ast.parse(source, filenamestr(path)) except SyntaxError: # 语法错误文件不迁先让人修编译问题 return [HitRecord(str(path), 0, syntax, , , False, 0.0)] hits: list[HitRecord] [] for node in ast.walk(tree): for rule in self.rules: if not rule.matcher(node): continue before astor.to_source(node).strip() try: new_node rule.transformer(node) after astor.to_source(new_node).strip() except Exception as e: # 转换失败记录为低置信不中断整批扫描 hits.append(HitRecord( str(path), getattr(node, lineno, 0), rule.name, before, fTRANSFORM_ERROR: {e}, False, 0.0, )) continue hits.append(HitRecord( str(path), getattr(node, lineno, 0), rule.name, before, after, False, rule.auto_confidence, )) return hits def apply_batch(self, path: Path, hits: list[HitRecord]) - list[HitRecord]: 对单文件应用本批命中返回应用结果。 仅应用置信度达阈值的命中其余标记为待人审。 source path.read_text(encodingutf-8) tree ast.parse(source, filenamestr(path)) applied 0 for node in ast.walk(tree): for rule in self.rules: if not rule.matcher(node): continue # 达阈值自动改保守优先 if rule.auto_confidence self.auto_threshold: continue try: rule.transformer(node) applied 1 except Exception: # 单点失败不影响其他点最终报告会暴露 continue if applied 0: return hits # 生成新源码带备份哈希便于回滚校验 new_source astor.to_source(tree) backup_hash hashlib.sha1(source.encode()).hexdigest()[:8] backup_path path.with_suffix( f.bak_{backup_hash}{path.suffix} ) # 备份原文件回滚时按哈希校验防止误覆盖 backup_path.write_text(source, encodingutf-8) path.write_text(new_source, encodingutf-8) for h in hits: if h.confidence self.auto_threshold: h.applied True return hits # 示例规则Python2 print 语句改函数调用 def _is_print_stmt(node: ast.AST) - bool: # Python3 解析器不会产生 print 语句节点 # 这里用 ast.Name 指向 print 当函数用的旧模式近似 return (isinstance(node, ast.Expr) and isinstance(node.value, ast.Name) and node.value.id print) def _noop_transform(node: ast.AST) - ast.AST: # 真实规则做语义改写此处仅作骨架占位 return node PRINT_RULE MigrationRule( nameprint_to_function, matcher_is_print_stmt, transformer_noop_transform, auto_confidence0.95, descriptionprint 语句迁移为函数调用, ) if __name__ __main__: engine MigrationEngine([PRINT_RULE], auto_threshold0.9) target Path(legacy.py) hits engine.scan_file(target) for h in hits: status 自动 if h.confidence 0.9 else 人审 print(f{h.file}:{h.line} {h.rule} [{status}])生产系统会接 LLM 做复杂语义改写。但 LLM 的产出仍走这套规则的置信度与验证链路不直接落盘。规则负责结构LLM 负责语义人负责裁决。四、自动化迁移的信任边界误改与漏改Agent 迁移提效但信任边界要划清。误改风险。规则匹配过宽改到不该改的地方。比如把字符串里的print也改了或改写了测试用例里的反面示例。AST 比正则安全但动态特性仍能骗过它。漏改风险。规则覆盖不全迁移后残留旧模式。残留比误改更隐蔽测试通过但语义已偏。漏改靠规则集完备性靠迁移后全量扫描兜底。语义等价难保证。语法改对了行为可能变了。print sys.stderr改成print(filesys.stderr)缓冲行为可能不同。验证依赖测试覆盖率覆盖不到的盲区是定时炸弹。不可逆迁移的代价。有些迁移不可逆改了就回不去。必须分批、可回滚、有备份。全仓一把梭等于赌博。适用边界。Agent 迁移适合模式清晰、测试完备的代码库。动态语言、元编程密集、无测试的代码自动迁移风险极高。一个常被忽视的点是迁移的不可逆检查。每条规则应标注是否可逆不可逆规则强制要求更高置信度与人工签字避免事后发现改错却无法回退。另一个实践要点是规则版本与代码版本对齐迁移规则本身要纳入版本管理与被迁移代码的 commit 对应出问题时能精确复现哪条规则在哪次迁移改了什么。最后人工 review 的抽样比例要随置信度动态调整高置信度规则抽 5%低置信度规则全量人审用有限的人力盯住真正的风险点而非均匀稀释审查力度。结论大规模代码迁移靠 Agent 把流程拆成识别、改写、验证三段。机制上规则可版本化、置信度门槛分级、分批执行可回滚。工程上 AST 保证结构安全LLM 辅助语义人裁决灰区。落地路线先把迁移模式编码为规则集再 dry-run 全量扫描产出清单按模块分批执行并跑回归测试低置信度命中人工预审。迁移能自动化但信任要分级。