四个招式:记踩坑点、可容错、可扩展、跨对话记忆。每一招都是把「将来会出的问题」提前写进文件。 实测一个只按列号取值的小脚本:六种输入对两个、错一个、崩三个;加完四招对五个、主动停下 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 秒实验
- 给你现有的脚本喂一个空文件和一个列名改过的表,看它是崩、是静默算错,还是说清楚再停。
- 写三条踩坑点,每张三行:什么情况、怎么坏的、以后怎么防。放进技能包。
- 找一处「把取值写死在正文里」的地方,改成「按现有取值列」。
常见误区
- 只防崩溃,不防静默算错。崩了你知道要修,算错要等别人发现。空表给 0 比抛异常危险得多。
- 以为可扩展就是什么都别写死。形状要定死、取值要放开。 把这两件事搞反的技能,既不稳定也扩展不了。
- 踩坑点只记在脑子里。三个月后你就是那个「又一个不知道这事的人」。 写进文件,读者是未来的自己。
自测三问(点开对答案)
1. 为什么「错 0 崩 0 停 1」比「对 5」更值钱? —— 因为它意味着脚本再也不会偷偷给一个错数。停是明确的,错是静默的。 2. 可扩展的正确做法是什么? —— 形状定死、取值放开。输出格式必须稳定,可选的取值不要列举在正文里。 3. 跨对话记忆的真正用途是什么? —— 不是省一次输入,是给结果加一条时间线。数据没变而结果变了,就是报警信号。下一篇讲实现:你决策、智能体执行,分三步比一次生成多命中三条要求。
- ☐ (本篇完,下一篇 6.3 实现:人机协作创造技能)