🧩 第6章 工程化:从需求到上线的技能开发全流程
📄 一句话概括

四个招式:记踩坑点、可容错、可扩展、跨对话记忆。每一招都是把「将来会出的问题」提前写进文件。 实测一个只按列号取值的小脚本:六种输入对两个、错一个、崩三个;加完四招对五个、主动停下 1 个。 最危险的不是崩,是静默算错——空表给你一个 0,看着像结果。

📝 本章和前面几章的关系

上一篇定的是「做什么」,这一篇定的是「做成什么样才算靠得住」。 第 3 章的安全红线在这里变成了具体写法:不静默、不乱猜、出错就说清楚。 第 6.4 节的测试会拿这里的六种输入当考题:容错做得对不对,一跑就知道。

一、记踩坑点:把踩过的坑变成文件

第一招最省力:把你踩过的坑写成一份文件,放在技能包里。 不写在脑子里,也不写在聊天记录里——写进 references/pitfalls.md。 每条坑写三行:什么情况、当时怎么坏的、以后怎么防。三行够了,写多了没人看。 比如「某次列顺序从月份在前改成金额在前,脚本按列号取值,直接把品类当金额加了」。 这条写下来之后,下次改这个技能第一件事就是翻这份文件,不用重新踩一遍。 踩坑点文件的价值不在记录,在提醒。它的读者是三个月后的你自己。

二、可容错:假定一定会出意外

第二招是心态上的:假定输入一定会出问题,不是「一般不会」。 常见的意外就那么几类:文件是空的、列名改了、值里混了千分位、编码不是 UTF-8、单位写成「万」。 处理方式只有两种:能算就算对,算不了就说清楚再停。 最不能接受的是第三种:算出一个像结果的错数。 空表加出来是 0,看着像个正常结果,其实一句提示都没有——这是最坏的一种。 所以可容错的判断标准只有一条:出错的时候,人能不能一眼知道出错了。

三、可扩展:会变的东西别写死

第三招问一个问题:这个东西明年会不会变。会变的就别写死。 「手机、配件、平板」会变,所以正文里写「按品类列现有取值」,不列举。 四段格式不会变,所以可以写死,而且应该写死——写死了才稳定。 所以可扩展不是「什么都别写死」,是分清哪些该稳、哪些该松。 结构松、内容松:加一个品类、加一个数据源,不用改技能。 接口紧、格式紧:输出的形状必须定死,不然下游接不住。 一句话总结:形状定死,取值放开。这两件事经常被搞反。

四、跨对话记忆:记住上次

第四招最少人做,但很实用:把上次跑了什么写下来。 写什么?文件名、跑的时间、算出来的关键数、有没有报警。存成 state/last_run.json。 下次跑同一份文件,技能会说一句「这份上周跑过,结果一样/不一样」。 「不一样」这三个字最值钱——数据没变而结果变了,说明中间有东西被改过。 这就是跨对话记忆的真正用途:不是省一次输入,是给结果加一条时间线。

五、四招合起来的效果

把四招加在一个只按列号取值的小脚本上,跑同样六种输入。

版本 对 错 崩 主动停
第一版 2 1 3 0
加完四招 5 0 0 1

对的数量从 2 到 5,但更重要的变化在最后两列:错和崩都没了,多了一个「主动停」。 「错 0 崩 0 停 1」比「对 5」更值钱。因为它意味着这个脚本再也不会偷偷给你一个错数。 剩下的那 1 个「停」是空表。空表本来就该停,不该算。

六、设计检查清单

查什么 合格的样子 不合格的样子
踩坑点 references 里有五条以上 全靠记忆
容错 缺列、空表都有话说 直接崩或静默算错
可扩展 加一个取值不用改技能 取值写死在正文里
跨对话记忆 有上次运行的记录 每次都像第一次

四条里前两条是底线,后两条是加分。写第一个技能时只做前两条也不丢人。 但四条加起来的工作量,比不上一次静默算错带来的返工。

七、动手 Demo:六个输入,第一版对 2 个,加了四招对 5 个

这一段拿一个「按品类汇总金额」的小脚本,用六种输入各跑一次。 结果分三档:对、错(给了个数但不对)、崩(抛异常)。第二版还多一档:主动停下。

def _v1(rows, cols):          # 按列号取值,什么都不查
    return sum(int(r[2]) for r in rows)

def _v2(rows, cols):          # 认列名、认千分位、认「万」、出错就停
    cs = [c.strip() for c in cols]
    for c in ['月份', '品类', '金额']:
        if c not in cs:
            raise ValueError('缺列:%s' % c)
    if not rows:
        raise ValueError('空表,先确认是不是拿错文件')

实跑输出:

--------------------------------------------------------------------
六个输入,第一版对 2 个,加了四招对 5 个
--------------------------------------------------------------------


  ① 先看第一版
  --------------------------------------------------------------------
| 输入 | 结果 | 它说什么 |
|---|:---:|---|
| 正常三行 | 对 | 1800000 |
| 列顺序改成金额在前 | 崩 | ValueError: invalid litera |
| 空表 | 错 | 给了 0,本该报错 |
| 金额带千分位 | 崩 | ValueError: invalid litera |
| 金额写成 1.2 万 | 崩 | ValueError: invalid litera |
| 表头多了空格 | 对 | 1800000 |

  六种输入对了 2 个,错了 1 个,崩了 3 个。
  ! 第一版按列号取值。「空表」它给了个 0,看着像结果,其实是静默算错。

  ② 四招各管什么
  --------------------------------------------------------------------
| 招式 | 干什么 | 这次加在哪 |
|---|---|---|
| 记踩坑点 | 把反复犯的错写下来 | 列顺序变过,这次先认列名 |
| 可容错 | 假定一定会出意外 | 缺列、空表、格式怪,都要有话说 |
| 可扩展 | 随时能加新东西 | 按数据源分节,加一个源加一节 |
| 跨对话记忆 | 记住上次做了什么 | 上次跑过同一份就提示一句 |


  ③ 加上四招,再跑同样六种输入
  --------------------------------------------------------------------
| 输入 | 结果 | 它说什么 |
|---|:---:|---|
| 正常三行 | 对 | 1800000 |
| 列顺序改成金额在前 | 对 | 776000 |
| 空表 | 停 | 空表,先确认是不是拿错文件 |
| 金额带千分位 | 对 | 1588000 |
| 金额写成 1.2 万 | 对 | 12000 |
| 表头多了空格 | 对 | 1800000 |

  对了 5 个,崩了 0 个,主动停下 1 个。
  第二版没有「错」这一档:算不出来就停下,不给你一个像结果的错数。
  ! 这就是可容错的意思:不是什么都算得出来,是出了事说清楚再停。

  ④ 报错和算错,差得很远
  --------------------------------------------------------------------
| 行为 | 你怎么知道 | 代价 |
|---|---|---|
| 算错 | 给你一个数字 | 你得先怀疑它,再查半天 |
| 报错停下 | 告诉你空表或缺列 | 补一下再跑一遍 |

  第一版那 1 个「错」里,最坏的不是崩,是空表给 0——它一句提示都没有。
  静默算错最危险:你也不知道该怀疑它。

  ⑤ 记踩坑点和跨对话记忆,落到文件上
  --------------------------------------------------------------------

  [技能包结构 · 可直接照建]
  | references/pitfalls.md     五条历史上真踩过的坑
  | scripts/analyze.py         带校验的脚本
  | state/last_run.json        上次跑了哪份、什么时候、算出多少
  这三样就是四招的落点

  踩坑点写成文件,下次改技能先翻一遍,不用重新踩。
  上次跑了什么写成文件,第二次跑同一份时会说「跟上次一样」,省一次核对。

  ⑥ 可扩展怎么留口子
  --------------------------------------------------------------------
| 写法 | 具体 | 后果 |
|---|---|---|
| 坏做法 | 把「手机、配件、平板」写死在正文里 | 加一个品类改三处 |
| 好做法 | 正文只说「按品类列现有取值」 | 加品类不用改技能 |

  留口子的判断很简单:这个东西明年会不会变。会变的就别写死。

第一段第一版跑六种输入:对 2 个、错 1 个、崩 3 个。 错的那个是空表——它给了个 0,看着像结果,其实一句提示都没有。这就是静默算错。 第二段是四招各管什么:记踩坑点、可容错、可扩展、跨对话记忆。 第三段加完四招再跑:对 5 个、错 0、崩 0、主动停下 1 个。 第二版没有「错」这一档了——算不出来就停,不给你一个像结果的错数。 第四段对比报错和算错:报错告诉你空表或缺列,算错要你先怀疑它再查半天。 第五段把四招落到文件上:references/pitfalls.md、scripts/analyze.py、state/last_run.json。

30 秒实验

  1. 给你现有的脚本喂一个空文件和一个列名改过的表,看它是崩、是静默算错,还是说清楚再停。
  2. 写三条踩坑点,每张三行:什么情况、怎么坏的、以后怎么防。放进技能包。
  3. 找一处「把取值写死在正文里」的地方,改成「按现有取值列」。

常见误区

  1. 只防崩溃,不防静默算错。崩了你知道要修,算错要等别人发现。空表给 0 比抛异常危险得多。
  2. 以为可扩展就是什么都别写死。形状要定死、取值要放开。 把这两件事搞反的技能,既不稳定也扩展不了。
  3. 踩坑点只记在脑子里。三个月后你就是那个「又一个不知道这事的人」。 写进文件,读者是未来的自己。
自测三问(点开对答案) 1. 为什么「错 0 崩 0 停 1」比「对 5」更值钱? —— 因为它意味着脚本再也不会偷偷给一个错数。停是明确的,错是静默的。 2. 可扩展的正确做法是什么? —— 形状定死、取值放开。输出格式必须稳定,可选的取值不要列举在正文里。 3. 跨对话记忆的真正用途是什么? —— 不是省一次输入,是给结果加一条时间线。数据没变而结果变了,就是报警信号。

下一篇讲实现:你决策、智能体执行,分三步比一次生成多命中三条要求。

相关笔记