Notes
把 AI 放在能被检查的位置上
先说结论。
用 AI 写代码,最先失效的不是它的能力,是你的审阅能力。它一天能写出你可能要一周才读完的改动 —— 逐行审阅这条路在速度面前直接断掉。而多数人第一次用它,恰恰就是把"我会仔细看"当成了质量保证。
这篇讲的是同一件事的推论:既然读不完,就得让"对不对"变成一个不必逐行读也能回答的问题。
过去一周我在这里做的事,大半围绕一个本机内容后台(这一带的二十几个提交里,一多半是它),下面每条都是从那里来的,不是通用道理。
一、先写"什么算做对了",再让它动手
这一周最要紧的一步,是在写代码之前把验收标准定死。
具体例子:后台要读写照片数据,前提是把数据从一个人手维护的文件里搬出来。那是个 730 多行、中间夹着大量注释的 JavaScript 字面量。程序去改这种文件,很容易把注释一起搅乱。
我给它定的验收标准是:
搬完之后重新构建一遍,产物必须逐字节相同。
不是"看起来一样",不是"条数对得上",是 cmp 说相同。这条标准让整件事变得可判定:跑一条命令,退出码就是答案。
你可以照着做的一件事:接到任务时先问一句"改完之后,用什么命令能证明它是对的?"。答不上来,就先别让它动手 —— 那说明任务本身还没想清楚,而不是 AI 不够强。
二、让产物可以被比较,而不是只能被阅读
这一周改动最多的不是源码,是生成物:照片数据变了,js/photos.js、manifest、sitemap、页面全都要跟着重建。这些文件我基本不读 —— 我从一开始就知道读不完。
不读它们,靠的是一条性质:重建是幂等的。
意思是:什么都没改的时候,跑一次"全部重建",工作区应当是干净的;改了东西,重建后 git diff 应当只出现我预期的那几处。做不到这一点,你就永远不知道 AI 的改动到底碰了什么。
这条性质不是天上掉下来的,得专门确认过才敢信:在什么都没改的情况下跑一遍全量构建,看 diff 是不是空的。 我为了写这一段真的跑了一次 —— 因为只要存在一个"每次都变"的字段(时间戳之类),"重建后干净吗"这个信号就废了:一屏无意义的 diff 会淹没真正的改动,而人会开始习惯性忽略它。信号一旦被忽略,就等于没有。
值得养成的习惯:每隔一阵,在没改任何东西的情况下跑一遍全量构建,然后看 diff。如果它不干净,先把这件事修好 —— 这是后面所有检查的地基。
三、给它一道会拒绝的闸门
这个站的部署前有一道闸门(一条 --check 命令),它会因为这些原因拒绝上线:
未映射的原片(data/photos/ 里没有条目的图)
映射指向的原片不存在
已发布但无映射的照片
网站参数与源片 EXIF 不一致
某个「机身 + 焦距」组合没有任何已列镜头拍得出来
器材清单里有、但关于页没列出的镜头
注意这些都是内容层面的问题,不是代码风格。闸门的价值在于:它是你的判断的自动执行,而不是"让 AI 自己检查自己"。让写代码的那一方自己定义合格线,等于没有线。
两条实践经验:
- 闸门必须真的会失败。 一个从没红过的检查等于仪式。这一周它红过两次,都拦住了部署:一次是我留了一个没有条目的原片(报"未映射的原片"),一次是照片参数与源片对不上(就是下面那条)。
- 闸门的失败信息要能直接照着改。 有一次闸门报"网站参数与源片 EXIF 不一致",看起来像是我手改错了参数;实际是那个系统在读文件时有个索引延迟,重跑一次就好。我为此专门在报错里加了一句"重跑即可,不要手改参数" —— 因为一条指向错误方向的报错,会让人花一小时去改一个本来没错的东西。
四、任务书里必须写"不许做什么"
这是这一周代价最大的一课。
我把后台的界面交给一个 AI 子代理做,规格写得很细,包括"点发布按钮时应该先弹出确认"。它做完了,自测也过了。但我的规格里漏了一句:不要真的执行发布。
于是它自测那一条时,它的对话框处理是自动接受的 —— 深夜 21:30,网站被真的发布了一次。
"测试一下这个按钮"和"运行这条命令"之间的距离,比我想的大得多。后来我把它写进了给代理的规格模板:
- 目标:要做什么、做到什么程度;
- 边界:明确列出不许做什么(部署、删除、推送、改这个文件之外的东西);
- 验证方式:做到什么状态算完成,用什么命令演示。
第三项同样重要:只写"测试一下",它会自己发明一套测试方式 —— 包括你没料到的那种。
五、不要问"做完了吗",要问"你怎么知道的"
AI 说"已完成"时的确信程度,和实际正确率没有关系。这不是它不诚实,是它没有"我不确定"这个内部信号。
所以我这一周形成的一个习惯是:要证据,不要结论。
- "改好了" → 要"哪条命令、什么输出";
- "没影响别的地方" → 要"重建后 git diff 为空";
- "测试通过" → 要"测试的代码贴出来",然后我自己挑一条复核。
这里有个很典型的反例,值得单独讲。我给照片列表做拖动排序,测试全绿 —— 六项都过。但打印出来的属性值不对:draggable 是枚举属性,值必须是字符串 "true";我写成了布尔 true,被转成了 draggable="",那是非法值,浏览器会忽略,真实鼠标拖动可能根本不生效。
而测试为什么是绿的?因为它是用合成事件驱动的 —— 合成事件绕过了"这个元素是否可拖"的判断,照样触发。
教训不是"别信测试",而是:要问清这个测试绕过了什么。 由真实输入驱动的检查,和由模拟输入驱动的检查,可信度不一样。前者能证伪,后者只能证明"我写的模拟路径没崩"。
六、把"为什么"留在仓库里,别留在对话里
对话会结束。仓库会留下。
AI 的上下文在两件事之间会归零:换一个会话、换一个执行者(人换人,或者 AI 换 AI)。所以凡是这轮搞明白、下轮还会再撞上的东西,都要落到文件里。
这个仓库现在有两千五百多行文档,加上代码里八十多行写着"为什么"的注释,大部分是这几天长出来的。它们写的都不是"这段代码干什么"(读代码就知道),而是:
- 为什么这个顺序不能换(不换会怎样);
- 为什么这里要留一个空分支不动(去掉会怎样);
- 为什么这个值是硬编码的(当初算过什么)。
你可以现在就做的一件事:让 AI 在每次解决一个"不显然"的问题之后,把理由写进代码注释或文档,并要求它写清"如果不这么做会怎样"。只写"做什么"的注释,对下一个执行者没有价值 —— 那正是代码本身已经说明的部分。
七、不可逆的动作,留在人手里
这一条我在另一篇里写过(《宁可不删,也不假装删了》),这里只补流程上的落点:
- 提交之前先把待提交的文件列出来给作者看 —— 而不是
git add -A之后才发现混进了别的东西; - 部署之前跑闸门,闸门失败就停在原地,不继续;
- 上线之前等一句明确的话。不是"改好了吧?"这种反问,是"上线吧"。
让 AI 拥有发布权是很有诱惑的 —— 少一次人工介入,流程看起来更顺。但你要想清楚:当它判断错误时,那个错误会以谁的名义、在什么时候、被谁发现。 如果答案是"以我的名义、立刻、被访客发现",那这个权力就不该交出去。
八、什么时候别用它
- 判据写不出来的事:这一周我始终没把照片文案的撰写交给它。哪张够格上精选、这张拍的是哪儿、这句话该怎么说 —— 这些没有
cmp可跑。 - 不可逆又没有闸门的事:没有回滚点、没有备份、没有测试环境的改动,不该让它先试。
- 需要"知道自己在编"的事:它能把一个错误说得非常流畅。凡是你要拿去当事实用的输出(数据、日期、引用、参数),都要有来源;没有来源的,让它标注"未核实"。
结语
用了一周之后,我的结论是:
与其花时间教它更聪明,不如花时间让它的产出更容易被否定。
先写下判据、让产物可比较、架一道会拒绝的闸门、把理由留在仓库里、任务书写上边界、要证据不要结论、不可逆的事留在人手里 —— 这些做法没有一个能让它写得更对。它们只是让"写错了"这件事更早、更便宜、更没法藏地暴露出来。
而在这个前提下,它的速度才是净收益。