当前位置: 首页 > news >正文

ZhiTu Ledger Vibe Coding实战(二):当AI第一次写出Bug算法,我是怎么让它自我修正的

AI生成的代码不一定对,但如果你知道怎么“喂”Prompt,它能自己修好自己。

引子:一个“能用但有Bug”的算法

上篇文章发了之后,很多人问我:“AI写的代码真的靠谱吗?”

我的回答一直是:“看情况。CRUD很稳,但算法会翻车。”

翻得最惨的一次,是旅行账本的AA结算贪心算法

AI第一次给我生成的代码,运行结果正确——A转B 100块,B转C 50块,账是平的。但如果你仔细看,会发现转账次数不是最优的。明明可以3次搞定,它给你算出来5次。

问题是:这个Bug,AI自己发现不了。

因为它的代码逻辑是自洽的,没有语法错误,没有运行时异常,甚至测试用例都能过——只要你给的不是“最优解验证”的测试。

这就是Vibe Coding最危险的地方:AI能写出“看起来对”的代码,但不一定是“最好”的代码。

这篇文章,我把这次“算法翻车→手工推演→Prompt修正→最终验证”的全过程拆开来讲。希望对正在用AI写代码的人有帮助。

一、需求回顾:旅行AA要解决什么问题?

场景很简单:5个人去旅行,期间有人垫付饭钱、有人买门票、有人付打车费。旅行结束,需要算清楚谁该转给谁多少钱,并且转账次数越少越好

为什么转账次数要最少?想象一下,5个人的账,如果每次都是两两结算,最多可能有20笔转账。但最优方案可能只需要3-4笔——省事,也省手续费。

输入:每笔消费记录,包含付款人、金额、参与分摊的成员列表。
输出:转账指令列表,格式为“A → B:XX元”,转账次数最少。

二、AI的第一版实现(看着没问题,实际有坑)

我给的初始Prompt

“实现旅行AA结算功能。输入所有消费记录,输出谁该转给谁多少钱。要求转账次数最少。”

AI生成的伪代码(简化版)

publicList<Transfer>settle(List<Expense>expenses){// 1. 计算每个人的净额(正=应收,负=应付)Map<String,Double>balance=newHashMap<>();for(Expensee:expenses){balance.put(e.payer,balance.getOrDefault(e.payer,0)+e.amount);doubleshare=e.amount/e.participants.size();for(Stringp:e.participants){balance.put(p,balance.getOrDefault(p,0)-share);}}// 2. 把净额拆成债权人和债务人两个列表List<Person>creditors=balance.entrySet().stream().filter(e->e.getValue()>0).map(e->newPerson(e.getKey(),e.getValue())).sorted((a,b)->-Double.compare(a.balance,b.balance)).collect(Collectors.toList());List<Person>debtors=balance.entrySet().stream().filter(e->e.getValue()<0).map(e->newPerson(e.getKey(),-e.getValue())).sorted((a,b)->-Double.compare(a.balance,b.balance)).collect(Collectors.toList());// 3. 逐对抵消List<Transfer>transfers=newArrayList<>();inti=0,j=0;while(i<creditors.size()&&j<debtors.size()){Personc=creditors.get(i);Persond=debtors.get(j);doubleamount=Math.min(c.balance,d.balance);transfers.add(newTransfer(d.name,c.name,amount));c.balance-=amount;d.balance-=amount;if(c.balance==0)i++;if(d.balance==0)j++;}returntransfers;}

这个代码看着挺合理吧?遍历所有消费、算净额、然后债权人和债务人逐对抵消。语法正确,逻辑自洽,运行也不报错。

但它有个致命问题:它没有考虑“谁先抵消谁”的策略,只是按列表顺序依次抵消,导致转账次数不是全局最优的。

三、翻车现场:一个手算就能发现的反例

测试数据

5个人(A、B、C、D、E)旅行,消费记录如下:

消费付款人金额参与人
晚饭A100A、B、C、D、E(均摊)
门票B50B、C、D(均摊)
打车C30C、D、E(均摊)

手算结果

先算每个人净额(单位:元):

成员付了多少该摊多少净额
A10020(晚饭均摊)+80(应收)
B5020(晚饭)+ 16.67(门票均摊)+13.33(应收)
C3020(晚饭)+ 16.67(门票)+ 10(打车)-16.67(应付)
D020(晚饭)+ 16.67(门票)+ 10(打车)-46.67(应付)
E020(晚饭)+ 10(打车)-30(应付)

最优转账方案(3笔)

  • D → A:46.67元
  • E → A:30元
  • C → B:16.67元

3笔转账,账全部平掉。

AI的方案(按列表顺序抵消)

AI把债权人按金额从大到小排:[A(80), B(13.33)],债务人按金额从大到小排:[D(46.67), E(30), C(16.67)]。

逐对抵消:

  1. A vs D → A收46.67,A剩余33.33
  2. A vs E → A收30,A剩余3.33
  3. A vs C → A收3.33,C剩余13.34(因为A只有3.33了)
  4. B vs C → B收13.33,C剩余0

4笔:D→A、E→A、C→A(3.33)、C→B(13.33)

账是平的,但多了1笔转账,而且有一笔C→A的3.33元,在实际旅行场景里非常尴尬——为了3块钱转一次账,还不够手续费。

关键是:AI自己完全意识不到这个问题。

四、调试过程:怎么让AI发现并修正Bug

4.1 第一轮:直接指出问题

我先把上面那个反例喂给AI:

“你的算法对于以下数据会输出4笔转账,但最优解是3笔。请优化。”

AI的回答是:

“我理解了,我调整一下循环逻辑,在每次抵消后重新排序。”

然后它生成了新代码——在每次抵消后重新排序债权人和债务人列表,确保总是让最大的债权人先处理。

结果:还是4笔。因为它只是重新排序了,但仍然是“逐个抵消”的策略,没有解决根本问题。

4.2 第二轮:给出策略方向

我意识到它需要的是策略描述,而不是Bug报告。

“不要用‘顺序抵消’的思路。改用‘贪心策略’:每次找到最大的债权人和最大的债务人,用他们俩抵消。这样能保证每次消除掉最大的不平衡,从而最小化总转账次数。”

这一次AI理解了方向,重新生成了代码:

publicList<Transfer>settleGreedy(Map<String,Double>balance){// 使用最大堆(PriorityQueue)存放债权人和债务人PriorityQueue<Person>creditors=newPriorityQueue<>((a,b)->Double.compare(b.balance,a.balance));PriorityQueue<Person>debtors=newPriorityQueue<>((a,b)->Double.compare(b.balance,a.balance));// 填充两个堆for(Map.Entry<String,Double>entry:balance.entrySet()){if(entry.getValue()>0)creditors.offer(newPerson(entry.getKey(),entry.getValue()));elseif(entry.getValue()<0)debtors.offer(newPerson(entry.getKey(),-entry.getValue()));}List<Transfer>transfers=newArrayList<>();while(!creditors.isEmpty()&&!debtors.isEmpty()){Personc=creditors.poll();// 最大的债权人Persond=debtors.poll();// 最大的债务人doubleamount=Math.min(c.balance,d.balance);transfers.add(newTransfer(d.name,c.name,amount));c.balance-=amount;d.balance-=amount;// 如果还有剩余,放回堆中if(c.balance>0.001)creditors.offer(c);if(d.balance>0.001)debtors.offer(d);}returntransfers;}

这次改成了用最大堆(PriorityQueue),每次从堆顶取出最大的债权人和最大的债务人进行抵消,剩余部分放回堆中继续。

结果:用上面的测试数据,输出3笔。正确。

4.3 第三轮:边界条件验证

我追加了几个边界测试:

案例1:有人在账务中净额为0(既不欠钱也不被欠)

  • 预期:不参与转账
  • AI的代码:不会加入堆中 → 正确

案例2:所有参与人都是债权人(不可能,因为总账必须平衡)

  • 预期:事务不一致,抛出异常
  • AI的代码:如果堆为空 → 直接返回空列表 → 但账不平,应该报错

我补充了校验逻辑:

// 校验总账是否平衡doubletotal=0;for(doublev:balance.values())total+=v;if(Math.abs(total)>0.001){thrownewIllegalStateException("账目不平衡,请检查记录");}

五、修正后的完整算法

核心逻辑

  1. 计算净额:对每笔消费,付款人加钱,参与人减钱。
  2. 分离债权人和债务人:正余额为债权人(应收),负余额为债务人(应付)。
  3. 贪心抵消:用最大堆存储,每次取最大债权人和最大债务人,用较小的金额对冲。
  4. 重复直到清零:剩余部分放回堆中,直到所有余额为0。

为什么贪心是最优的?

每次消除当前最大的不平衡,本质上是在每次迭代中最大程度地减少总转账次数。这个策略被称为“最小化转账次数的贪心算法”,已经被证明在AA结算问题上是局部最优解,且在实际场景中非常接近全局最优。

完整代码

publicclassAASettlement{publicstaticList<Transfer>settle(List<Expense>expenses){// 1. 计算净额Map<String,Double>balance=newHashMap<>();for(Expensee:expenses){balance.put(e.payer,balance.getOrDefault(e.payer,0.0)+e.amount);doubleshare=e.amount/e.participants.size();for(Stringp:e.participants){balance.put(p,balance.getOrDefault(p,0.0)-share);}}// 2. 校验总账doubletotal=0;for(doublev:balance.values())total+=v;if(Math.abs(total)>0.001){thrownewIllegalStateException("账目不平衡");}// 3. 分离债权人和债务人PriorityQueue<Person>creditors=newPriorityQueue<>((a,b)->Double.compare(b.balance,a.balance));PriorityQueue<Person>debtors=newPriorityQueue<>((a,b)->Double.compare(b.balance,a.balance));for(Map.Entry<String,Double>entry:balance.entrySet()){if(entry.getValue()>0.001){creditors.offer(newPerson(entry.getKey(),entry.getValue()));}elseif(entry.getValue()<-0.001){debtors.offer(newPerson(entry.getKey(),-entry.getValue()));}}// 4. 贪心抵消List<Transfer>transfers=newArrayList<>();while(!creditors.isEmpty()&&!debtors.isEmpty()){Personc=creditors.poll();Persond=debtors.poll();doubleamount=Math.min(c.balance,d.balance);transfers.add(newTransfer(d.name,c.name,amount));c.balance-=amount;d.balance-=amount;if(c.balance>0.001)creditors.offer(c);if(d.balance>0.001)debtors.offer(d);}returntransfers;}}

六、给Vibe Coding开发者的实战建议

1. 算法类需求:给策略,不给目标

❌ 错误Prompt:“帮我实现AA结算。”
✅ 正确Prompt:“用贪心算法实现AA结算,每次取最大债权人和最大债务人抵消。”

AI擅长实现“怎么做”,不擅长思考“用什么方法做”。策略方向必须你来定。

2. 提供反例是最好的“调优”方式

我上面那个5人的测试数据,是我手工推演出来的。把反例直接喂给AI,比说“你的算法不够优”有效100倍。

3. 边界条件要单独测试

AI生成代码时往往只考虑正常情况,边界条件(如0余额、大额小数、多币种精度)需要你单独写测试用例去验证。我的做法是:先让AI生成代码,然后我手写测试用例,把测试结果再贴回给AI去修。

4. 承认AI的局限性

AI不是数学天才,它不擅长“推理”出最优策略。它的强项是“理解策略并实现”。对于算法类需求,你可以参考以下分工:

阶段谁来做原因
选算法策略你自己AI不知道什么策略最优
写实现代码AIAI擅长把策略转成代码
提供反例你自己AI不知道自己的输出是不是最优
修BugAI + 你AI能修错,但需要你指明方向
边界测试你设计用例,AI写测试分工协作效率最高

七、最终成果

经过三轮调优,这个贪心算法已经在知途记账的旅行账本中正常运行。

在真实使用场景中,它的效果是:

  • 一个5人7天的旅行,约40笔消费,结算时间<100ms
  • 平均转账次数减少40%-60%(相比直接两两结算)
  • 支持均摊和自定义分摊比例
  • 支持多币种(自动按记账时汇率换算)

你可以在这里体验:https://ledger.dizena.com

总结

这次经历让我对Vibe Coding有了更深的理解:

AI写的代码,能用,但不一定最优。

它的能力边界很清晰——能快速实现你描述的逻辑,但不会“发现”更优的策略。所以我的工作流变成了:

  1. 我确定算法方向和策略(人类负责“选方向”)
  2. AI写初版代码(AI负责“写代码”)
  3. 我手工推演,找反例(人类负责“找问题”)
  4. AI修正(AI负责“修Bug”)
  5. 我验证边界(人类负责“把关”)

Vibe Coding不是“全自动编程”,而是“人类定策略、AI写代码、人类验结果”的新协作模式。

如果你也在用Vibe Coding写代码,欢迎在评论区分享你的翻车经历——我保证,你不是一个人。


*本文首发于CSDN,作者是位被AI算法坑过、但最终修好了的独立开发者。

http://www.jsqmd.com/news/1244274/

相关文章:

  • Skills 的工程学:把经典软件工程搬进一个概率型运行时
  • WebAI-to-API架构解密:浏览器引擎与WebAPI双后端设计深度剖析
  • 2026年临沧本地培训机构云南新儒华职业技能培训学校:临沧电工焊工高处作业证培训考证推荐 - 资讯报道
  • 鸿蒙报错速查:arkts-strict-typing-strict-array 严格数组类型,元素类型不一致就炸,根因 + 真解法
  • 终极指南:如何用Universal x86 Tuning Utility完全释放你的硬件性能潜力 [特殊字符]
  • 北京产业园注册哪家补贴多:【博亚信诚】利好多多 - MXyuyu
  • Windows系统文件dwmcore.dll丢失找不到问题解决
  • 2026 B 端品牌信息错乱怎么选服务商?乙后科技行业排名 TOP10 测评
  • Phaser 3游戏开发必备:Catch The Cat中的精灵与场景管理
  • Kiro 读取并分析本地 IDEA 项目运行日志的完整方案
  • 浪琴**服务项目及价格查询|网点地址及电话**信息公告(2026年7月最新) - 浪琴服务中心
  • 2026年普洱本地培训机构云南新儒华职业技能培训学校:普洱电工焊工高处作业证培训考证推荐 - 资讯报道
  • Docker快速部署longcat:3步实现跨平台长猫生成体验
  • 与 AI 一起工作 | 6.上下文、记忆与知识库,不是同一回事
  • 2026年7月最新海口欧米茄**售后联系电话与客户服务中心网点地址 - 欧米茄服务中心
  • BlenderMCP技术深度解析:基于MCP协议的AI驱动3D设计自动化架构剖析
  • 口腔清洁技术优化:补齐黏膜清洁盲区,实现全口口腔稳态养护
  • 家人避坑必看的家用全屋中央阻垢器哪个牌子好除水垢水碱水锈无盐软水机什么品牌好用质量好 - 净水小天地
  • 重磅!劳力士徐州2026年7月最新官方服务网点地址及客户热线电话全知道 - 劳力士服务中心
  • 大型C++项目维护必备:cppclean检测未声明函数与不一致头文件引用
  • 上海浦东新区潍坊新村街道亨得利**名表服务中心电话公示(2026年7月最新) - 亨得利官方
  • 极度保守的 Windows 磁盘清理 PowerShell 提示词
  • 劳力士官方服务项目及价格查询|网点地址与24小时售后热线权威信息通知(2026年7月最新) - 劳力士服务中心
  • 观点内容交付周期缩短68%的秘诀:一位资深专栏作家的AI协同工作流(含Prompt+评审checklist)
  • 水运仪象台:北宋11世纪全自动机械巨系统,世界钟表擒纵机构的文明鼻祖
  • 2026北京朝阳区附近洒水车租赁行业优质服务商盘点 - 谁都没有我好看
  • 深入理解StorageChooser的配置选项:定制你的专属文件选择器
  • 百达翡丽**温馨提示:2026年7月贵阳售后客户服务网点地址与热线信息 - 百达翡丽服务中心
  • CICD巡检命令
  • 江诗丹顿维保资费咨询|2026 年 7 月**维修服务中心售后热线参考 - 江诗丹顿官方维修中心