AI 半小时写完 9 章重构方案?真正值钱的是后面那几次追问

AI实战架构设计技术方案Java人机协作灰度发布

AI 半小时写完 9 章重构方案?真正值钱的是后面那几次追问

TL;DR 上一篇《干了十几年 Java 的老登,我都让 AI 接管了哪些日常工作?》里,我说一份 9 章的重构方案是 AI 半小时写出来的。不少人私信问我要 Prompt。

我回看了当时的对话记录,发现 Prompt 没什么特别的。初稿确实半小时就出来了,但方案最后敢上生产,靠的是之后两周里我对它的几次追问。 这篇就复盘这几次追问:问了什么、为什么要问、方案因此改了什么。不管你做不做 Java,用 AI 写方案时都能用上。


一、要改的是什么:一个“做菜做到一半去做酱”的问题

我们有一个风控服务,负责给用户打分。打分前要先准备几千个数据(下文叫“变量”),然后交给模型去算。

问题出在准备数据的方式上。打个比方:

厨房接到一张订单,厨师开始做菜,做到一半才发现缺一种酱,于是停下来自己去做酱。更糟的是,做酱用的灶台和做菜是同一批:灶台全被“等酱的厨师”占着,做酱的活排在队尾,谁也动不了。

对应到代码里,就是打分的模块在执行过程中,又回头调用了一次整个查询服务(称为“嵌套查询”)。线上实测:一次打分约 4.9 秒,其中 4.5 秒都在等这次嵌套查询。

改法说起来很简单:订单一进来,就把要用的酱提前安排上(称为“预拉取”),厨师做到那一步直接拿来用。

难的不是写代码,而是三个问题:

  1. 怎么证明改完以后,每一个分数都和原来一样? 风控分数错一个,就是线上事故;
  2. 怎么拿真实数据做测试,又不影响线上?
  3. 出了问题怎么快速回退?

这些问题写成方案,就是几十页的细节。我决定只定方向,细节交给 AI 来写。


二、给 AI 的输入:四样东西,缺一不可

很多人让 AI 写方案,就是一句“帮我写个重构方案”,出来的往往是一堆正确的废话。我给的输入只有四样:

输入我给了什么为什么必须由我给
背景之前的性能分析文档、代码路径让 AI 自己去读,比我口述准确
方向只解决嵌套查询;把线上流量复制一份到测试机器上验证,重点看分数是否一致AI 能列出 5 种方案,但选哪一种,要看团队能力和风险偏好
红线部分数据要调用外部付费接口,测试时绝对不能产生费用红线是 AI 最不可能自己想到的东西,它不知道哪个接口一调就要花钱
范围其他几个优化点这次都不做不划范围,AI 会把它能想到的优化全塞进来

最后,我还加了一句:“重新完整仔细评估下。” 这是在提醒它:先判断这个方向站不站得住,而不是顺着我的思路一路写下去。

半小时后,AI 交出了 9 章初稿:背景、改动清单、核心机制、执行流程、灰度方案、测试环境保护、对比方法、回滚预案、风险清单。

我审稿的第一个标准是:每一章能不能对应评审会上一定会被问到的一个问题。对应不上的,删掉。

可带走的经验:给 AI 的输入分成两类。信息类(背景资料)给它链接,让它自己读;决策类(方向、红线、范围)由你用大白话写死。前者是体力活,后者是责任。


三、人当审判官:5 次关键追问

初稿写得很全,但不少细节经不起追问。下面是 5 次真实的追问。

追问 1:“复制到测试机的流量,会不会写坏线上数据?”

这是整个方案里最重要的一问。

为了让测试结果和线上可比,测试机器(下文叫“沙箱”)直接用了线上的配置:连的是线上数据库,调的是线上服务。这就好比演习时用的是真枪实弹,任何一个会“开火”的地方都可能伤到真人。

AI 的初稿只在主查询入口上做了拦截。但同一个服务里,还跑着消息队列的消费者、短信告警,以及好几个别的接口。沙箱一启动,它们就会照常工作:抢线上的消息、往线上写数据、给值班同事发告警短信。

我追问之后,AI 把整个服务的所有入口挨个排查了一遍,列出了一张保护清单:

会“开火”的地方不处理会怎样沙箱里怎么处理
主查询调用付费接口,产生费用只处理打分相关的请求,所有外部数据只读缓存
其他写数据的接口往线上数据库写数据直接返回
消息队列消费者抢走线上的消息,还会写线上存储不启动
短信告警值班同事半夜收到告警只打日志

另外还加了两道兜底:识别不出自己是沙箱还是线上时,直接拒绝服务;以及一个随时可以关掉全部测试流量的总开关。

可带走的经验:AI 会把你点名的地方改得很好,但它不会主动想到:“这个程序里,还有谁在悄悄干活?” 这个问题要你来问。

追问 2:“为什么不直接在外面把数据一次性拉齐?用两个具体的例子说明”

有一个看起来更简单的方案:不用提前“做酱”,而是在订单进来时就把所有原料一次性备齐(下文叫“拍扁平”)。

我让 AI 比较两种方案,并且追加了一句:“用两个具体的模块举例说明。”

这是我最常用的追问方式。不要求举例,AI 只会讲原则;一旦要求举例,它就必须去读代码,问题自然就暴露出来了。它找到的问题是:

  • 每个厨师有自己的叫法:同一个数据,外面叫 idNo,到了打分模块里叫 idCardNo。16 个打分模块就有 16 套叫法,在外面统一备料,很容易拿错;
  • 有些原料要先“拆包装”:有的数据传进来是加密的,打分模块内部会先解密。如果在外面备料,拿到的是没解密的,程序不报错,只是悄悄算出一个错误的分数;
  • 最危险的是缓存串号:系统会把查过的数据缓存起来,用参数拼一个“取件码”。名字对不上,某个参数就会被漏掉。严重时,取件码里连“这是哪个用户”都没有了,张三就会拿到李四的数据。

最后,AI 用一句话总结了两种方案的区别,我原样写进了方案:

拍扁平改的是“用什么原料来做”,预拉取改的只是“什么时候做、做几次”。

预拉取让每个打分模块用自己原来的代码准备请求,只是把时间提前了。另外给每个请求生成一个“指纹”,正式执行时核对一下:指纹一致,就用提前准备好的结果;不一致,就当场按老办法重新查。最坏的情况只是慢一点,不会用错数据。

可带走的经验:遇到“为什么不用更简单的方案”这类问题,就让 AI 拿真实代码举反例。举不出来,说明简单方案可能就够用;举得出来,这些反例就是方案里最有说服力的部分。

追问 3:“这里改了参数,提前准备的时候没执行到,不就不一样了吗?”

这是我自己读代码时找到的茬:某个模块在发请求之前,会先改一下参数。提前准备的时候,这段代码没执行,那准备出来的请求不就不对了?

AI 的回答是:提前准备时,执行的就是这个模块自己的准备代码,改参数的那段同样会跑;万一真有对不上的地方,指纹核对也会发现,然后退回老办法。

可带走的经验:AI 说“已经保证一致”时,挑一处具体的代码去反驳它。审方案不是看它写得顺不顺,而是拿代码去找茬。这一步没法交给 AI。

追问 4:“灰度开关太复杂了,简化一下”

初稿的灰度方案有好几层开关,看起来很周全。我的判断是:开关越多,线上出事时越没人敢动。

最后只留下了 4 个配置项,上线分四步:

步骤做法
1. 发布开关关闭,行为和改造前完全一样
2. 小范围试打开 1~2 个业务场景,新老两套同时跑,并比对结果
3. 逐步放量按比例 10% → 50% → 100% 放开,只跑新代码
4. 出问题关掉总开关,几秒内全部回到老代码,不用重新发版

AI 在这里补了一个很好的细节:第 2 步验证过的场景,进入第 3 步之前要从名单里移出去。 否则这些场景会一直新老两套同时跑,外部调用量直接翻倍。

可带走的经验:AI 倾向于“什么都做成可配置的”。你要问一句:半夜出事时,值班同事能在 30 秒内看懂这些配置吗?

追问 5:我自己拍错的一次

最后这条要老实交代:这次是我错了。

做沙箱保护时,我觉得“配置自动刷新”这个功能在测试里用不上,就让 AI 关掉了。一周后对比数据,出现了一批莫名其妙的不一致。原因是:测试期间有同事在线上改了配置,线上机器自动刷新了,沙箱没刷新,还在用旧配置。

于是又让 AI 把这个功能加了回来。

可带走的经验:人也会拍错板。所以让 AI 把每个决定的理由写进方案,而不只是写结论。以后被数据打脸时,你能很快找到当初是基于什么假设做的决定。


四、用数据拍板,不靠感觉

方案写到一半,卡在了一个决定上:测试时能不能把 16 个打分模块全部打开?

一开始只敢打开一个,因为只有它调用的外部数据全部走缓存,不会产生费用。其他模块打开后,会不会产生真实调用、要花多少钱?谁也说不准。

这种事不能拍脑袋。我让 AI 去统计:每组线上机器抽一台,看每个模块一天调用了多少次外部接口,其中多少次命中了缓存、多少次真的花了钱。

我还提了一个要求:统计必须能对上账。每一行都要满足:

外部请求总数 = 命中缓存的次数 + 真实调用的次数 + 没有计费记录的次数

AI 逐条把服务日志和计费账单关联起来(关联率 98.4%)。结论是:其他模块的缓存命中率在 45%~99% 之间。如果测试机和线上同时去查,两边都可能没命中缓存,都会产生真实调用。

于是方案改成:测试机上所有请求一律只读缓存。 16 个模块全部打开,额外费用为 0。

可带走的经验:方案里任何“应该没问题吧”的地方,都换成“让 AI 统计一下”。AI 做大量统计不嫌累,但怎么验证统计结果是对的,要你来定。否则它可能交给你一堆对不上账的数字。


五、加一台“对照组”:分清是环境问题还是代码问题

第一版测试只有一台沙箱:线上跑老代码,沙箱跑新代码,两边对比分数。

跑了几天发现一个根本问题:沙箱和线上的差别,不只是新代码和老代码。 沙箱强制只读缓存,线上不是;沙箱的请求比线上晚到几毫秒,缓存的状态也可能不一样。一旦分数对不上,分不清是新代码有 bug,还是环境不同造成的。

我的办法是:再加一台沙箱,跑老代码,作为对照组。

双沙箱流量镜像与三方比对架构图

机器跑什么和谁比、能看出什么
线上老代码标准答案
沙箱 A老代码 + 测试环境线上 vs A:环境带来的偏差
沙箱 B新代码 + 测试环境A vs B:新代码带来的差异

两台沙箱用同一个安装包,靠一个环境变量来切换跑新代码还是老代码,这样能保证除了被测的改动,两台机器完全一样。

在这一轮,AI 补出了两个我没想到的坑:

  • 流量复制要分别指定两台机器。如果把两台沙箱当成一个集群,流量会被平均分到两边,每台只拿到一半,两边处理的请求对不上,就没法比了;
  • 老代码在沙箱里会被“误伤”。沙箱有一个过滤规则:只处理打分相关的请求。这个规则会把老代码内部的嵌套查询也一起过滤掉,沙箱 A 的结果会全错。如果没发现,A 的数据就全部作废了。

其中一组对照的真实结果:

指标数值
两台沙箱都收到的请求1641 个
打出的分数7069 个
三台完全一致5486 个
A 和 B 一致、和线上不同(环境偏差)1579 个
三台各不相同(需要人看)3 个
新代码速度提升(中位数)11.9%

1579 个由环境造成的差异被干净地分了出来,真正需要排查的只剩 3 个。

可带走的经验:做对比测试,一定要有对照组。只拿“新环境 + 新代码”和线上比,一旦出现差异,你永远分不清是环境的问题还是代码的问题。


六、拿去就能用

输入模板

【背景】问题分析文档:<链接>;代码:<路径>,重点模块:<名字>
【方向】本次只解决 <一个问题>,做法是 <一句话>。验证方式:<怎么证明结果不变>
【红线】<绝对不能发生的事,比如:不能产生费用 / 不能写线上数据>
【范围】做:<A>;不做:<B>、<C>
请先完整、仔细地评估这个方向是否可行,再输出方案。
每个决定写明理由,每个数字写明依据(来自哪段代码或哪份统计)。

审查清单

  • 每一章都能回答评审时会被问到的一个问题吗?有没有夹带“顺手优化”?
  • “为什么不用更简单的方案?”让 AI 拿真实代码举反例
  • 挑一处具体代码,反驳“已经保证一致”
  • 超时时间、线程数这类数字,是从现有代码里推出来的吗?
  • 这个程序里还有谁在悄悄干活? 哪些调用会花钱,哪些会写数据?
  • 识别不出运行环境时,默认行为安全吗?有一键关停的开关吗?
  • “不一致”怎么定义?有对照组来排除环境偏差吗?
  • 回滚要不要重新发版?测试用的临时改动,上线前怎么还原?

几个好用的追问句式

想达到的目的怎么问
逼 AI 去读代码“用两个具体的 X 举例说明。”
质疑一个数字“这个值是怎么来的?对吗?”
找遗漏的副作用“除了这里,还有哪些地方会写数据 / 花钱 / 发消息?”
确认自己理解对了“也就是说……,对吧?”(用自己的话复述一遍)
砍范围“这些都不要了。”
要数据“统计一下……,要靠它做判断,所以必须准确。”

结语

这份方案的文字几乎都是 AI 写的,但最关键的几个转折点,都是追问出来的:补全沙箱保护、选预拉取而不是拍扁平、所有请求只读缓存、加对照组。反过来,AI 也不只是在打字:老代码被误伤、流量被分走这两个坑,是它在读代码的过程中自己挖出来的。

所以上一篇说“人只负责当审判官”,更准确的说法是:人负责问出 AI 想不到的问题。

下一篇:12 组对照每天要对比 8.5 万个分数,这套对账是怎么做成一个 AI Skill 的? 从“跑不完”到 20 分钟,以及对账过程中意外揪出来的一个藏了很久的多线程 bug。