Language:Chinese VersionEnglish Version

代码迁移是软件工程中最繁琐的任务之一。从 React 类组件升级到 hooks。将代码库从 JavaScript 迁移到 TypeScript。将 API 从 REST 迁移到 gRPC。这些项目定义明确、重复性高且耗时 — 这使它们成为 AI 辅助的理想候选。

过去一年,我使用 AI 工具协助了四个主要迁移项目。两个进展顺利。一个是部分成功。一个是灾难性的,比手动迁移花费了更多时间。本文将分析哪些方法有效,哪些无效,以及如何评估 AI 辅助迁移是否适合您的项目。

2026 年的迁移格局

AI 驱动的代码迁移分为三类:

  1. LLM 辅助迁移: 使用 Claude、GPT-4 或类似模型逐个转换代码文件,并进行人工审核。这是最常见的方法。
  2. 专用迁移工具: 专门构建的工具,如 OpenRewrite(用于 Java)、ts-morph(用于 TypeScript)和 jscodeshift(用于 JavaScript),它们使用 AST 转换。
  3. 混合方法: 使用 AI 生成 AST 转换规则,然后在整个代码库中确定性地应用这些规则。

我学到的关键见解:AI 非常擅长理解意图和生成初始转换,但在处理大型代码库中的一致性方面存在困难。混合方法 — 使用 AI 编写转换规则,然后机械地应用它们 — 始终能产生最佳结果。

案例研究 1:JavaScript 到 TypeScript(成功)

项目:一个 45,000 行的 Express.js API,包含 180 个文件,零 TypeScript。目标是在严格模式下全面采用 TypeScript。

有效的方法

我将迁移分为几个阶段,使用 Claude 协助每个阶段:

阶段 1:基础设施(手动,2 小时)

// tsconfig.json - 从宽松设置开始,稍后收紧
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": false,           // 开始时宽松
    "allowJs": true,           // 与 JS 文件共存
    "outDir": "./dist",
    "rootDir": "./src",
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "declaration": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.test.ts"]
}

阶段 2:类型提取(AI 辅助,4 小时)

我将数据库模式、API 路由和示例请求/响应载荷提供给 Claude。它生成了全面的类型定义:

// types/api.ts - 由 AI 生成,然后经过审查和优化
export interface User {
  id: string;
  email: string;
  displayName: string;
  role: "admin" | "editor" | "viewer";
  createdAt: Date;
  lastLoginAt: Date | null;
  preferences: UserPreferences;
}

export interface UserPreferences {
  theme: "light" | "dark" | "system";
  emailNotifications: boolean;
  timezone: string;
}

export interface CreateUserRequest {
  email: string;
  displayName: string;
  role?: User["role"];  // 默认为 "viewer"
}

export interface PaginatedResponse<T> {
  data: T[];
  pagination: {
    page: number;
    pageSize: number;
    total: number;
    totalPages: number;
  };
}

仅从模式来看,AI 生成的类型约有 85% 是正确的。其余 15% 需要手动优化,主要集中在 JavaScript 代码中隐含的可空字段和联合类型上。

第三阶段:逐文件转换(AI 辅助,12 小时)

这是方法至关重要的阶段。我没有要求 AI 独立转换每个文件,而是提供了一个包含类型定义和风格指南的转换提示:

将此 JavaScript 文件转换为 TypeScript。规则:
1. 使用 types/api.ts 中提供的类型
2. 所有导出的函数都优先使用显式返回类型
3. 在类型确实未知的情况下使用 unknown 而不是 any
4. 保留所有现有注释
5. 不要更改任何业务逻辑
6. 在无法从上下文确定类型的地方添加 TODO 注释

要转换的文件:
[粘贴文件内容]

提示的一致性极为重要。如果没有规则 #5,AI 偶尔会”改进”业务逻辑,引入微妙的错误。如果没有规则 #6,它会使用 any 来掩盖类型模糊性,而不是标记出来供人工审查。

结果

指标
总时间 18 小时(手动估计需要 60 小时)
转换的文件数 180
AI 准确率(无需手动编辑) 72%
AI 引入的错误 3 个(在审查中发现)
剩余的 TypeScript 严格模式错误 0

案例研究 2:React 类组件到 Hooks(部分成功)

项目:一个包含 120 个组件的 React 应用程序。大约 60 个组件使用了带有生命周期方法、ref 和复杂状态管理的类语法。

有效的方法

简单的组件转换完美。AI 可以将这个:

class UserCard extends React.Component {
  constructor(props) {
    super(props);
    this.state = { expanded: false };
  }

  toggleExpand = () => {
    this.setState(prev => ({ expanded: !prev.expanded }));
  }

  render() {
    return (
      <div className="user-card">
        <h3>{this.props.user.name}</h3>
        {this.state.expanded && <UserDetails user={this.props.user} />}
        <button onClick={this.toggleExpand}>
          {this.state.expanded ? "Collapse" : "Expand"}
        </button>
      </div>
    );
  }
}

每次都正确地转换为这个:

function UserCard({ user }: { user: User }) {
  const [expanded, setExpanded] = useState(false);

  const toggleExpand = useCallback(() => {
    setExpanded(prev => !prev);
  }, []);

  return (
    <div className="user-card">
      <h3>{user.name}</h3>
      {expanded && <UserDetails user={user} />}
      <button onClick={toggleExpand}>
        {expanded ? "Collapse" : "Expand"}
      </button>
    </div>
  );
}

失败的部分

复杂的组件,包含相互作用的 componentDidMountcomponentDidUpdatecomponentWillUnmount,存在问题。AI 生成的 useEffect 钩子看起来正确,但存在微妙的依赖问题:

// AI 生成的 — 看起来正确,但有陈旧闭包错误
useEffect(() => {
  const interval = setInterval(() => {
    if (isActive) {  // 这里捕获的是 isActive 的初始值
      fetchNewData();
    }
  }, 5000);
  return () => clearInterval(interval);
}, []);  // 依赖数组中缺少 isActive

这些错误很隐蔽,因为它们能通过表面审查。组件在大多数测试场景中都能正确渲染。陈旧闭包只在用户在初始渲染后切换 isActive 时才会显现,而这可能不在现有测试的覆盖范围内。

解决方案:基于 AST 的规则

对于复杂组件,我转而编写 jscodeshift 转换器,并利用 AI 帮助编写转换器本身:

// jscodeshift 转换器:将 componentDidMount 转换为 useEffect
export default function transformer(file, api) {
  const j = api.jscodeshift;
  const root = j(file.source);

  root.find(j.MethodDefinition, {
    key: { name: "componentDidMount" }
  }).forEach(path => {
    const body = path.value.value.body;
    // 生成带有空依赖数组的 useEffect
    const useEffect = j.expressionStatement(
      j.callExpression(j.identifier("useEffect"), [
        j.arrowFunctionExpression([], body),
        j.arrayExpression([])  // 空依赖 = 仅挂载
      ])
    );
    // 在函数组件主体中替换
    j(path).replaceWith(useEffect);
  });

  return root.toSource();
}

该转换器是确定性的——它对每个匹配的组件应用相同的转换。AI 帮助我编写了转换器,但执行过程是机械的。这消除了一致性问题。

案例研究 3:REST 到 gRPC(灾难)

项目:将一个 40 个端点的 REST API 迁移到 gRPC 以实现内部服务通信,同时为外部客户端维护 REST 网关。

失败原因

迁移需要同时更改多个层:Protocol Buffer 定义、服务器实现、客户端代码和 REST 网关。AI 可以单独处理每一层,但不能跨层保持一致性。

具体问题:

  • AI 生成的 .proto 文件看起来正确,但在服务之间使用了不一致的命名约定
  • 由于上下文窗口限制,生成的服务器实现与 .proto 定义不匹配
  • gRPC 状态码和 HTTP 状态码之间的错误映射不一致
  • 流式端点生成了不正确的流控制

经过三天的 AI 辅助迁移和调试后,我放弃了 AI 生成的代码,并在五天内完成了手动迁移。手动方法虽然较慢,但产生了正确、一致的代码。

经验教训

当转换是局部的时,AI 辅助迁移有效——一次转换一个文件、一个组件或一个函数,具有明确的输入和输出类型。当转换是系统性的时,它会失败——需要跨多个文件进行协调更改,这些文件必须保持相互一致性。

评估 AI 迁移的框架

基于这些经验,这里有一个决策框架:

因素 AI 表现良好 AI 面临挑战
范围 逐文件转换 跨文件协调
验证 编译器捕获错误 仅运行时验证
模式 机械性、重复性 需要领域知识
类型 明确定义的输入/输出 隐式契约
测试 现有测试验证 无现有测试覆盖

理想的 AI 迁移候选具有三个特性:

  1. 每个文件可以独立转换
  2. 类型检查器或编译器验证输出
  3. 现有测试确认行为正确性

2026年值得使用的工具

对于 JavaScript/TypeScript 迁移:

  • ts-morph: 程序化 TypeScript AST 操作。非常适合添加类型、重命名、重构。
  • jscodeshift: Facebook 的代码修改工具包。已在数百万行代码上经过实战检验。
  • Claude/GPT-4 与结构化输出: 用于生成转换规则本身。

对于 Java 迁移:

  • OpenRewrite: 黄金标准。处理 Spring Boot 升级、Java 版本迁移和依赖更新。其配方系统是确定性的和可测试的。
  • Error Prone: Google 的静态分析工具,包含可作为微迁移使用的自动修复建议。

对于多语言项目:

  • Semgrep: 基于模式的代码转换,跨语言工作。用于安全相关迁移(修复易受攻击的模式)和 API 更改。
  • ast-grep: 一个较新的工具,结合了 AST 匹配和简单的模式语法。对于大型代码库比 Semgrep 更快。

混合工作流

最有效的方法:

  1. 使用 AI 分析代码库 并识别所有需要迁移的实例。AI 擅长分类。
  2. 使用 AI 生成转换规则(jscodeshift 转换、OpenRewrite 配方、Semgrep 规则)。让它编写规则,而不是应用规则。
  3. 机械性地应用规则 到整个代码库。这确保了一致性。
  4. 使用 AI 处理长尾问题 — 不符合机械规则的 10-15% 的案例。这些需要个别关注,AI 可以为人工审核草拟初始转换。
  5. 运行现有测试。 如果测试失败,手动调试。AI 生成的测试失败修复往往会掩盖错误而不是修复错误。

这种工作流程利用了 AI 的优势(理解模式、生成代码),同时避免了其弱点(保持文件间的一致性、处理复杂的相互依赖关系)。

接下来会发生什么

“AI 可以转换这个文件”和”AI 可以迁移这个系统”之间的差距正在缩小。更长的上下文窗口有所帮助。工具使用能力(AI 可以运行编译器并迭代修复错误)提供了更多帮助。但就目前而言,混合方法——AI 生成的规则,机械式应用——仍然是生产环境迁移最可靠的路径。

如果您正在计划一次迁移,先从 10 个文件的试点开始。测量 AI 的准确率。如果准确率在 80% 以上且只需少量审核,就可以扩大规模。如果低于 60%,则应该投入编写确定性转换规则。最糟糕的结果是一个不一致的代码库,其中一半的文件由 AI 使用略有不同的模式进行了迁移。

By

Leave a Reply

Your email address will not be published. Required fields are marked *

You missed