🧩 第4章 动手做:写出你的第一批技能
📄 一句话概括

技能不是写出来的,是改出来的。第一版能跑就行,剩下的靠一次次撞错误撞出来。 改的第一步不是改,是把错分成两类:改得不干净的,和压根没改的。这两类修法完全不同。 这一篇拿一个日期规范化的技能跑六个用例:先只对一个,补齐规则后全过,再试边界又崩一次。

📝 本章和前面几章的关系

前三篇各写了一个技能,这一篇讲它们之后怎么长大。 「改完做三步验证」这套做法,第 6 章的测试一节会展开成完整的评分流程。 第 7 章会看到真实的迭代轨迹:数据分析技能从 V1 到 V3,每一版的改动都来自「吃自己的狗粮」。

一、技能是改出来的,不是写出来的

第一版技能的目标只有一个:能跑。 别指望第一版就写得周全。你在写的时候想不到的边界,跑起来三分钟就撞到了。 这也是技能比软件好办的地方:改一版、跑一遍,几秒钟的事。 所以正确的节奏是「写一个能跑的最小版 → 拿真实输入跑 → 记下哪里不对 → 改 → 再跑」。 这个循环里最值钱的一步是「记下哪里不对」。不记,第二次撞到同一个坑你还是不认识它。 记的时候别写「效果不好」,写「输入是什么、得到什么、应该是什么」——这三行才是能改的东西。

二、改的第一步:把错分成两类

把一堆错误摆在面前,第一件事是分类。分错了,修法就会错。

哪一类 长什么样 怎么修
改了但没改干净 格式对了一半,细节没处理 加一步后处理
压根没匹配上 原样输出,一个字都没动 补一条新规则

这两类的区别用一句话就能判断:输出和输入一样吗。一样就是没匹配上。 分类的价值在于:第一类只要在流水线尾巴上加一步统一处理,第二类必须往前补规则。 混在一起看,很容易得出「这个技能不行」的结论;分完类会发现,其实是两三个具体的小问题。 分类还有一个副产品:它能告诉你缺的是哪个方向的规则。 三个错都是同一种格式,说明那个格式压根没写。

三、补规则,然后重跑

补规则有个顺序讲究:先补覆盖面的,再加后处理的。 因为后处理只处理「已经匹配上但没改干净」的,补了也不会救那些没匹配上的。 补完一定要重跑全部用例,不能只看刚改的那一个。 原因很实际:新加的规则可能抢在旧规则前面匹配,把原来对的那几个改坏。 这类「修好一个坏掉两个」的事,在规则串起来的时候特别容易发生。 还有一个经验:能放到最后统一做的一步,就别分散到每条规则里。 比如补零、去空格、转小写这类动作,放在流水线末尾做一次,比在十条规则里各写一遍可靠得多。 写十遍就有十次漏掉的机会,写一遍只有一次。

四、用例永远不够:所以要试边界

六个用例全过了,是不是就完了?不是。 六个用例是你想得到的,真正的坑在你想不到的输入里。 这时候要专门造几个不该被改的输入去撞它。这类输入叫反例,比正例更能暴露问题。 比如「10.3 万平方米」——它长得像日期,其实是数量。 这种「长得像但不是」的输入,是规则型技能的命门。规则越宽,误伤越多。 所以每次补完规则,都要问一句:这条规则会不会碰到别的什么东西。 答案通常是有。那就给它加一个限定条件——在正则里叫反向断言,在业务里叫「什么情况下不适用」。

五、三步验证,一步不能省

改完之后固定跑三步。三步各查一件不同的事,谁也替不了谁。

步 验什么 漏了会怎样
单例验证 刚改的那个错,现在对了吗 白改一场
回归 原来对的那几个,还对不对 修好一个,改坏两个
边界 不该动的东西,动了吗 误伤,比不改还糟

三步里最容易被跳过的是第三步,因为前两步都有明确的「过了没有」。 但第三步的后果最重:误伤是静默的,它不会报错,只会在你最想不到的地方改坏一个数。 「宁可不改,也不要猜着改」,这是规则型技能的通用原则。 所以在写规则的时候,遇到模糊情况,正确的做法是原样输出让人来看,而不是猜一个最可能的。

六、为什么改技能比改软件快

同样是一次小修,改技能和改软件的代价差得很远。

动作 代价
改一行 改文本,不用编译不用重装
验一次 重跑六个用例,一秒
发布 存盘就是发布,没有版本号要走
回滚 git 回退一个提交

快的原因不是技能简单,是它没有编译与分发这两段。 软件改一行要编译、要打包、要重新部署,每一段都可能出岔子;技能改完存盘就生效。 这个差距带来一个反直觉的结论:技能的第一版可以写得更糙。 因为改的成本低,一边用一边改,总代价比一次写到完美更低。 但快也有代价:改得太容易,就容易改乱。所以每次改都要把那三步重跑一遍,一次都不能省。

七、本章小结

第 4 章写了三个技能,最后落到一件事:改。 三个技能的形态不同(词表、模板、流程),但长大方式一样——撞错、分类、补规则、重跑三步。 写作风格教的是「把模糊的标准拆成可数的指标」,会议纪要教的是「把判断压成能跑的校验」, 文章配图教的是「把玄学拆成五个有产出的步」。三件事其实是同一招的三个变体。 这个招式可以一句话概括:别要求模型「做好」,给它一套能照着做、能回头验的动作。 下一章往上走一层:一个技能能干活了,五个技能串起来才能干活干得快。

八、动手 Demo:六个用例只对一个,补齐规则后全过

这一段拿一个「把日期统一成 YYYY-MM-DD」的技能迭代三次:第一版只对一个,补齐规则后全过, 再拿一句不该改的话去撞它,又崩了一次。 六种写法都是真跑,错在哪也是真算出来的,不是编的。

RULES_V1 = [(r'(\d{4})/(\d{1,2})/(\d{1,2})', r'\1-\2-\3'),
            (r'(\d{1,2})月(\d{1,2})日', r'2026-\1-\2')]

for s, want in CASES:
    got = apply_rules(s, RULES_V1)
    print('对' if got == want else '错', s, '->', got)

# 补规则:点号、英文月份;放宽:日 → [日号];最后统一补零
RULES_V3[2] = (r'(?<!\d)(\d{1,2})\.(\d{1,2})(?!\d)(?!\s*[万亿千百十])',
              r'2026-\1-\2')

实跑输出:

--------------------------------------------------------------------
六个用例只对一个,补齐规则后全过
--------------------------------------------------------------------


  ① 先定验收标准
  --------------------------------------------------------------------
  任务:把周报里的日期统一成 YYYY-MM-DD。
  验收标准两条:格式完全一致,年月日不许错。
  ! 这两条得分开验——有的版本格式对了,日期却错了。

  ② 第一版:两条正则
  --------------------------------------------------------------------
| 输入 | 得到 | 判定 |
|---|---|:---:|
| 2026/10/1 提交周报 | 2026-10-1 提交周报 | 错 |
| 10月2日开会 | 2026-10-2开会 | 错 |
| 10.3 上线 | 10.3 上线 | 错 |
| Oct 4 复盘 | Oct 4 复盘 | 错 |
| 10月5号交材料 | 10月5号交材料 | 错 |
| 2026-10-06 已规范 | 2026-10-06 已规范 | 对 |

  六个用例对了 1 个,错 5 个。

  ③ 五个错,其实是两类
  --------------------------------------------------------------------
| 输入 | 得到 | 应该 | 哪一类 |
|---|---|---|---|
| 2026/10/1 提交周报 | 2026-10-1 提交周报 | 2026-10-01 提交周报 | 匹配了没补零 |
| 10月2日开会 | 2026-10-2开会 | 2026-10-02开会 | 匹配了没补零 |
| 10.3 上线 | 10.3 上线 | 2026-10-03 上线 | 压根没匹配 |
| Oct 4 复盘 | Oct 4 复盘 | 2026-10-04 复盘 | 压根没匹配 |
| 10月5号交材料 | 10月5号交材料 | 2026-10-05交材料 | 压根没匹配 |

  「匹配了没补零」2 个,「压根没匹配」3 个。
  前一类是改得不干净,后一类是根本没改。修法完全不同:
  前者加一步后处理,后者得补规则。

  ④ 补两条、放宽一条、加一步补零
  --------------------------------------------------------------------
| 输入 | 输出 | 判定 |
|---|---|:---:|
| 2026/10/1 提交周报 | 2026-10-01 提交周报 | 对 |
| 10月2日开会 | 2026-10-02开会 | 对 |
| 10.3 上线 | 2026-10-03 上线 | 对 |
| Oct 4 复盘 | 2026-10-04 复盘 | 对 |
| 10月5号交材料 | 2026-10-05交材料 | 对 |
| 2026-10-06 已规范 | 2026-10-06 已规范 | 对 |

  从 1 个对变成 6 个对。三处改动是:
  补两条新规则(点号、英文月份);原来只认「日」的那条放宽成「日或号」;
  最后统一补零。
  ! 补零放在最后一步做,比在每条规则里各写一遍可靠——规则一多就会漏。

  ⑤ 再拿一句没写进用例的话试
  --------------------------------------------------------------------
  输入:10.3 万平方米
  得到:2026-10-03 万平方米

  被改了。点号规则分不清「日期」和「数量」。
  ! 这句是我写完用例才想起来的,说明用例永远不够用。

  ⑥ 加点守卫,然后重跑三步
  --------------------------------------------------------------------
  那句数量:10.3 万平方米 -> 10.3 万平方米
  原来六个:对了 6 个

| 步 | 验什么 | 这次结果 |
|---|---|---|
| 单例验证 | 那句数量现在改不改 | 不改了 |
| 回归 | ④ 那六个还过不过 | 六个全过 |
| 边界 | 别的东西动没动 | 没动 |

  守卫就是规则末尾那段反向断言:点号后面跟着量词,就别当日期。
  ! 守卫要写进规则里,不能写在注释里——注释拦不住任何东西。

  ⑦ 为什么改技能比改软件快
  --------------------------------------------------------------------
| 动作 | 代价 |
|---|---|
| 改一行 | 改文本,不用编译不用重装 |
| 验一次 | 重跑六个用例,一秒 |
| 发布 | 存盘就是发布,没有版本号要走 |
| 回滚 | git 回退一个提交 |

  快的原因不是技能简单,是它没有编译与分发这两段。文本改完立刻生效。
  ! 快也有代价:改得太容易,就容易改乱。所以每次改都要把那三步重跑一遍。

第一版两条正则,六个用例只对一个。错的五个不是同一类错。 分完类很清楚:两个「匹配了没补零」,三个「压根没匹配」——前一类加后处理,后一类补规则。 补齐之后六个全过。 三处改动:补点号和英文月份两条规则,把只认「日」的放宽成「日或号」,最后统一补零。 然后拿一句没写进用例的话去撞:「10.3 万平方米」被改成了日期。 加一个限定条件之后,那句数量不再被改,六个用例照样全过——这就是单例、回归、边界三步。 最后一段说清为什么改技能比改软件快:它没有编译与分发这两段,改完存盘就生效。 快也是负担:改得太容易就容易改乱,所以那三步每次都得重跑。

30 秒实验

  1. 给一个你已经写好的技能补三个反例(长得像但不该被处理的那种输入),跑一遍看它误伤几个。
  2. 把你技能里散落在各条规则里的「补零」「去空格」「转小写」挪到流水线末尾,只做一次。
  3. 下一次改技能的时候,逼自己先写下「输入、得到、应该」三行,再动手改。

常见误区

  1. 只看刚改的那个用例。新规则可能抢在旧规则前面匹配,把原来对的改坏。每次都要重跑全部用例。
  2. 以为第一批用例全过就完事了。它只说明你想得到的都对了。 真正的坑在你没想到的输入里,得专门造反例去撞。
  3. 靠「猜一个最可能的」处理模糊输入。 规则型技能的铁律是宁可不改也不要猜着改——误伤是静默的,不会报错,只会在别处改坏一个数。
自测三问(点开对答案) 1. 技能迭代的第一个动作是什么? —— 把错分成两类:改了没改干净的,和压根没匹配上的。前者加后处理,后者补规则。 2. 为什么补完规则要重跑全部用例? —— 新规则可能抢在旧规则前面匹配,把原来对的改坏。修好一个坏掉两个,在规则串联时很常见。 3. 三步验证各自查什么? —— 单例验证查刚改的错有没有修好,回归查原来对的有没被改坏,边界查不该动的有没有被误伤。

第 4 章完。下一章把技能串起来:一个能干活的技能,和五个技能搭成一条流水线,是两回事。

相关笔记