尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

用LLM破解迁移疲劳:从Struts到Spring Boot的工程实践

用LLM破解迁移疲劳:从Struts到Spring Boot的工程实践 维护过五年以上老项目的工程师大概都有过这样的时刻技术债已经堆到临界点团队终于决定把旧的 Struts/SSH 工程迁到 Spring Boot或者把单体拆成微服务。真正让人崩溃的不是某一段代码改不动而是成千上万个相似又不完全相同的文件每一个都靠人工去理解、改写、验证。这种疲惫感有一个专门的说法Migration Fatigue迁移疲劳。这篇文章想聊的核心问题是为什么传统迁移这么消耗人以及 LLM大规模语言模型为什么恰好能帮我们把这部分成本大幅降下来。先说判断LLM 对迁移工作的价值不是“自动完成迁移”这么玄乎而是把迁移中最昂贵的信息处理成本——理解旧代码、转换代码模式、补齐测试——从“全靠人脑”变成“人负责判断LLM 负责生成”。迁移疲劳的本质是重复劳动而重复劳动恰恰是 LLM 的舒适区。这篇文章会从迁移疲劳的定义和成本拆解讲起用一个真实的 Struts 到 Spring Boot 迁移案例演示 LLM 辅助迁移的完整流程包括提示词设计、批量代码生成、编译验证、回归测试最后给出团队可以落地的工程实践和排查方法。适合正在做技术升级、框架替换、代码库重构的团队和开发者阅读。1. 先搞清楚Migration Fatigue 到底是什么Migration Fatigue 不是一个学术名词而是工程师对自己工作状态的一种真实描述。它指的是在技术栈升级、框架替换、数据库迁移、语言版本升级这类任务中团队因为重复性改造工作量大、回归验证负担重、知识迁移成本高产生的效率下降和倦怠感。它不是“项目快上线了熬夜很累”那种累而是一种特别隐蔽的消耗。你每天都有产出每天都有代码提交但一个模块改造完下一个模块又和上一个几乎一样只是换了表名、换了接口、换了页面。这种产出感会维持一段时间直到某一天你发现自己连续三周都在重复同一个动作只不过文件路径不同就会产生很强的疲惫感。迁移疲劳有三个明显的特征第一工作量看起来不大但实际持续时间极长。每个文件单独看都是小改动乘上几百个文件就变成了一个大项目。团队往往低估这种“常数级重复”对士气的影响。第二累的不是身体是上下文切换。每一次从“理解旧逻辑”切换到“写新代码”再切换到“查框架文档”都是在消耗注意力。而迁移恰好需要在这种切换中反复往返。第三最可怕的不是改造本身而是你不知道自己漏掉了什么。人工迁移最大的风险是遗漏。一个配置文件被漏掉、一个请求路径写错、一个事务边界被无意破坏表现通常不是立即报错而是一两周后某个只在生产环境出现的诡异问题。所以迁移疲劳不是体力问题而是信息处理问题。当一个任务需要同时处理大量“高重复、低创造”的模式转换时人脑会在几个小时内达到信息处理瓶颈而这正好是 LLM 最不疲劳、也最容易出错的反面——它对重复模式的处理成本几乎为零。2. 传统迁移为什么会累从“体力活”到“脑力活”很多人以为迁移是体力活照着旧代码写新代码逻辑不变就完事了。实际经历过一次就知道迁移是彻头彻尾的脑力活而且是最枯燥的那种。先看传统迁移通常怎么进行。第一步是盘点。翻旧代码梳理模块边界列出需要迁移的文件清单。第二步是逐文件改造。这个过程最耗时因为每个文件都要读旧逻辑理解它在整个系统里的位置然后对照目标框架的写法把它重写一遍。第三步是配置调整。框架切换通常意味着 XML、properties、yaml、包路径、依赖版本全要跟着变。第四步是编译和测试。改完一批编译一次报错修再编译再修。第五步是回归验证把系统跑起来对着功能清单一项一项点。这套流程的问题在于它把大量低价值的重复劳动和高价值的判断工作混在了一起。一个工程师需要同时扮演三个角色旧框架的考古学家、新框架的熟练工、业务逻辑的守门员。三件事同时做效率必然低。如果看本质传统迁移的累可以拆成三个底层原因。第一模式数量大。翻新框架不是把旧代码逐行翻译而是在理解旧代码语义的基础上把旧框架的表达模式替换成新框架的表达模式。一个系统的代码量动辄十万行起步模式再简单放大到这个规模都是巨大的信息处理量。第二上下文容易丢失。老项目的逻辑往往不是写在代码注释里而是分散在多层调用、配置文件、消息队列主题和数据库表结构里。任何人接手都是一个重新考古的过程。你在迁移时表面上在写代码实际上在重建一套几乎没人完整掌握的上下文。第三验证基本靠人。框架切换后的正确性验证最理想的是靠自动化测试兜底。但很多老项目恰恰没有足够的测试覆盖于是验证只能靠手工点页面、看日志、猜行为。人工验证又累又不可靠这就让迁移变成一个高风险、低成就感的长周期任务。传统工具也不是没有尝试解决。代码生成器、脚手架、重构插件能帮上忙但它们在“理解旧代码语义”这件事上几乎没有能力。它们能做模式套壳做不了语义理解。而 LLM 恰好补上了这一环。3. LLM 为什么刚好能帮上忙从“翻译工具”到“迁移助手”LLM 在迁移这个场景里真正值钱的能力不是会写代码而是能同时做三件事理解旧代码的语义、识别代码间的结构关系、按照目标框架的表达方式生成等价的新代码。这三件事恰好覆盖了迁移过程中最耗人的部分。想想看一个 Struts Action 迁移到 Spring Boot Controller本质是什么是语义保持不变、但表达范式变了。旧代码通过 ActionMapping 和 ActionForm 来处理请求新代码通过注解和参数绑定来处理请求。框架不同但“接收用户请求、调用服务层、返回结果”这个语义完全一致。LLM 的价值就在于它读得懂这两套表达方式并且能完成语义等价的转换。有人会问这不就是代码翻译吗翻译工具翻译的是语法LLM 翻译的是语义。语法翻译只要规则准确就够了但迁移旧代码时旧代码里经常存在不规范的写法、隐式的全局状态、散落的配置。这些是需要靠上下文推断才能处理的不是规则匹配能解决的。还有一层很多人忽略了LLM 在迁移中的真正优势是“上下文压缩”。如果让一个人去理解一个老模块需要读几十个文件才能把调用关系理清。但如果能把关键代码和上下文喂给 LLM它可以在很短的时间内给出一个整体认知并且基于这个认知批量产出新代码。这相当于给人配了一个“一天能读完整个项目”的同事。不过这里必须澄清一个重大误区LLM 不能替代迁移决策也不能替代人工验证。它擅长的是把“从旧代码生成新代码”这个环节做到 80%剩下 20% 的业务判断、边界处理、兼容性验证仍然需要人来完成。真正靠谱的 LLM 辅助迁移不是让 LLM 一键生成整个项目而是让人和 LLM 各司其职人负责拆解任务、制定规则、审查结果LLM 负责批量处理那些重复度最高、最消耗精力的代码转换。这种分工带来的收益是直接可量化的一个原本需要三天的模块迁移可能缩短到半天一个原本需要逐文件改写的环节可能变成“生成一批、审查一批、修正一批”的流水线。4. 一个真实迁移场景Struts 到 Spring Boot 的代码改造理论说再多不如看一段真实场景。假设现在有一个维护了多年的 Struts 项目团队决定往 Spring Boot 迁移。模块很多我们先看最典型的用户管理模块。旧代码大概长这样// 迁移前Struts Action public class UserAction extends Action { private UserService userService; Override public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { String action request.getParameter(action); UserForm userForm (UserForm) form; if (list.equals(action)) { ListUser users userService.findAll(); request.setAttribute(users, users); return mapping.findForward(userList); } else if (save.equals(action)) { userService.save(userForm.toUser()); return mapping.findForward(userList); } return mapping.findForward(error); } }这段代码在整个老项目里可能有几十个类似的 Action。每个都是这种套路从 request 拿参数调 service把结果塞进 request返回一个逻辑视图名。人工迁移几十个这样的文件最大的问题不是看不懂而是看完一个下一个还是同一个套路但每个文件名和业务方法又不一样不能直接复制粘贴。迁移到 Spring Boot 之后同一个模块的写法变成了这样// 迁移后Spring Boot Controller RestController RequestMapping(/user) public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService userService; } GetMapping(/list) public ListUser list() { return userService.findAll(); } PostMapping(/save) public void save(RequestBody UserForm userForm) { userService.save(userForm.toUser()); } }对比一下这两段代码你会发现业务逻辑完全没变变的只是框架的表达方式。Struts 用 Action 继承、ActionMapping、ActionForm 这些老一套机制Spring Boot 用注解、构造器注入、声明式路由。对 LLM 来说这种转换正好落在它最强的能力范围内它理解旧代码在做什么也知道目标框架应该怎么写剩下就是生成。但这里有一个容易踩坑的地方不能让 LLM 只对着一个 Action 文件“翻译”那样生成的代码可能不完整因为旧代码里往往引用了你还没给它的类。比如StrutsConfig.xml里定义了userList转向哪个 JSP 页面validation.xml里定义了表单校验规则这些上下文不给到 LLM它就很难生成“真正能编译通过”的代码。所以LLM 辅助迁移的第一步不是写提示词而是整理上下文。把旧模块涉及的 Action 类、Form 类、Struts 配置文件、相关 Service 接口、目标框架的规范文档等内容组织好一次性交给 LLM才能得到高质量的结果。5. LLM 辅助迁移的完整实操提示词、批量生成与验证理解了上面的场景接下来看具体怎么做。下面给出一套可以直接落地尝试的流程以 Struts 到 Spring Boot 迁移为例。5.1 第一步设计迁移专用的提示词模板提示词是 LLM 辅助迁移效果好坏的分水岭。写“帮我迁移这段代码”是拿不到好结果的好的迁移提示词要明确四点角色目标、输入上下文、转换规则、输出要求。下面是一个可复制的提示词模板放在prompts/struts-to-springboot.md文件中你是一名资深 Java 工程师正在帮助团队把旧版 Struts 项目迁移到 Spring Boot。 要求如下 1. 保持原有业务逻辑完全不变不要主动重构除非有明确的迁移必要性 2. 原有通过 request.getParameter 获取的参数转换为方法参数或 RequestBody 3. 原有 request.setAttribute 设置的数据改为 Controller 方法直接返回 4. 原有 mapping.findForward 返回的页面地址迁移为 Spring Boot 的视图名称 5. 使用构造函数注入不要使用字段注入 6. 代码风格遵循 Spring Boot 官方推荐规范 7. 如果旧代码中有不合理的写法先保留并在注释中说明不要自作主张修改。 输入上下文 - Struts Action 源码见下方 - 对应的 Form 类见下方 - StrutsConfig.xml 中的路由配置见下方 输出要求 - 输出 Spring Boot Controller 完整源码 - 输出对应的 Yaml 配置片段如果涉及路由或参数配置 - 在注释中说明迁移后的每个方法与旧方法的对应关系。你会发现这个提示词里最关键的不是“帮我写代码”而是“输入上下文”和“转换规则”。上下文决定了 LLM 能不能理解迁移对象转换规则决定了生成结果符不符合目标工程规范。5.2 第二步把旧模块上下文喂给 LLM现实中的老项目不会像上面那个 Action 那么干净通常还有 Struts 配置文件。提供足够的上下文是减少后续返工的最直接方式。举个例子迁移前需要把这样一段配置一起提供给 LLM!-- struts-config.xml 中的配置片段 -- action path/user typecom.example.action.UserAction nameuserForm scoperequest forward nameuserList path/pages/user/list.jsp/ forward nameerror path/pages/error.jsp/ /action这段配置告诉 LLM用户模块访问路径是/user表单类是userForm成功转到用户列表页失败转到错误页。有了这些上下文LLM 生成的 Controller 就不只覆盖了“业务方法”还覆盖了路由和页面跳转关系。5.3 第三步批量生成并人工审查如果模块很多不建议让 LLM 一次迁移整个项目而是按模块逐个迁移。每个模块跑一轮“上下文输入、批量生成、人工审查”。人工审查的重点有三个第一检查业务逻辑是否被改变。这是迁移的底线。LLM 生成代码时偶尔会丢掉边界条件比如一个if判断、一个状态字段的置空操作。第二检查错误处理是否被简化。很多老代码的异常处理比较啰嗦LLM 的生成风格会偏现代、偏简化容易把原来的容错逻辑“优化”掉了而迁移要的是等价不是优化。第三检查依赖是否都存在。LLM 生成代码时假定它用到的类都存在但你实际工程里可能没有这个依赖。所以生成完必须先编译让编译器来验证。5.4 第四步写一个自动验证脚本迁移代码能不能合并到主干不能靠眼睛看要靠编译和测试。用一个 Shell 脚本把验证过程固定下来比如scripts/verify-migration.sh#!/bin/bash # 迁移验证脚本编译新工程并运行测试 # 用法./scripts/verify-migration.sh set -e echo 1. 编译新工程 mvn -q clean compile if [ $? -ne 0 ]; then echo 编译失败请检查迁移后的代码 exit 1 fi echo 2. 运行单元测试 mvn -q test if [ $? -ne 0 ]; then echo 测试失败请定位失败的用例 exit 1 fi echo 3. 生成测试报告 mvn surefire-report:report-only echo 迁移验证完成脚本本身不复杂但把它固化成“每次迁移完就跑一遍”的动作能挡住大部分低级错误。编译错误是最好修的真正的风险藏在那些编译能过、测试也过了但业务语义已经改变的情况里。5.5 第五步人工回归但聚焦在高风险区域编译和自动化测试过了之后还需要人工回归。但这里建议不要把时间花在均匀地点击所有页面上而是聚焦在三类高风险区域事务边界、状态流转、外部接口调用。这三类问题在迁移中最容易“静默出错”要把有限的人工精力投在这里。6. 迁移中千万别让 LLM 替你做的三件事LLM 辅助迁移能力很强但边界同样清晰。如果使用不当轻则返工重则引入生产故障。下面三件事务必让 LLM 靠边站。第一不要直接拿 LLM 的输出合并到主干。这不是不信任 LLM而是迁移场景的失败模式决定了它不能跳过人工。LLM 的输出质量严重依赖上下文完整度而老项目里的上下文通常是不完整的。生成结果必须经过编译、测试、审查三个环节才能进入主干。第二不要让 LLM 决定迁移边界。哪些模块本期迁移、哪些模块留在老系统、哪些逻辑要趁迁移一起重写这是架构决策必须由熟悉业务和技术债情况的工程师定。LLM 不知道你们团队的下个迭代目标也不知道数据库表不能随便动的历史原因。第三不要用 LLM 生成“一次性抛弃”的代码。有团队用 LLM 辅助迁移时只把生成结果当成草稿然后手工重写。这种用法白白浪费了 LLM 的核心价值。正确姿势是把 LLM 当成流水线上的熟练工给它稳定、可复用的提示词模板让它批量产出可用代码人工把精力放在审查和修正上而不是重写一遍。此外特别提醒数据库迁移、权限配置迁移、生产环境数据回填这类任务LLM 只能辅助生成 SQL 或配置文件草稿绝对不能直接在生产环境执行。任何涉及生产环境的变更都必须走评审、测试、审批、回滚预案流程。7. LLM 辅助迁移常见问题与排查思路在实际使用中团队常遇到的几个问题可以汇总成一张排查表遇到问题时先对照一下问题现象可能原因排查方式解决方案生成的代码编译不过依赖类缺失或版本不匹配查看编译日志检查生成的 import 和依赖树在提示词中补充目标工程依赖清单先补依赖再重新生成生成的结果和旧逻辑不一致上下文不足LLM 误解了业务规则对比生成的代码与旧代码特别是边界条件和异常分支补充相关配置文件、调用方代码并在提示词中明确“保持逻辑不变”输出格式不规范无法直接接入工程提示词没有给出输出规范检查提示词中的输出要求是否明确在提示词中给出目标代码的风格示例和结构要求一次给太多文件结果非常混乱输入超出了模型有效处理范围分批输入按模块组织上下文每轮只迁移一个模块前后轮次之间保持上下文衔接测试通过但运行时有诡异问题事务传播行为或代理机制差异检查事务注解、代理类、异步调用补充事务相关提示规则逐类排查框架差异团队使用方式不统一效果忽好忽坏没有沉淀统一的提示词模板检查提示词是否被每个人随意修改将提示词模板纳入版本控制统一迁移流程这六个问题基本覆盖了团队第一次导入 LLM 辅助迁移时遇到的主要障碍。前三个是技术问题后三个是流程和协作问题。后者比前者更值得重视因为流程不统一LLM 的输出质量就没法稳定也就无法评估这套方案到底值不值得继续投入。8. 把 LLM 迁移能力固化到工程流程最佳实践如果团队确定要用 LLM 辅助迁移建议不要把它当成“临时用的 AI 工具”而是当成工程流程的一个环节来建设。下面是几条可以直接落地的实践。第一把提示词当成代码来管理。提示词模板要进 Git 仓库和代码一起维护版本。每次因为效果不好调整提示词都要记录修改原因。这样迭代几轮后团队的迁移提示词本身就是一份价值极高的知识资产。第二建立迁移知识库。迁移过程中遇到的典型问题、旧框架的坑、目标框架的注意事项都值得结构化记录。可以把这些内容整理成工程知识文档也可以借助 LLM Wiki 这类工具搭配 Obsidian 之类的笔记工具搭建团队可检索的迁移知识库让后续同类迁移不用从头探路。第三坚持小步迁移持续集成。无论有没有 LLM迁移都忌讳“憋大招”。用 LLM 批量生成后按模块合并、按模块验证让每次改动都处于可发布状态。不出两周团队就能建立信心迁移疲劳感会明显下降。第四考虑用 LLM Agent 编排迁移流水线。如果迁移任务足够多可以在 LLM 之上封装一层 Agent让编排框架自动完成“读取模块清单、拉取上下文、调用模型、生成代码、跑编译脚本、汇报结果”的流程。这样每次迁移的执行步骤可控、可审计、可追溯。不过这一步需要一定的工程投入建议团队在把阶段成果跑顺之后再考虑。第五针对特有框架做微调。如果公司内部有大量特有的老框架代码通用模型的效果可能不够理想。可以收集一批“旧代码-新代码”的迁移样例对 LLM 做轻度微调fine-tune让模型更适应团队特有的代码风格和框架模式。这一步适合已经验证过 LLM 迁移价值、且迁移任务规模很大的团队不建议一开始就做。第六明确安全边界。迁移任务涉及的数据、代码、配置都属于内部信息。使用外部 LLM 服务时要确认敏感信息是否会被用于模型训练不合适的场景下优先选择私有化部署或本地化方案。同时生成代码即使看起来没问题也要由有权限的工程师审查后才能合入。9. 从一次迁移开始给你的第一个可执行建议如果你所在团队正在酝酿一次技术迁移与其先买一个昂贵的新工具不如先用一个最小实验验证 LLM 在这个场景的真实效果。具体做法是找一个重复度最高、风险最低的模块整理好上下文套用上面给的提示词模板生成一版迁移代码跑一遍编译和测试让团队里最有经验的工程师做一次代码审查看看到底能省多少时间、埋了多少雷。这一步做下来答案比任何理论分析都准确。如果效果好再把流程扩展成一条标准化的迁移流水线把提示词、验证脚本、代码规范都固化到 CI 里。如果效果不理想也基本能判断出瓶颈在哪里是上下文整理不够、还是模型能力不足、还是流程没跑对。迁移疲劳最怕的不是“慢”而是“闷头重复”。LLM 改变不了迁移的复杂度但它能把最消耗人的重复劳动接过去让工程师把注意力留在真正需要判断的地方。从这个角度看它值得每个正在跟存量代码苦苦搏斗的团队认真地试一次。
返回列表