想象一下,你手里有几百个跑在旧架构上的业务指标,每个指标背后都堆着 5 到 10 年前写的遗留代码——文档残缺,作者早已离职。你知道必须把它们迁到新平台,但过去每一次尝试都像在翻一座山:几百个 Jira 工单、几百个工程师周,工期动辄按季度算,而且项目走到半路才发现路径不通的情况比比皆是。怎么办?
日前,ServiceTitan 首席 AI 工程师 David Stein 在 QCon 的分享中给出了一种反常识的解法:处理大规模的遗留代码问题,你不需要让 AI 变得多聪明,只需把任务分解到它能独立完成并验证的粒度,然后用一条自动化“组装线”,像玩《百战小旅鼠》那样等着验收即可。实际结果显示, 85% 的迁移工作能够由 AI 自主完成,剩余 15% 的复杂案例才需要人类工程师介入,整个项目周期从几个季度压缩到几周。本文基于该分享整理。
核心观点如下:
- 作为高级工程师或架构师,我们每天都在把大问题拆成可执行的子任务。但真正的新变化是:我们可以把时间线反转过来。
- 当你用 agent 来加速执行大型迁移项目时,验证器变得至关重要。尤其是在短时间内并行推进大量迁移任务时,你必须确信每一步都运行良好,否则很快会偏离轨道。
- 你只需要给 Cursor 一个非常简单的指令,把复杂性尽可能隐藏在验证步骤和工具背后,并确保它拥有解决问题所需的全部上下文。
- 能写代码的 Agent 带来的巨大变革不止于让单个任务跑得更快,而是让你以前所未有的速度完成那些曾经不可能的任务,从而敢于尝试新东西。
被忽视行业的 AI 落地实录
今天我想和大家聊聊软件工程里每个人最爱的话题——代码迁移。迁移就是那些大家都知道必须做的事:把一堆遗留代码从老系统搬到一个更好、更快、更强的的新系统上。通常这类项目会规划数月、一个季度甚至一年。我想讲讲在 ServiceTitan,我们如何用 AI 彻底改变这件事的玩法,让我们能以前所未有的速度进行迁移,以及这对我们意味着什么。
先给不熟悉 ServiceTitan 的朋友一点背景知识。ServiceTitan 是为承包商和技工行业打造的技术平台,覆盖住宅、商业、建筑服务、施工、电工、管道、暖通空调、屋顶、车库门……我们为之提供端到端的技术平台。技工行业是一个长期被忽视的领域,大量工作还停留在纸笔和 Excel 表格的阶段,迫切需要迁移到更高效、更数字化的方式上来,而 ServiceTitan 正在引领这场变革。
如今人人都在谈 AI 用例,全行业充斥着各种近乎噱头的展示。能真正聚焦于这些接地气、关乎核心服务的 AI 场景,真的非常棒。这包括工作价值预测,以便安排承包商,知道该把哪些人分配到哪些车辆,哪天派往哪些住宅和楼宇,从而实现效率和营收的优化——这是客户喜爱我们产品的一个极为重要的功能。还有别的场景,比如听取通话录音或语音留言。如果你经营一家技工公司,错过一个重要电话,可能就错失一个能带来大量收入的重要线索,你肯定希望 AI 不停给你的手机发提醒:“快给那个人回电话!”虽然说得有点过于简单,但这大致就是“二次机会线索”的概念。此外,我们还有语音 Agent 等产品,有一项 AI 接听服务可以帮助客户 7×24 小时全天候为顾客预约工作。以上只是我们在 ServiceTitan 构建的部分用例。
大规模迁移如同高山
不过,我今天想聊的重点并不是这些具体的 AI 产品,而是一个更普遍的挑战:当你手里有一堆跑在旧架构上的遗留代码,你知道必须动它,但你也知道动起来会非常痛苦。在座各位如果在一家公司待过超过两年,一定懂我在说什么。遗留代码就是你越积越多、存在时间越长,它就越拖慢你添加新功能、做变更的速度。
整个行业的人,或多或少都参与过某种迁移项目。如果你在领导层或参与规划,你也知道这类项目风险极高。当你决定把一大坨遗留代码迁到新平台时,你不可能只完成 25% 或 50% 就收手,在许多情况下,你必须走完全程。这个“必须走完”本身就制造了巨大的风险。你绝对不想走到半路,才发现自己卡住了,进退两难。
我先澄清一下,我说的不是那种早就被自动化解决了的迁移——比如把数据库从一种格式转到另一种格式,写个脚本一键转换就完事了。那是简单问题,早就被解决了。我讨论的是真正的硬骨头:几十万行遗留代码,其中有些是 5 到 10 年前写的,写代码的人早就不在公司了,文档可能不全,单元测试覆盖率堪忧。这种代码,我们很多人都见过。把这样的庞然大物搬进新的抽象层,简直是赫拉克勒斯式的任务。

案例分析:迁移 ServiceTitan 的报告指标
为了把问题讲具体,我接下来分享一个案例——ServiceTitan 的报告指标迁移项目。
ServiceTitan 很大一部分应用都和报告相关,我们让客户能够下载、查看、做各种复杂的运营、财务和其他业务指标的分析,涉及工单、发票、技术人员、客户等等。支撑这一切的底层代码,就是典型的遗留代码。有些技术组件接入生产数据库的方式,跟我们今天从头构建时会采用的方式完全不同,这是极为常见的困境。
但真正棘手的是规模问题。在我们这个场景里,有海量的业务指标,每一个指标背后都有一大块遗留代码在驱动,而每一块代码又可能带着一堆隐藏的依赖关系。有些依赖是架构层面的,有些是隐性的,比如对数据库表结构的隐含假设,或者对数据形态的默认依赖。要理清所有东西,需要大量时间。按照传统方式,你会看到一个庞大的项目:需要成百上千张 Jira 工单,耗费数百个工程师周的时间,这就是数个季度乃至数年的光景。
还有一个关键问题,在 AI 出现之前,迁移项目并不总能成功。在 ServiceTitan 的报告系统上,已经有过不止一次尝试,想要重构部分遗留代码并迁移到更好的架构。这个案例就源于这样一种局面:团队花了一段时间试图拆解遗留代码,将其迁移到基于数据湖仓的新架构中,过程中遇到了重重挑战,时间线一再挣扎。最糟糕的是,当你投入了几个月甚至几年,却发现项目进展和最初的预期完全不一样。
到那时你就会开始问自己:这个方向真的对吗?这种处境很糟糕,而且会让很多人在规划这类项目时彻夜难眠。在我们这个案例中,我们当时正盯着一个挣扎中的项目,一个团队在挖掘遗留代码时遇到了困难。于是我们开始用当时最前沿的编码 LLM 来审视那些代码,想看看有没有可能用不同的方式、更好的方式来推进,从而在迁移到新架构的过程中获得更好的进展,这就是我们接下来要讲的故事背景。
到了 2025 年,任何问题的默认解决方案都是:能不能直接问 AI?你有几百个指标要迁移,直接扔进 Cursor 里就行了,听起来很美好,对吧?但现实是,你不能直接让 Cursor 或 Claude Code 把你的整个遗留代码库迁移到你描述的新架构中。我确实试过,结果非常糟糕。原因和一个工程师无法独自完成这件事是一样的——这个任务太大了。
就算你是非常优秀的工程师,你也不可能直接去修复几十万行代码,把它们全部迁移到最新的架构里去。你可能会取得一些初步进展,但很快你就会发现自己面对一座大山,根本无法理解所有遗留代码背后的上下文。队友离职了,文档缺失了,你只能一点一点地挖掘,进展极其缓慢。
而换成 LLM 来写代码呢?它会幻觉,会误解任务,会发明新的指标而不是迁移旧的,做了五个然后说“剩下的我会这么做:”就停了,而那做出来的五个还是错的。哪怕用上最先进的模型,你看到的全是这种问题。
所以关键洞察是什么?把问题分解成可以逐个验证的小步骤。这不是什么新想法,作为高级工程师或架构师,我们每天都在做这件事:把一个大问题拆解成可执行的子任务。但真正的新变化是:我们可以把时间线反转过来。
加速原则
几年前,规划一次大型迁移意味着要进行大量的前期调查。你得做概念验证(POC),评估可用方案,看看它们如何映射到现有技术栈,是否能满足所有需求,然后做决策,购买商业软件或选定开源方案,组建团队,再开始那个耗时极长的批量迁移过程,最终价值要到项目结束时才能完全实现。这些项目天生就极难排期。
但现在不一样了,我们可以在初期多做一些额外工作,在验证环节变得极其严格,严格到每个增量步骤都能被精确验证是否完成了该做的事。这就像并行化一样,你可以把原本需要几百个步骤的苦差事压缩到更短的时间窗口内,从而更早地实现全部价值,同时也能获得关于架构敏捷性的信号,能在几周内而不是几个季度后就知道新方案是否行不通。

先看迁移一个指标的场景。假设我团队里的工程师要把一个特定指标从旧系统迁移到新的指标存储系统,比如我们用的 DBT MetricFlow 和 Semantic Layer。要完成这个任务,他需要一些上下文:需要知道旧代码在哪里,需要知道它依赖哪些数据库表,需要知道数据的真实样貌,甚至需要知道数据的分片信息。还需要了解目标模式是什么,架构师已经决定如何在新平台上下文中应用它。然后才能规划哪些文件需要改动,如何改动。接着才是编写代码,再验证这些代码工作良好,编写测试。最后才能发布工作成果。
如果把视野拉远到整个任务,不管它是 5 个步骤还是 500 个步骤,架构迁移的范畴其实都一样:架构目标会展开成 云栈社区 上大家常讨论的那些子任务和子目标。这很简单,在 AI 时代之前我们也是这么做的,以前我们会脑暴出这些步骤,然后丢进 Jira 里。(如果你喜欢 Jira 的话。)
但现在,如果你能把标准化的上下文获取、标准化的提示词以及强大的验证机制做到极致,确保每一步都能以足够高的确定性判断是否真的完成,你就可以把所有这些任务分配给机器人,然后以快得多的速度推进。

我把这个模式命名为“组装线”(assembly line),当然这不是什么新名字。关键在于,我们其实早就能够做第一步和第二步了,只是方式略有不同。而我们现在有了 2025 年水平的编码 LLM,就能真正自动化第三步。核心思路是:你构建一条组装线,不再把这些任务分配给工程师们花很长时间去完成,而是把它们分解、标准化、自动化。

我来详细讲讲每一步。就像我之前说的,你绝对不想做的事情就是直接问“怎么把整座山搬走”,那太大了。你要做的是,精确地知道“怎么从山上搬走一块石子”。把任务分解到这样一个粒度:通过实验能够证明,一个 agent 确实可以成功完成这个子任务。当然,也有可能“石子”这个粒度并不合适,你可以通过实验来调整,直到找到正确的分解粒度。
分解任务只是第一步。最关键的是第二步:为每个子任务建立一个可以程序化判断“通过 / 失败”的脚本、环境或平台,你要能精确地验证 bot 的每一次尝试。你可能觉得这听起来理所当然,但说实话,回过头来看,我在想,为什么以前我们把这些任务分配给高级软件工程师的时候,没有坚持用这么严格的方式?当 Joe 或 Alice 提交 PR 时,我们本应有一个清晰、可编程的验证机制,确保他们的产出完全符合预期,但过去我很少看到有人这么做。然而,当你用 agent 来加速执行大型迁移项目时,验证器变得至关重要。尤其是在你需要短时间内并行跑完大量迁移任务时,你必须知道每一步都运行良好,否则你会很快偏离轨道。
在我们的案例中,我们构建了一个小型模拟器,它能够生成和保存后端报表系统相同的报告,但使用的是新的指标平台来构建这些报告。听起来可能很简单,但里面其实有很多细节。你要确保格式正确,数据可以直接比较,要能轻松地判断“通过”:数据正确吗?你能扫过整个分布吗?数字对吗?格式对吗?这个“物理引擎”,让你能实际测试编码 agent 产出的环境,是整个流程的关键部件。
另一个要点是,你要强制让所有这些任务彼此相似。这意味着你知道某些上下文信息对很多甚至大部分任务都是必需的,比如访问 staging Snowflake 集群中的真实数据。很多人都在谈论 MCP(Model Context Protocol),但实际上我们在这次迁移中完全没有使用 MCP。我们是在 CLI 层面工作的,就像工程师获取他们需要的上下文来修改代码一样。工程师用 CLI,CLI 能很好地展示测试数据在 Snowflake 中是什么样子,我们给 agent 配备的就是这些 CLI 工具。根据我们的经验:你不能只告诉 AI“把这个指标的公式在新平台上重写一遍”,而不提供关于表中数据实际样子的详细上下文,这样它写不出正确的代码,就像人类一样。
我们做的另一件事是,把整个项目的最终目标状态非常清晰地定义在一个文本文件中,定义“单个任务的成功”意味着什么,“整个迁移的成功”意味着什么,这样 bot 就拥有了所有正确的上下文,尤其是如何用验证工具来测试自己的工作。我们要让组装线上的每个 bot 都拥有它需要的工具和上下文,以便能够自主推进。
自愈循环
这样,你就建立起了一个自愈循环:对于单个任务,agent 使用你提供的工具获取所需上下文,然后编写代码,接着运行验证器来检查代码是否满足任务要求。如果不满足,agent 会查看不匹配的细节,然后重试,重复这个过程。这就是验证器与上下文获取共同驱动的自愈循环,是整个迁移飞轮能够持续运转的核心动力。

但我们最初几次尝试启动这个迁移飞轮时,碰到的最大问题是——如果没有足够好的验证,事情会以惊人的速度失控。核心教训非常清楚:自动化中的所有环节都很重要,如果你不给 bot 提供足够的上下文来解决任务,结果就是 bot 卡在某个任务上;如果你用的 LLM 不够聪明,无法理解你那堆写于很久以前、文档残缺不全的遗留代码,结果就是 agent 在特定任务上挣扎。
更可怕的是验证器失效的场景。如果验证器工作得不够精确,你会让 agent 长时间运行,任务一个接一个地跑,最终得到一堆看起来合理但实际上就是“垃圾”的东西。没有真正好用的验证器,你根本无法推进,你可能会浪费一整天甚至一整周的计算资源,然后发现根本没达到目标。
我们在实际迁移几百个指标的过程中,多次改进了验证器。让验证器精确工作这件事本身就不简单,很多遗留系统天生就不容易被观测,就像不是所有代码都容易做单元测试一样。理想情况下,你应该能轻松看到系统内部发生的一切,能在正确的时间点访问到正确的数据,但实际中你会有时序问题、数据对齐问题等等。这些原本在以前可能只是“锦上添花”的能力,现在变成了“生死攸关”的必要条件。
像管理“百战小旅鼠”一样管理 AI
这就引出了一个关键问题:LLM 到底需要多聪明?能像小黄人那样,只会执行简单指令就行吗?能用去年那种短上下文窗口的模型吗?答案是:它不需要像人类工程师那么聪明。关键不在于 LLM 的智商有多高,而在于你构建的自愈工作流。最坏的情况不过是 LLM 不理解任务,导致无法推进,这时候你需要反过来优化上下文呈现的方式。但这本身就是一个可控的迭代过程,而不是不可控的赌博。我经常听到这类说法:“AI 会幻觉,它不懂怎么正确写代码,它经常犯低级错误,它写出来的代码你根本不敢放到生产环境。”这些担心正在变少,但依然存在。我的回应是:它们根本不需要那么聪明。
为了让你更直观地理解成功的形态,我分享一个类比。不知道有多少人是跟我一样在 90 年代长大的?当时有一款游戏叫《百战小旅鼠》(Lemmings)。那些小旅鼠笨得要命,但你只要在它们的行进路线上放一些路障,就能确保它们全都到达目的地,这就是组装线架构的本质。

那具体到代码层我们到底做了什么?我们有一个migration_goals.txt文件。Goal State 定义了“完成”的标准:整体迁移在什么条件下才算成功?你需要在文件中明确写出这个定义,并且用你配备的工具能检测到的状态来描述,包括整体状态和每个步骤的进度。这就像给那些笨笨的小旅鼠画好了路线图,你只需要确保它们沿着路障走,最终就能到达终点。

在我们的例子里,我们大量使用了 ctpg 和 snowsql——一个从命令行运行 SQL 并连接 Snowflake 的工具。我们给 LLM 配备了正确的凭证,让它能访问暂存数据,这样它就能直接运行命令,查看指标底层表中的数据长什么样。我们还给它提供了如何找到已有迁移构件的信息,让它能寻找复用的机会。然后我们告诉它如何检查自己的工作,检查方式必须和我们工程师验证时用的方法一致。我们还限定了它的验证范围:只检查自己的工作,不能去干后面的任务,只能做当前任务。
我们还维护了一个migration_tasks.txt文件,里面是任务的详细拆解。我们把它分成多个阶段,给工程师提供方便的检查点,确保迁移过程中一切正常。刚开始的前 10、20、30 个任务,我们反复重做了很多次,直到把验证脚本、上下文和工具都打磨到位之后,我们就放手让它跑了。在短短几周内,它几乎完成了所有剩余的任务。LLM 每完成一个指标,就会在文件中标记为已完成。我们通过每次提交来观察这个文件,能看到迁移进度逐步推进。

我们在 Cursor 中运行这个流程,但不再像之前那样简单告诉 Cursor“做这个”,而是说:“熟悉一下migration_goals和migration_tasks,然后实现阶段 2.3。”Cursor 会自己生成待办列表,尤其是最新版本。它本质上采用了我们总结出的相同原则:当你用它作为工具执行任务时,它会分解出若干子任务来完成一两项指标的迁移,关键是要把握好粒度。设置好迁移机制后,你只需要给 Cursor 一个非常简单的事情去做,把复杂性尽可能隐藏在验证步骤和工具背后,确保它拥有解决问题所需的所有上下文。
迁移不再是高山
我们为报告平台的大量指标运行了这个流程,结果非常令人振奋。作为工程师,这种感觉很爽,在我的职业生涯中,很多时候因为迁移太难、成本太高,很难证明做正确的事(比如采用更好的架构、使用更新的工具)是值得的。现在,基于这个原则,我们可以做很多以前根本做不了的事情,因为你能够将时间线中漫长的那一段压缩到一个非常短的时间窗口,因为我们现在可以把第三步——对代码的理解——自动化掉,这就是这项能力所开启的可能性。
在我们搭建这一切的过程中,我们从最初的一般性分析阶段(只是想弄清楚什么是可能的),一路做到在我们的真实新指标平台上针对真实指标实际运行,也遇到了不少有趣的问题,我总结了几类常见的“翻车”场景。
第一个大麻烦是:Agent 以为自己成功了,但实际上失败了。你给它一个任务,它跑完了,报告“完成”,结果验证脚本一查,发现逻辑根本不对。解决方案就是把验证工具做得极其扎实,我们项目中大部分工程精力都花在了让模拟验证脚本跑得完美上。
Agent 卡住不动,也是常见问题,这通常是因为它找不到合适的测试数据来覆盖某个指标背后微妙的逻辑。比如某个指标的计算逻辑依赖一个特殊的时间段或数据密度,而 Agent 从示例里扒不出来这些细节,这本质上就是信息不够用。我们的应对方式是:回到源头,找“黄金清单”——那些包含优质测试数据的 ID、数据密度已知的时间范围的清单。把这些信息写进migration_goals上下文文件里。这就像你以前需要隔着工位问同事:“嘿,你测试那个指标用的什么 ID?”现在把这些“部落知识”(tribal knowledge)文档化,Agent 的成功率就能大幅提升。
完全靠自动化能覆盖多少?大约 15% 的指标实在太复杂,必须工程师亲自介入。也就是说,85% 的脏活累活可以被自动化覆盖。当你有几百个指标需要迁移时,这个比例的意义就完全不同了。
另一个有意思的点是:迁移做到一半,你发现架构设计得不好,想推倒重来。在过去的 Jira 工单模式下,这意味着巨大的时间成本和团队士气打击。但“旅鼠们”并不在乎,你改好规则,重新跑一遍就完了。
我希望我传达清楚的是:有一类项目,我们以前会想“这太费时间了、太难了”,所以干脆不做。现在,我们有了全新的敏捷性。原则和过去一样,就像你组织一批初级工程师或实习生来帮忙做项目。但不同的是,现在你需要把这些流程标准化:写好验证脚本、提供清晰的上下文和工具。一旦这些就绪,你就可以搭起一条“组装线”,让所有任务像那些“旅鼠”一样自动流过,它让我们能做以前根本做不到的事。在 AI Agent 落地的过程中,验证和工具配备至关重要。
回顾我的职业生涯,在加入 ServiceTitan 之前,我从 2012 年到 2023 年在 LinkedIn 工作。那段时间里,我亲眼目睹了好几次巨型迁移,有些项目需要几十甚至上百名工程师,连续工作多个季度,去实现一些极其复杂的目标。比如实现跨地理位置的 safe active-active 架构,以及为此需要做的种种架构变更;或者拆解巨大的单体代码库,进行微服务化改造,这些项目背后是巨大的辛劳和复杂性。我经常在想,如果我们当时就拥有今天这种能理解代码的“旅鼠”,那些项目会走向何方?我认为,这就是拥有能写代码的 Agent 所带来的巨大变革之一——它不只是让单个任务跑得更快,而是让你能以前所未有的速度完成那些曾经不可能的任务,从而去尝试新东西,去做那些你原本不会去做的事。
所以我想把问题抛给你们:有什么项目是你一直在想,但因为需要太多 Jira 工单、太多季度的开发周期,所以一直拖着、无限期推迟的?现在,借助这些思路,你是不是真的能去做了?
Q&A
Q:如果现在让你再做一遍这件事,通往成功的最短路径是什么?有没有什么已经投入生产环境的工具,能够真正加速这个过程?比如仅仅利用 Copilot、Cursor 这些工具现有的能力,能不能把整个工作流压缩到极致?
David Stein: 你问题的意思是:基于我们现在能做到的事情,这套工作流还能继续简化吗?当初为了迁移那几百个指标,我们做了很多工作,比如用 goal_state.txt 这样的思路。现在 Cursor、Claude Code 这类工具,其实正在引导大家采用类似的方法,只不过未必像我这样,把验证器和类似物理引擎的机制做得那么正式。
我相信未来某一天,你完全可以直接把这个问题丢给 Cursor。到那时,它应该能够自己分析你的遗留代码,理解其中的各种细节,然后帮你构建合适的验证器、合适的模拟引擎,把这些事情自动完成。因为它能够处理整个代码库,理解全局上下文。
所以从长期来看,我预计大家不需要再做这么多结构化设计。但在那一天到来之前,我并不认为存在更简单的方法。现阶段,你还是需要认真地把问题拆解成 Claude 4.5 Opus,或者其他顶级模型能够完成的任务;把任务定义得足够清晰;搭建一个可靠的验证器;然后按照我刚才描述的流程启动它,让系统自己运行。

Q:我觉得我们大家面对的其实都是同一个问题:怎么把自己从这些枯燥、繁琐的工作里解放出来。那么,你如何保证这些 agents 在工作时的安全性和隐私性?我有些比较有创造力的 agents 会尝试主动连接 AWS,或者执行一些危险操作,比如格式化机器之类的事情。面对这种情况,你怎么确保它们在编码过程中不会做出高风险行为?
David Stein: 出于这方面的考虑,我们实际采用的方法很简单:严密监控,并且只给它们 staging 数据。我们不会把能够造成严重后果的权限交给 agents,你肯定不希望它们拿到生产数据库的密钥,也不希望它们掌握那些能够删除线上数据的控制权限,这种做法非常危险。与此同时,我认为即使是 Cursor 这样的工具,在安全隔离能力方面也仍然处于不断成熟的阶段。
现阶段,我们必须足够谨慎:不要给它不该拥有的密钥,也要在它工作时持续观察它到底在做什么。我觉得未来一定会有越来越多关于这个话题的讨论,关于如何把更多安全机制直接内置到这些系统里。因为如果你面对的是极其敏感的应用场景,那么你必须非常谨慎地决定给 agents 配置哪些工具,以及这些 agents 应该运行在什么样的环境里。好消息是,这套安全体系的严格程度其实可以不断提高,最终达到你的业务场景所需要的安全等级。
演讲视频原链接:
https://www.infoq.com/presentations/refactoring-ai-agents/#