AI 编程里的未知,比 prompt 重要
看到《Finding Your Unknowns》之后,我回头翻了这几个月用 Codex 和 Claude Code 的真实记录。最后发现,AI 编程最难的部分常常不在代码本身,而在那些我们自己没说清、甚至没意识到的未知。
前几天我看到一篇文章,A Field Guide to Fable: Finding Your Unknowns。我读完没什么“被点醒”的感觉,反而像是终于有人把那层窗户纸捅破了。
因为这几个月把 Codex 和 Claude Code 当执行型工程代理用下来,我最大的体感也差不多:模型越强,越会暴露人脑子里那些没说清的部分。
以前工具不够强,很多问题会被能力上限盖过去。它不会做,你也就懒得继续想。现在不一样了。它能读代码,能查网页,能跑命令,能开浏览器,能自己把一条链路往下推很长。于是新问题来了。它跑偏的时候,未必是它不会,很多时候是你自己也没把边界想明白。
我后来越来越少问“这个 prompt 怎么写更神”,开始反过来问自己:我到底有哪些东西没说清?
我现在会先分四类未知
那篇文章里提了四种未知。这个分类我后来是认的,而且确实好用。
第一类是已知已知。我要它做什么,这部分得直接写清楚。比如修一个登录链路,还是做一轮 GitHub 巡检,还是摸一个陌生系统的架构,这些都属于任务本身。
第二类是已知未知。我知道自己还没想清楚,比如到底走哪条技术路线,某个旧模块是不是还能复用,或者验证到底要跑到什么深度。这类东西不怕没答案,怕的是假装自己已经有答案。
第三类是“我以为不用说”的东西。人脑对上下文太熟了,很多约束会默认成空气。比如“这个任务最后必须开浏览器验证”、“空窗口 no-op 也是合法结果”、“评论里不要把本地路径贴出去”。这些在我这里几乎是常识,模型那里不是。
第四类最麻烦,是连我自己都没意识到的盲区。这个才最贵。因为它不会在开头跳出来,只会在实现一半、验证快结束,或者已经准备发评论的时候突然咬你一口。
我后来看自己的记录,真正有价值的习惯,几乎都在围着这四类未知转。
先摸盲区,再让 AI 动手
有一类任务,我现在已经很少一上来就说“开始实现”。
比如要看一个陌生系统能不能复刻。我现在更常见的做法,是先让它做一轮勘察:公开 npm 包、前端页面、接口形状、CLI 参数、早期版本残留、现成源码参考,能摸的先摸一遍。这个阶段不急着产出代码,先把盲区翻出来。
这不是绕路。相反,这是省时间。
我印象很深的一次,是接一个陌生的 agent runtime 体系。表面上看,旧版本代码已经把 daemon 怎么拉起 CLI、怎么接双向流、怎么桥接聊天写得很明白了,照着抄一版 PoC 很快就能出东西。但在真正开做前,我们先多看了一眼当前 CLI 的 help,结果立刻撞见一个会把整条链路打断的细节:旧代码依赖的参数形式已经变了,少一个 flag 就会直接跑不起来。
这个坑如果等到实现后再踩,就会被误判成“代码接错了”、“桥接坏了”、“环境有问题”。可它其实只是一个很朴素的未知未知。先做 blindspot pass,成本只是多看几分钟;晚一点才发现,后面整串排查都会被污染。
所以我现在碰到陌生代码库、陌生服务、陌生领域,经常先让 AI 帮我回答三个问题:
- 这里最容易踩错的边界是什么?
- 哪些前提在我脑子里成立,但在代码和文档里并不稳?
- 如果直接开做,最可能白干的是哪一段?
这一步很不性感。但真有用。
很多 bug 要靠验证才能收敛
另一条我后来越来越笃定:完成态一定要写死。
“改完代码”不算完成。“单测绿了”也不算。很多时候,那只是模型觉得自己该收工了。
我们后面把真实验证看得这么重,纯粹是被坑出来的。
有一次是一个登录注册链路的问题。表面现象很普通,前端给用户的反馈也很普通,就是一个模糊的服务器错误。你如果只让 AI 在代码里找,很容易停在表单校验、接口实现、数据库写入这些熟悉的位置。它也确实能给出一堆看起来合理的怀疑。
但这条线最后卡住的地方,不在代码主逻辑,而在更外层的一段真实链路。我们把服务跑起来,用 HTTP 走注册,再去看邮件落点,再开浏览器把验证链接点完,问题才收敛。没有这条真实链路,AI 会一直在代码里兜圈子,而且说得还挺像那么回事。
这类事多了之后,我现在给 AI 的“完成定义”会写得很硬,通常至少包括这些东西:
- 该过的测试要过
- 真实接口要打
- 真正的页面要开
- 关键路径要按用户顺序走完
有时候还要再往后一步,把 review、merge、cleanup 也算进任务。
这听起来像在给模型加束缚。其实是在帮它收口。因为当前这代模型最大的问题之一,就是会开工,不太会判断什么时候真的结束。你不把完成态写清楚,它就会在一个“差不多能交差”的地方停下来。
而真实世界从来不认“差不多”。
AI 最爱补全。补得对不对,是另一回事
还有一类坑,比不会做更烦:它会替你补全。
这能力有时候很爽。你话没说满,它能顺着上下文把活接过去,效率高得离谱。
可只要前提不稳,这能力马上就会反噬。
我在自动化巡检和代码 review 这类任务上,特别容易看到这个问题。你如果给它一个带倾向性的目标,比如“找出这批改动里的问题”,它常常真的会给你找出来一些“问题”。其中有些是真问题,有些只是看上去像问题,还有一些纯粹是因为它默认“既然你叫我找,我就最好别空手回来”。
后来我们对这类任务加了很多很土的护栏。先锁身份。先锁时间窗口。先确认这次是不是 fresh run。先读正文,不把提醒消息当正文。先读完整 diff 和上下文,再决定要不要说话。必要的时候,还得明确告诉它:空窗口、no-op、静默结束,本身就是正确结果。
这个变化挺说明问题的。
AI 会幻觉,当然是真的。但很多“幻觉”背后,其实是人给了它一个会诱导补全的环境。你让它一定找东西,它就容易硬找。你默认它知道哪些事不能公开说,它就会把不该带出去的上下文一起带出去。你不给它 state,它就会在每一轮 fresh run 里重新发明一次自己。
所以我现在对 prompt 的要求反而更朴素了。我会尽量少做诱导,把边界说清楚。与其让它“帮我证明”,不如先把它看到的情况摊开。与其默认“这里应该有问题”,不如直接区分哪些已经确认,哪些还需要确认。
这听着不高级,但能明显降低它自作聪明的概率。
后来我们花最多时间的地方,变了
刚开始用 AI 编程的时候,我以为主要矛盾会是模型能力不够。
用到现在,我不这么看了。
当然,模型能力仍然重要。Codex 和 Claude Code 也各有手感差异,工具面、浏览器面、长任务稳定性、搜索方式,都有细微不同。但这些差异没有大到能盖过更底层的东西。
更底层的东西是:你有没有把未知摊平。
要不要先做一轮勘察。哪些约束必须显式说出来。哪些假设不能靠它自己补。验证到底要跑到哪里。什么叫完成,什么不叫完成。什么时候该继续深挖,什么时候 no-op 才是正确答案。
这些事情想清楚了,工具就会顺手很多。没想清楚,换再强的模型也只是把偏差放大。
所以我现在越来越相信一件事:AI 编程里最值钱的能力,可能不是写 prompt,也不是会不会配一堆花哨工作流。更值钱的是把那些没说出口的前提一个个挖出来,摆上台面。
代码通常没有那么难。
难的是承认自己还没想清楚哪一段。