☰
AI编程工作流实战:陌生代码扫描、契约先行与影子实现
2026/10/5 5:20:20 网站建设 项目流程

1. 为什么“工作流”比“提示词”更值得花时间

很多人聊 AI 编程,第一反应是去搜“最强提示词”“咒语合集”。我早期也这么干过,收藏夹里躺了几百条 prompt,真到写业务代码的时候,一条都想不起来用。问题出在哪?提示词是单点的,它解决的是“这一次我问 AI 该怎么问”,而真实开发是连续的——需求拆解、方案设计、编码、测试、重构、写文档,每一步的上下文都不一样,指望一条万能提示词打通全流程,本身就不现实。

工作流不一样。工作流是把“什么阶段、喂什么上下文、让 AI 产出什么形态的东西、人怎么验收”这一整套动作固定下来。它更像是一条流水线,而不是一把瑞士军刀。你一旦把某条流水线跑顺了,下次遇到同类任务,直接复用,几乎不用重新思考“我该怎么问”。

我自己的判断标准很简单:一条工作流值不值得留下来,看它能不能让我在第二次遇到同类任务时,省掉至少 70% 的思考成本。提示词做不到这一点,工作流可以。

下面这三条,是我在真实项目里反复用、反复改,最后稳定下来的。它们分别对应三类高频场景:读陌生代码、写新功能、改遗留系统。每一条我都会讲清楚它解决什么问题、为什么这么设计、具体怎么落地,以及我踩过的坑。

提示:这三条工作流不依赖任何特定厂商的模型,你用手头的任意一个具备长上下文和代码能力的模型都能跑。重点在流程设计,不在工具。

2. 工作流一:陌生代码库的“三遍扫描法”

2.1 这个场景到底难在哪

接手一个几万行、没有任何文档、原作者已经离职的代码库,是每个程序员都会遇到的噩梦。传统的做法是:从入口文件开始,顺着调用链一路读下去。这个方法在小型项目里没问题,但在中大型项目里,你会很快迷失——因为调用链是网状的,你顺着一条线走下去,走到第五层就忘了第一层是干嘛的。

AI 在这里能帮上大忙,但前提是你不能一上来就问“帮我解释这个项目”。这种问法得到的回答,通常是一堆正确的废话,比如“这是一个基于 XX 框架的 Web 应用,包含控制器、服务层和数据访问层”。这些你打开目录结构就能看出来,不需要 AI 告诉你。

真正有价值的问法是:带着具体问题去扫描,每一遍只解决一类问题。

2.2 第一遍:只问“数据从哪来到哪去”

第一遍扫描,我只看一件事:核心数据实体的流转路径。比如一个电商系统,核心实体就是订单。我会这样操作:

  1. 先让 AI 列出项目里所有跟“订单”相关的文件,按目录归类。
  2. 然后针对每一个文件,只问一个问题:“这个文件对订单数据做了什么操作?读、写、还是转换?”
  3. 最后让 AI 把结果整理成一张表。

这一步的关键是限制 AI 的输出范围。如果你问“这个文件是干嘛的”,AI 会给你一大段描述,里面 80% 是你暂时不需要的。但如果你问“它对订单数据做了什么”,AI 的回答会非常聚焦。

我实测下来,一个 5 万行的项目,第一遍扫描大概需要 20 到 30 次对话,产出一张 30 行左右的数据流转表。这张表就是你后续所有工作的地图。

2.3 第二遍:只问“边界和异常”

有了数据流转表,第二遍扫描就有的放矢了。这一遍我只关心一件事:每个关键节点上,异常是怎么处理的。

具体做法是,拿着第一遍产出的表,逐个节点问 AI:

  • 这个函数在参数为空时怎么处理?
  • 这个接口在数据库连接失败时返回什么?
  • 这个转换逻辑遇到非法输入时是抛异常还是返回默认值?

这些问题看起来琐碎,但它们决定了你对这个系统的信任边界。你知道哪些地方是可靠的,哪些地方是“假设上游一定正确”的,后续改代码时心里就有数了。

注意:这一遍千万不要让 AI 去“评价”代码质量。你一问“这段代码写得好不好”,AI 就会开始输出一堆主观意见,对你理解系统没有任何帮助。只问事实,不问评价。

2.4 第三遍:只问“如果我要改 X,会影响到谁”

前两遍做完,你对系统已经有了基本认知。第三遍是验证性扫描,目的是确认你的理解是否正确。

具体做法是,假设一个你未来大概率要做的改动,比如“我要给订单增加一个折扣字段”,然后问 AI:这个改动会波及哪些文件、哪些函数、哪些数据库表?

AI 给出的影响范围,你拿去跟第一遍的数据流转表对照。如果对不上,说明你对系统的理解还有盲区,回去补。如果对得上,说明你已经可以安全地动手了。

这三遍扫描下来,通常需要一到两天。听起来很久,但比起“读了两周代码还是不敢改”的情况,这个投入产出比非常高。

2.5 我在这条工作流上踩过的坑

最大的坑是贪多。我一开始想一遍就把所有问题都问完,结果 AI 的回答越来越泛,最后变成了一篇项目介绍。后来我强制自己每遍只问一类问题,效果立刻不一样了。

第二个坑是不给 AI 看目录结构。AI 不知道文件之间的物理关系,它只能根据你粘贴的代码片段去猜。后来我养成了一个习惯:每次开始扫描前,先把项目的目录树(用tree命令生成,去掉 node_modules 之类的噪音)贴给 AI,让它对项目规模有个概念。

第三个坑是对话太长。一条对话超过 40 轮之后,AI 对早期内容的记忆会明显衰减。我的做法是每 15 轮左右,让 AI 把当前结论整理成一段摘要,然后开一条新对话,把摘要和目录树一起贴进去,继续问。这样虽然麻烦一点,但准确率高很多。

3. 工作流二:新功能开发的“契约先行”法

3.1 为什么直接让 AI 写代码往往返工

很多人用 AI 写新功能的方式是:描述一下需求,让 AI 直接输出代码,然后复制粘贴,跑一下,报错,再让 AI 改。这个循环会重复很多次,而且每次修改都可能引入新的问题。

根本原因在于:AI 在写代码时,对“输入输出”的理解和你不一致。你脑子里的“用户对象”有十个字段,AI 可能只实现了五个;你以为异常要往上抛,AI 可能直接吞掉了。这些分歧在代码写完之后才暴露,修改成本就很高。

“契约先行”的思路是:在写任何一行实现代码之前,先把接口契约敲定。契约包括:函数签名、输入输出的数据结构、异常约定、边界条件。契约一旦确定,实现代码就变成了“填空题”,AI 出错的概率大幅下降。

3.2 第一步:让 AI 帮你把需求翻译成契约

假设我要做一个“根据用户标签推荐商品”的功能。我不会直接说“帮我写一个推荐函数”,而是先让 AI 做一件事:

我要实现一个商品推荐功能。输入是用户 ID 和一个可选的标签列表,输出是一个商品列表,每个商品包含 ID、名称、匹配分数。请你先不要写实现,只帮我定义这个函数的契约,包括参数类型、返回值结构、可能的异常情况。

AI 会给出一个类似这样的契约:

def recommend_products( user_id: int, tags: Optional[List[str]] = None, limit: int = 20 ) -> List[ProductRecommendation]: """ 根据用户标签推荐商品。 异常: - UserNotFoundError: 用户 ID 不存在 - InvalidTagError: 标签格式非法 边界: - tags 为空时,使用用户历史行为标签 - limit 超过 100 时,强制截断为 100 """

拿到这个契约后,我会逐条审查。比如“tags 为空时使用历史行为标签”这个约定,是不是我想要的?如果历史行为也为空呢?这些细节在契约阶段确认,比在代码阶段发现要省事得多。

3.3 第二步:把契约拆成“可独立验证的小块”

契约确定后,不要一次性让 AI 写完整实现。我的做法是把它拆成几个独立的小块,每块单独让 AI 写,单独验证。

以上面的推荐函数为例,我会拆成:

  1. 参数校验逻辑(用户是否存在、标签是否合法、limit 是否越界)
  2. 标签获取逻辑(显式标签优先,否则取历史行为标签)
  3. 商品匹配逻辑(根据标签查商品、计算匹配分数)
  4. 排序和截断逻辑

每一块写完后,我立刻写一个最小的测试用例跑一下。跑通了再写下一块。这样即使某一块出了问题,我也知道问题出在哪一块,不会在一大堆代码里大海捞针。

提示:这一步的关键是不要让 AI 一次性输出所有块。你让它一次写四块,它会在块与块之间做很多“合理但你没要求”的假设。一块一块来,虽然对话轮次多了,但总时间反而更短。

3.4 第三步:让 AI 写测试,但你来定测试用例

实现代码写完后,我会让 AI 生成单元测试。但这里有个陷阱:如果你让 AI 自己决定测什么,它会倾向于测那些容易通过的路径。真正容易出问题的边界条件,它反而会跳过。

我的做法是:我自己列出测试用例清单,让 AI 把清单翻译成测试代码。清单包括:

  • 正常路径:用户存在、标签合法、有匹配商品
  • 边界路径:标签为空、limit 为 0、limit 超过上限
  • 异常路径:用户不存在、标签格式非法、数据库查询超时

AI 负责把这些用例写成规范的测试代码,我负责检查用例覆盖是否完整。这样分工,效率和覆盖率都能保证。

3.5 这条工作流里最容易被忽略的细节

契约的版本管理。我吃过一次亏:契约改了,但实现代码和测试代码没有同步更新,结果测试全绿,线上却出了问题。后来我养成了一个习惯:契约文件单独放在一个目录里,每次修改都在文件头加一行注释,写明修改日期和原因。这样至少能追溯。

不要让 AI 同时改契约和实现。有一次我让 AI“优化一下这个函数”,它把契约和实现一起改了,改完之后我完全不知道哪些行为变了。正确的做法是:契约的修改必须由人发起,AI 只负责执行。

留一个“契约快照”。每完成一个功能,我会把最终的契约文件复制一份到docs/contracts/目录下。下次有人问“这个接口当初是怎么约定的”,直接翻快照,比翻聊天记录靠谱得多。

4. 工作流三:遗留系统改造的“影子实现”法

4.1 遗留系统为什么不能直接让 AI 重写

遗留系统改造是 AI 编程里风险最高的场景。很多人想的是:把老代码贴给 AI,让它重写一遍。这个做法在玩具项目里可能可行,在真实系统里几乎必然翻车。

原因有三个。第一,老代码里有很多隐式约定,比如某个字段虽然叫status,但实际上同时承载了状态和优先级两种信息,这种约定不会写在注释里,AI 看不出来。第二,老代码的副作用往往比主逻辑更重要,比如某个函数在返回结果的同时还往日志表里写了一条记录,AI 重写时很容易漏掉。第三,老代码的性能特征是长期调优的结果,AI 重写后的版本可能逻辑正确但性能差十倍。

所以我的做法是:不重写,而是做“影子实现”。

4.2 什么是影子实现

影子实现的意思是:新代码和旧代码同时存在,新代码在后台运行,但不接管实际流量。每次旧代码被调用时,新代码也跑一遍,然后把两者的输入输出做对比。对比结果一致,说明新代码逻辑正确;不一致,说明有差异,需要排查。

这个方法的妙处在于:它把“验证”从人工审查变成了自动对比。你不需要逐行读懂老代码,只需要让新老代码在真实流量下跑一段时间,差异自然会暴露出来。

4.3 具体怎么落地

第一步,给旧函数加一个“旁路调用”。比如旧函数叫calculate_price,我在它内部加一行,调用新函数calculate_price_v2,把相同的参数传进去,然后把两个结果都写进一张对比表。

def calculate_price(order): old_result = _calculate_price_legacy(order) # 影子调用,不影响主流程 try: new_result = calculate_price_v2(order) log_shadow_comparison(order.id, old_result, new_result) except Exception as e: log_shadow_error(order.id, str(e)) return old_result

注意这里有几个细节:新函数的调用必须包在 try 里,因为新代码可能有 bug,不能让它影响主流程;对比结果要记录订单 ID,方便后续排查;新函数的异常也要单独记录,这本身就是有价值的信息。

第二步,让 AI 帮你分析差异。跑了一段时间后,对比表里会积累大量记录。这时候把差异记录(只取不一致的那些)贴给 AI,让它帮你归类:哪些是浮点数精度问题,哪些是边界条件处理不同,哪些是真正的逻辑错误。

AI 在这件事上非常擅长,因为它不需要理解整个系统,只需要对比两组数据。我实测下来,一个中等复杂度的函数,跑一天真实流量,差异记录通常能控制在几十条以内,AI 几分钟就能归类完。

第三步,逐类修复,直到差异归零。差异归零后,再把流量切到新函数。这时候你对新代码的信心,不是来自“我觉得它写对了”,而是来自“它在真实流量下和旧代码表现完全一致”。

4.4 影子实现法的适用边界

这个方法不是万能的。它最适合的场景是:纯函数、输入输出明确、调用频率高。调用频率高意味着你能在短时间内积累足够的对比样本。

它不适合的场景是:有大量副作用的函数、调用频率极低的函数、涉及外部系统交互的函数。对于这些场景,影子实现要么无法对比(副作用没法比),要么样本太少(跑一个月才调用几次),要么对比结果不可靠(外部系统本身就不稳定)。

我自己的经验是:一个遗留系统里,大概 60% 到 70% 的函数可以用影子实现法安全改造,剩下的需要人工介入。这个比例已经能省下大量时间了。

4.5 我在遗留系统改造中总结的几条铁律

永远不要在没有对比数据的情况下切换流量。我见过太多团队,新代码写完,测试跑通,就直接上线,结果线上出问题。测试通过只能说明你想到的场景没问题,不能说明你没想到的场景没问题。

对比表的保留时间要足够长。我一般会保留至少两周的对比数据。有些差异是周期性的,比如月末结算时的特殊逻辑,跑一天是看不出来的。

新代码的性能要单独监控。影子实现只对比了输出,没对比耗时。如果新代码比旧代码慢很多,即使输出一致,切换后也可能拖垮系统。我的做法是在对比表里同时记录两个函数的执行耗时,差异超过 20% 就告警。

不要一次改太多。我试过一次改五个函数,结果差异记录混在一起,排查起来非常痛苦。后来改成一次只改一个函数,改完验证完再改下一个。慢是慢了点,但稳。

5. 三条工作流的共同底层逻辑

5.1 都是“先约束,后生成”

回头看这三条工作流,它们有一个共同点:在让 AI 生成任何东西之前,先给它一个明确的约束框架。

三遍扫描法里,约束是“每一遍只问一类问题”。契约先行法里,约束是“先定契约再写实现”。影子实现法里,约束是“新代码必须和旧代码输出一致”。

这个思路和很多人用 AI 的方式正好相反。大多数人是先让 AI 生成,生成完了再想办法约束它、修正它。但 AI 的生成能力太强了,一旦方向偏了,修正的成本比重新生成还高。先约束后生成,看起来慢,实际上快。

5.2 都把“验证”当成工作流的一部分

这三条工作流里,验证不是事后补的,而是流程里天然存在的一环。三遍扫描法的第三遍就是验证,契约先行法的单元测试就是验证,影子实现法的对比表就是验证。

我越来越觉得,用 AI 编程的核心能力不是“写提示词”,而是“设计验证机制”。你能设计出一个让 AI 的输出可以被自动验证的流程,你就能放心地把更多工作交给 AI。反之,如果你的流程里没有验证环节,你就永远得盯着 AI 的每一行输出,那效率反而比手写还低。

5.3 都假设 AI 会犯错

这三条工作流的设计前提都是:AI 会犯错,而且犯的错不一定能被你一眼看出来。所以三遍扫描法要扫三遍,契约先行法要拆块验证,影子实现法要跑真实流量对比。

这个假设听起来很悲观,但它恰恰是效率的来源。因为你不再指望 AI“一次做对”,而是设计了一个“即使做错也能被发现和修正”的流程。心态上放松了,用起来反而更顺手。

6. 怎么把这三条工作流变成你自己的

6.1 从最小可复用的单元开始

不要一上来就想搭建一套完整的工作流体系。我的建议是:从你下周要做的第一个任务开始,挑一条工作流,完整跑一遍。

如果你下周要接手一个陌生模块,就跑三遍扫描法。如果你下周要写一个新接口,就跑契约先行法。如果你下周要改一个老函数,就跑影子实现法。

跑完一遍之后,你会对这条工作流有体感。哪里顺、哪里卡、哪里需要根据你的项目特点调整,这些只有跑过才知道。

6.2 把工作流写成清单

跑顺之后,把它写成一份清单。清单不需要很正式,几行字就行。比如三遍扫描法的清单可能是:

  • 第一遍:贴目录树,只问数据流转,产出流转表
  • 第二遍:拿着流转表,只问异常处理,产出信任边界
  • 第三遍:假设一个改动,问影响范围,和流转表对照

清单的好处是,下次你不需要重新回忆流程,照着做就行。而且清单可以迭代,你每次跑完发现哪里可以优化,就改一行。

6.3 不要追求“完美工作流”

我见过一些人,花大量时间设计工作流,但真正用的时候发现各种不顺手。工作流是在使用中长出来的,不是设计出来的。

我的做法是:先用一个粗糙的版本跑起来,跑的过程中记录哪里不舒服,跑完再改。改完再跑,再改。迭代个三五次,一条适合你的工作流就成型了。

6.4 一个我常用的收尾技巧

每次用完一条工作流,我会花五分钟做一件事:把这次用到的关键提示词、关键判断、关键坑,记在一个固定的笔记文件里。不需要很详细,一两句话就行。

比如:“三遍扫描法,这次项目有 200 个文件,第一遍花了 25 轮对话,比上次多,原因是目录树没去掉测试文件,下次记得先过滤。”

这个习惯坚持了半年之后,我发现自己对“什么项目该用什么工作流”有了直觉。这种直觉,是任何教程都给不了的。

最后说一句实在话:AI 编程工具每天都在变,今天好用的模型明天可能就过时了。但工作流这种东西,底层逻辑是稳定的——先约束、后生成、再验证。你把这三件事想清楚了,换什么工具都能快速上手。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询