03

第 3 章:Debugging——在深水区和 AI 一起查 bug

今天的 AI 编程,是在不确定性中、在混沌中、在混乱中的一种编程。

Cover Image for 第 3 章:Debugging——在深水区和 AI 一起查 bug

“今天的 AI 编程,是在不确定性中、在混沌中、在混乱中的一种编程。”

延伸阅读:Debug 工作流如何嵌入整体工程循环,参见Test–Code 循环:为什么测试代码比功能代码更重要AI-Native 工作流:Plan–Act、Test–Code、Doc–Code–Doc

本章通过一个扑克游戏动画队列的真实案例,来展示 AI-native 团队在“深水区”如何和 AI 协作调试,以及在哪些地方,人类必须亲自下场。

1. 案例:扑克摊牌动画消失的深层 bug

这个 bug 的现象是,如果一个牌局打到了最后一条街(river),具体情况是:

  • 如果是用户先手的一个牌局,在最后一条街用户 check,然后对手跟着 check
  • 这个时候理应是出一个摊牌结算的动画的,但是摊牌结算的动画就不会出
  • 只有在这样特定的牌局下才会暴露这个问题

最后一路排查,这个牌局需要是用户先手,然后打到了最后的一条街。同时用户先手选择 check,然后对手选择 check。这个时候理应期待的是这两个 check 动画播完,然后再接着播摊牌结算的动画,但是最后摊牌结算动画就不会被播放。

最后发现是前端没有错,问题在于:后端传过来的数据里就少了这个摊牌结算的信息,所以前端根本没有动画可以播。

这个链路非常深,因为代码库本身已经相当庞大,这就直接把我们带进了一个问题:在用 AI 写代码的过程中,软件工程质量要怎么把控?这其实是 AI 编程的一个核心问题。

2. 第一步:不要急着改代码,先让 AI 建立上下文

假设我是代码库的一个新读者,我会怎么解决它?

第一步,我不会一上来就把 issue 丢给 AI,而是先让 AI 建立它自己的上下文:

  1. 我知道问题跟动画队列有关
  2. 我会让某个 AI(比如 codex,找快一点的模型)帮我先找到:有哪些文档、哪些代码和动画队列有关
  3. 这一步,一方面是我自己也找不全,另一方面是让 AI 自己先对“所有和动画队列有关的东西”建立一个完整的上下文

如果你一上来就把 issue 告诉它,它可能会过于 focus 在这个 issue 本身,而漏掉一些关键逻辑和模块。

这里"先建上下文、再动手"的模式,与AI 的无状态性与 Context Window中关于 session 接力的讨论是一致的。

3. 第二步:精确描述问题,而不是一句“有 bug”

在 AI 对上下文有了一定了解之后,才是讲述问题现象的时机。比如:

“In a specific scenario, where the hero is out of position (OOP), and we play through the game till the river street. The hero checks. And the villain checks back. We should see a showdown animation. Right now, we are only seeing the hero check and the villain check, but not the showdown. Please find out why.”

这里的关键点是:
尽可能用现象和场景去描述问题,而不要一上来就给结论。

4. 第三步:先加 debug logs,而不是让 AI 立刻改代码

对于 codex 或者别的 AI,它有一个天然倾向:
一听到“有 bug”,就会立刻去改代码。

很多时候,我们并不希望它马上改代码,所以需要提前给它一个约束:

“Don't make changes yet.”

在我们的例子里,我们已经知道 animation queue 的设计,在 river 这一条街里本来应该有三个动画:
我 check,然后对手 check,然后有一个摊牌动画。但我们现在只看到了前两个。

所以,此时问 AI 的正确方式不是“帮我改好”,而是:

  • 告诉它预期的动画队列
  • 让它在关键位置帮你 加 debug logs
  • 然后再用这些 logs 去反推问题在哪里

The six-step AI debugging workflow

5. 第四步:用 debug logs 校正 AI 的思路

为什么要打 debug logs?

  • 因为 AI 对代码的理解不一定是对的
  • 它给出的 debug 结论也经常是错的,而且会“陷在自己那套解释里”

如果你直接让它根据自己的理解去改,很可能会越改越偏。但如果:

  1. 先让它在关键路径上加好 debug logs
  2. 然后你把真实的 logs dump 给它
  3. 再让它在 logs 的基础上重新分析

它的思考过程往往会被纠正很多。

如果你发现:
— 你已经贴了 debug logs,它还是想不对,那就要回头想一想,是不是你对问题现象的描述本身就不够清楚,甚至是错的。

很多时候,debug 不成功的本质原因,是问题空间本身就没被描述清楚。在你描述不清楚、甚至描述不了 problem space 的情况下,AI 帮不了太多。

6. 第五步:AI 进入深水区时,先“记账”再换人

在动画队列这个 bug 里,我们看到一个典型的深水区场景:

  • AI 已经尝试了两次,还是没有改对
  • 再继续让它改,只会在错误路径上越跑越远

这时候不能再让 AI 自己改了。
大致有两种可能:

  1. 我们对问题的描述有问题
  2. 问题本身确实更复杂,超出它当前的能力

此时的做法是:

  1. 把“到目前为止的研究过程、猜测的原因、阶段性的结论”都让 AI 写到一个文档里
  2. 暂时不在乎这个文档是不是将来要丢掉,先把“研究过程”记账记清楚

等我们真正解决了问题,再把这些过程性的信息删掉,最后只留下结论性的文档。

Write the account, then hand the case to a fresh session

7. 第六步:新开一个更强的 session,从文档继续

接下来,应该另起一个新的 session:

  1. 把模型能力调到最强
  2. 让它先认真读刚才那份“研究文档”
  3. 不要马上让它改代码,而是让它在关键地方再加一轮更精准的 debug logs
  4. 同时,人类也要开始自己理解这块代码

到这个阶段,其实已经非常接近“传统人工 debug”了:
一方面尽量让 AI 继续写,一方面人类自己也要开始读。

这是 AI 进入深水区的一个典型例子——只有在特别复杂的模块里才会遇到,但一旦遇到,就必须接受:

人类要下场读代码。

8. Debug 工作流总结

总结如下:

  1. 拿到问题之后,先把需求/现象描述清楚,丢给 AI
  2. 让 AI 自己去解决,观察它的过程,解决完一轮之后马上验证结果
  3. 没有解决的话,进行第二轮,同时关注 context 使用百分比,感知它的思考逻辑跑到哪里了
  4. 如果还没有解决,让它把思考过程总结成一个文档,作为更深层次搜索的输入
  5. 如果多轮之后还是没解决,那就用这几轮产生的 bug log、人类自己去读代码,人工修改
  6. 人工处理完之后,回头看 AI 为什么解决不了,把经验或者这个 issue 的结论记录到文档里(只保留结论,不要过程)

这样,当你下次再遇到类似问题的时候:

  • 你知道怎么优化描述,让问题的描述更清晰
  • 你也知道 AI 的能与不能,哪些环节要提前预留给人类

这个总结与AI-Native 工作流:Plan–Act、Test–Code、Doc–Code–Doc中对中高复杂度需求的标准流程是互相印证的。

9. 关键原则:AI 改对了也不能“误打误撞”

在整个 debug 流程里,有几个关键原则:

  1. 虽然人不用去写代码,但你一定要知道 AI 到底在改哪一块
  2. 根据自己对代码库的熟悉程度,判断它改的是不是“该它改的地方”
  3. 如果它改了无关的东西,虽然最后结果“看起来是对的”,你也必须阻止它这么做

不能接受“误打误撞改对”的结果。

对一些根深蒂固的 bug,如果最后是通过人工解决的,那么:

  • 过程性的内容可以丢掉
  • 但可以留下一个结论性的文档,说明这个 flow 到底是怎么回事
  • 让 AI 写一版详细的流程解释,放在文档里
  • 但这段话必须是“结论性”的,不要带过程性信息

目的就是:
让未来的自己和未来的 AI,更容易看懂这个 flow。