跳到主要内容
给 AI 分了工,其实就是提前决定了哪一半不用看

给 AI 分了工,其实就是提前决定了哪一半不用看

阿舟
阿舟

· 阅读约 7 分钟

九月底刷那份月度盘点,有个标题我扫过去又倒回来——大意是"我们好像都有两个 AI,一个正经干活的,一个用来瞎玩的"。

画外音:这不就是我那台常年开着两个窗口的编辑器吗。

我盯着这话看了半分钟。被总结命中的感觉,翻过车的人都懂。

先把现场交代一下。我干活的机器上一直有两套东西:一个正经工作目录,接着仓库,历史干净,跑测试,提 PR;另一个是 ~/scratch——一次性脚本、半成品、各种想试的东西,扔进去就不怎么管。这习惯 AI 进来之前就有了,那时候两个目录的区别很单纯,一个"重要",一个"不重要"。

用了 agent 之后,这区别在我脑子里悄悄换了个词。不再是"重不重要",变成了"要不要审"。

工作目录里的产出,我一行一行看 diff。scratch 里的产出,我不看,跑通就行——反正它不进仓库。

问题就出在"反正"上。

摸鱼那一半的产出,是会走路的

那篇帖子的本意是聊工具分工:写文章用一个,写代码用另一个,两个脑子别混着使。这个我认,工具按任务挑没什么毛病。但我想说的不是选型。

我想说的是,当我在工具层面分了工,我顺手在流程层面也签了张空白支票——我提前决定了哪半产出来自"不用审"的区域。注意,这个决定是在看到产出之前就做好的。

然后支票就被兑了。

上个月我要给一个配置读取函数补个默认值。这活本该在正经目录里写,可手里刚好有半年前在 scratch 写的一段,懒得重写,直接粘了过来。粘的时候我没细看——它属于"不用审"那一类,我潜意识里就是这么归档的。

这行 import 就是这么进的仓库:

// 从 scratch 目录粘过来的,当时只想让它先跑起来
import { normalizeConfig } from "./legacy/config-helpers";

它跑起来了,测试也过了。故事在函数体里:

function normalizeConfig(raw: RawConfig) {
  try {
    return parseConfig(raw);
  } catch {
    return raw; // 出任何事,就当没解析过
  }
}

一个空 catch。任何解析错误被它静默吃掉,然后返回一个"看起来能用"的原始值。它不是我手写的——骨架确实是当年那个实习生生成的,但那个空的 catch 是我自己觉得"反正能跑"留下的。它在 scratch 里待着的时候,"反正"完全成立。它进了主流程之后,不成立了。

整个过程里没有一步是错的,每一步都很合理。这才是最烦人的地方。

反面也一样,别把"没用 AI"当勋章

同一个月,我还看到有人特意在文末标了一句"这篇没用 AI 帮忙"。那篇数据不错。

我没有要挖苦谁的意思。愿意写、愿意标,比藏着掖着强,这点我站他。但那个标注本身暴露了一件事:我们默认把风险安在了工具身上,而不是安在"有没有人看过"这件事上。

这跟我开头那个空 catch 是同一枚硬币的两面。一边是"产出来自摸鱼 AI,跳过审查";另一边是"产出没让 AI 碰,所以没问题"。两边都拿同一个变量——产出从哪来——去代替了真正该看的那个变量:这段东西有没有被一双眼睛认真过一遍。

手写的空 catch 和 AI 生成的空 catch,出事的时候一样精彩。我审 AI 生成的代码从来不手软,但"没让 AI 写"不自动等于审过了。这个等号很危险。

边界一定会漂,而没人替你看着它漂

我前面其实补了一句没展开的话,这里接上。

一开始,scratch 里真的都是些一次性的东西:一条正则,一行 shell,临时验证个想法。用"不用审"对待它们,合理,投入产出也划算。我没做错。错的是我以为这条线是固定的。

它不是。今天放进去的一个小函数,明天会因为"懒得重写"被粘到别处;再后天,它被第二个地方 import 了。每一次都没越界,每一步都很自然。等你发现的时候,"一次性"这个前提早就不成立,但"不用审"这个习惯还在原地站着。

// 三个星期后,第二个文件也引了它
import { normalizeConfig } from "./config-helpers";

工具不会提醒你边界漂了。agent 只看到你让它写、你让它跑、你点了同意。这条线从头到尾是你在脑子里独自维护的,而它偏偏在你最忙、最想走捷径的时候才派上用场——也就是最守不住的时候。

这周期的实习生表现其实都挺好。出问题的是带实习生的人,是我没告诉它这块地后来变成了主流程的一部分。

萎缩不发生在"用 AI"的那一半

那个月还有篇讲"工程师认知在悄悄萎缩"的,评论区有人总结得特别好,说那种累,是你在追一条一直往后跑的终点线。

现象我认。但结论我不同意——很多人顺着这篇往下推,落点是"少用 AI"。我的落点正好反过来。

萎缩不是发生在"用 AI"的那一半,是发生在"摸鱼 AI"那一半。因为那一半里,我问的问题最少。不较真,不追问,不挑战那个默认值为什么偏偏是 raw。我以为省下来的脑力会花在架构判断上——结果那半年我也没见着架构判断在哪儿。扯远了,回正题。

对了,那篇评论区里有人推了个东西,我顺手点进去看了,印象特别深。发帖的人用 prompt 把一个完整的 3D 空间"说"了出来,效果很爽,底下清一色"这就是 vibe coding"。续集是:为了让这玩意真能用,他自己动手写了一个关卡编辑器。

画外音:vibing 出来的东西,最后总得有人给它配一个编辑器。

这例子比我的空 catch 大得多,但结构一模一样——生成那一步有多爽,维护那一步就有多"正经",而且维护那步没法 vibe。没人能替你判断"这个关卡放这儿崩不崩",模型也不行,它连你的历史都不知道。工具能给你一个能跑的东西,给不了你一个可以不用管的东西。

我的补丁

补丁很土,就是把两个窗口合成一个。

不是把 scratch 删了——删了我过两天还会再建一个。是让它不再是"免审区"。现在不管产出从哪个窗口来,进仓库之前走同一套:同一份 rules,同一个 review 队列,同一条 dry-run。

规则文件里我删掉了一行,那行大概长这样:

<!-- 实验目录,产出不用管,能跑就行 -->

换成了:

<!-- 唯一规则:进了这个仓库的,就是被审过的。来源不写进判断。 -->

然后那个空 catch,被真正看了一遍:

- } catch {
-   return raw;
+ } catch (err) {
+   logger.warn("config 解析失败,回退原始值", err);
+   return raw;

它要的就是这一眼。三秒钟的事,我拖了半年。

到现在也没想清楚的

中间那条线该画哪儿,我至今没有标准答案,也不想硬凑一个出来。工具要不要按用途分开、agent 要不要分权,这些我都在试。但有一条我现在能守住:你分几个 AI 都行,review 只能有一个入口,而来源永远不写进判断里。

补一句,我中间那句"工具从来不是变量"是错的,写到这儿自己打了脸 😅。真正致命的不是分工本身,是你顺手把"哪半不用看"也一起分配了出去。工具只是那块被分配的地,画地的人还是我。

本期缴税:一个空 catch 从"试验品"一路躺进主流程,因为我在它出生那天就把审查关了。

阿舟
阿舟

写代码写到一半开始怀疑人生,靠 AI 工具续命,顺手把踩过的坑都记下来。

查看主页 →