🛠️ 第2章 AI 编程的最小原理——知道边界在哪里
📄 一句话概括

知道边界之后,能做的事只有三件:切小块、先复述、用验证代替质疑。 切小块不是「拆任务」,是「让每一块都能被单独验证」——这个区别决定了它有没有用。 三条方法都能量出成本,不是感觉。第一条的收益是 5 倍定位范围。

📝 本章和前面几章的关系

这一篇是第 2 章的收口。 前面四篇讲了:它靠预测下一个 token 干活、靠四样东西支撑、信息永远不完整、它还会犯三类错。 知道了这些,接下来不是「更小心」,是「换方法」。 这一篇给三条方法,每条都能落到具体动作上。 Demo 里量了第一条的收益:同一个任务,一次做完和分五块做,出错时的定位范围是 5 行对 1 行。 这个 5 倍不是估算,是数出来的。

一、把任务切成能被验证的小块

「切小块」这句话谁都会说,但大部分人切错了。 常见的切法是按「工作量」切:先把接口写完,再写实现,最后写测试。 这种切法有个问题:前三块都没法验证。 正确的切法是按「能不能单独验证」切。 每一块的结尾都要能回答一个问题:怎么知道这块对了? 答不上来的,说明这块还不够小,或者切错了方向。

切法 每块的验收 好不好
按层切(接口/实现/测试) 前两块都没法验 不好
按功能切(登录/列表/导出) 每块能跑给用户看 好
按文件切 改完了但跑不起来 不好
按「一次改动」切 每块一条测试能过 最好

「按一次改动切」是什么意思? 就是「这一块改完之后,应该只有一条测试从红变绿」。 如果一块改完有三条测试变绿,那它其实是三块——拆开之后,出错了你能立刻知道是哪一块的问题。 Demo 量过这个收益:五个函数一起加日志,diff 是 5 行;分五次做,每次 1 行。 第 3 处写错时,前者要在 5 行里找,后者只看 1 行。 差 5 倍。而且如果整份 revert,前者前 4 处白做。

二、先让它复述,再让它动手

这是三条里最便宜的一条,也最少人用。 因为它看起来「多一步」,而人讨厌多一步。 具体做法:动手之前,让它用自己的话说一遍「我理解的任务是……」。 你对照一眼,歪了当场纠正。 成本是一句话,收益是「在动手之前发现理解歪了」。 这一条为什么有效? 因为它的理解偏差在「复述」这一步会暴露出来—— 而如果直接动手,偏差会藏在代码里,直到你审 diff 才发现。 审 diff 的成本比读一句复述高一个数量级。 Demo 里那个复述漏了「别动返回值」。 而「返回值」恰恰是最容易被顺手改掉的地方—— 如果没复述,这个漏会在 diff 里出现,那时候已经改完了。 有个细节:复述不需要它写得多完整。 三句话就够——改哪儿、改成什么、不许动什么。 你把这三句当模板,每次都要,形成习惯。

三、不信任但有礼貌的默认姿态

「不信任」听着刺耳,但它是工程上的正确姿态。 不是怀疑它的人品,是承认「它可能看漏、可能记错、可能顺手改多」这三件事客观存在。 「有礼貌」是指:不要每次都说「你确定吗」。 反复质疑会让对话变成拉扯,而且它每次都会说「确定」——因为它的训练数据里,「被质疑后改口」是少数。

动作 做什么 花多久
看计划 动手前让它说打算改哪几个文件 十秒
看 diff 先看文件数和行数,再看内容 一分钟
看退出码 命令返回 0 才算过,不看它怎么说 零

三个动作加起来不到两分钟。它们能拦住的是「看起来做完了」这一类, 而这一类占了失败案例的绝大多数。 「用验证代替质疑」是这一条的核心。 跑一遍比问十遍管用——因为「问」得到的答案是它生成的,而「跑」得到的结果是环境给的。 前者可以编,后者不能。

四、本章小结

第 2 章五节,可以压成一句话: 它是一个很擅长猜下一个 token 的程序,猜得准是因为代码的约束多; 但它看不见的部分等于不存在,而且它不会告诉你它没看见。 两条边界分别对应两种失误: 信息不完整 → 它会按惯例猜(补的办法:写下来、写成测试、让它复述); 三类工程错误 → 它会犯(补的办法:查文档、写死边界、跑全量)。 从这里往后,全书的重点就转到「怎么把这两条补上」: 第 3 章跑通第一次闭环,第 5~8 章讲 Harness 四件套,第 9~12 章在四个场景里用。 方法都从那两条边界推出来。 最后一句: 「知道边界」不是让你少用它,是让你知道在哪一步加人。 边界之外的活儿交给它,边界之内的活儿留给自己——这才是「会用」,不是「敢用」也不是「不用」。

五、动手 Demo:同一个任务,一次做完和分五块做

这个 Demo 把「切小块」的收益量出来。 任务:给五个函数各加一行日志。 做法 A:一次全改;做法 B:分五次改,每次一个函数。 两种做法都真跑 git,量 numstat。 然后算一个数:假设第 3 处写错了,审查的人要在多大范围里找那一处。 这个数就是这个方法的收益,不是感觉。 后面还演了「先复述」怎么用规则检查出漏项。 复述漏了「别动返回值」,而返回值恰恰是最容易被顺手改掉的地方。

# 做法 A:五个函数一起改
for i in range(1, 6):
    one = one.replace("    return x * %d\n" % i,
                       '    log.info("step%d x=%%s", x)\n    return x * %d\n' % (i, i))
git diff --numstat          # 加 5 删 0,合计 5 行

# 做法 B:一次一个,每块单独提交
for i in range(1, 6):
    cur = get(base, "svc.py")
    put(base, "svc.py", cur.replace(...))
    git diff --numstat       # 每块加 1 删 0
    git commit -m "feat: step%d 加日志" % i

实跑输出:

--------------------------------------------------------------------
同一个任务,一次做完和分五块做
--------------------------------------------------------------------


  ① 任务:给五个函数各加一行日志
  --------------------------------------------------------------------
  svc.py 五个函数 step1~step5,每个就一行。

  ② 一次做完:一处改动,一份大 diff
  --------------------------------------------------------------------
  五个函数一起改:加 5 行、删 0 行,合计 5 行 diff
  假设其中第 3 处写错了(这是常事),审查的人要在 5 行里找那一处。

  ③ 分五块做:每块一份小 diff
  --------------------------------------------------------------------
| 块 | numstat | 出错时要看的范围 |
|---|---|---:|
| 第 1 块 | 加 1 / 删 0 | 1 行 |
| 第 2 块 | 加 1 / 删 0 | 1 行 |
| 第 3 块 | 加 1 / 删 0 | 1 行 |
| 第 4 块 | 加 1 / 删 0 | 1 行 |
| 第 5 块 | 加 1 / 删 0 | 1 行 |

  分块之后,第 3 块写错:看 1 行、重做 1 行。
  一次做完时第 3 处写错:要在 5 行里找;若整份 revert,前 4 处白做。
  定位范围 5 行对 1 行,差 5 倍——这是数出来的,不是估的。

  ④ 先复述,再动手
  --------------------------------------------------------------------
  把它理解的任务复述一遍,你对照一眼就知道有没有理解歪。这一步几乎不花成本。
| 要素 | 原任务 | 复述 | 判定 |
|---|:---:|:---:|:---:|
| 范围 | 有 | 有 | — |
| 内容 | 有 | 有 | — |
| 禁区 | 有 | 无 | 漏了 |

  这段复述漏了「别动返回值」——而返回值恰恰是最容易被顺手改掉的地方。
  ! 复述的成本是一句话,收益是「在动手之前发现理解歪了」。返工的成本是一整轮。

  ⑤ 不信任但有礼貌:三个动作
  --------------------------------------------------------------------
| 动作 | 做什么 | 花多久 |
|---|---|:---:|
| 看计划 | 动手前让它说打算改哪几个文件 | 十秒 |
| 看 diff | 先看文件数和行数,再看内容 | 一分钟 |
| 看退出码 | 命令返回 0 才算过,不看它怎么说 | 零 |

  三个动作加起来不到两分钟,能拦住八成的「看起来做完了」。
  ! 「有礼貌」是指:不要每次都说「你确定吗」。用验证代替质疑——跑一遍比问十遍管用。

结果:一次做完 5 行 diff,分块做每块 1 行。 第 3 处写错时,前者要在 5 行里找,后者只看 1 行——差 5 倍。 还有一个更隐蔽的差别:返工范围。 一次做完写错了,最省事的做法是整份 revert 重来——那前面 4 处白做。 分块做写错了,只 revert 那一块,前 4 块不受影响。 这个 Demo 的任务很小,所以差距只有 5 倍。 真实项目里一个改动动辄几百行,差距会到几十倍。 但倍数不重要,重要的是「这个数是能算出来的」——下一次你可以拿自己项目的数字算一遍。 复述那一段值得看第二遍: 表格里「禁区」那一栏,原任务有、复述没有,判定是「漏了」。 而这个漏,恰恰是最容易出事的一项—— 「别动返回值」这句话没说,它加日志的时候顺手把返回值改了。

30 秒实验

  1. 把你手上一个任务切成「每次一条测试从红变绿」的小块,数一数切成了几块。 超过七块,说明任务本身该拆成两个。
  2. 下次交任务之前,加一句「先说一遍你打算怎么做」。 就一句话,试试省下多少返工。
  3. 把「看计划、看 diff、看退出码」三个动作写进你的日常流程。 不到两分钟,但它们挡住了大部分「看起来做完了」。

常见误区

  1. 「切小块太麻烦,一次做完更快。」 快的是「做完」,慢的是「查错」。 一次做完省下的时间,会在审查和返工时还回去。 而且分块做的时候,每一块都能单独验证——验证得早,返工就少。
  2. 「复述是形式主义。」 复述的成本是一句话,收益是「在动手之前发现理解歪了」。 它是所有纠错手段里最便宜的一个——比审 diff 便宜,比返工便宜得多。
  3. 「不信任会让它表现变差。」 它不会因为你不信任就变差,也不会因为你信任就变好。 验证是为了让你自己知道结果,不是为了给它压力。
自测三问(点开对答案) 1. 「按能不能单独验证」切块,和「按工作量」切块,差别在哪? —— **按工作量切,前三块都没法验**(接口写完不能跑、实现写完没测试); **按可验证切,每一块结尾都能回答「怎么知道这块对了」。** 2. 复述为什么比审 diff 便宜? —— **复述是一句话,你读一眼就知道歪没歪;** **diff 是几十行,你要逐行判断。** **而且复述在动手之前,改的是「理解」;diff 在动手之后,改的是「代码」。** 3. 为什么说「用验证代替质疑」比反复问「你确定吗」有效? —— **因为「问」得到的答案是它生成的,可以编;「跑」得到的结果是环境给的,不能编。** **而且它每次被问都会答「确定」**——质疑换不来信息。

第 2 章到这里收住。 下一篇进第 3 章 3.1,动手装、登、跑第一条命令——从这一节开始,正式进入「怎么用」的部分。

相关笔记