8. 先猜,再运行,再改一处
上一章测试全绿,回答的是「这段程序符合哪几条验收条件」。这仍然不等于理解它怎么运转。理解结构是另一件事,需要一套读代码的顺序,但不要求读懂每一行,也不要求记住语法。
这一章就练这套顺序。练习对象是解析函数里最容易出错的一小段:判断价格算不算缺失。
拿一小段来读,先看它的解释
Section titled “拿一小段来读,先看它的解释”下面这一版判断价格缺失的写法,是练习用的对照版本,不是上一章的最终实现:
// 判断价格是否缺失const missingPrice = !price;AI 给它配了一段解释,听起来很顺:
这里用
!price判断价格缺失。当价格为 0 时,!0的结果是true,所以价格为 0 的行也会被标记为缺失,正好满足第 5 章那条验收条件。
解释流畅,还准确引用了验收条件。先别急着信,也别急着运行,进入第一步。
不运行,只根据这段代码和上面的解释,猜一个具体输入的结果。输入取第 3 章那行价格为 0 的报价:
赠品,个,0按解释的说法,这一行的 missing 应该是 true。把这个猜测写下来,待会儿用来对照。
现在真跑一遍,把这一行喂进用了对照版本的解析。实际结果:这一行的 missing 是 false,价格照常显示成 0,没有被标成缺失。
实际和猜测对不上。对不上的地方,就是理解出现缺口的地方,也正是解释骗过了直觉的地方。
针对猜错的这一处,加一句临时诊断,看 price 到底是什么:
console.log(typeof price, price);跑出来是:
string 0price 是字符串 '0',不是数字 0。而非空字符串在这里算「真」,所以 !'0' 的结果是 false,价格为 0 的行不会被标成缺失。AI 的解释说的是数字 0 的情况(!0 确实是 true),可代码走到这一步时,price 还是分隔出来的字符串。解释本身的语气毫无破绽,却对错了对象。
弄清原因后,只改这一处,把字符串和数值两种情况都判进去:
// 价格为空,或数值等于 0,都算缺失const missingPrice = price === '' || price === undefined || Number(price) === 0;重新跑那一行,missing 变成 true,价格 0 的行被标成缺失,和最初的猜测对上了。改动只落在这一行,其余不动,方便看清楚是这一处带来了变化。这一版也正是上一章最终实现里用的写法。
最后基于刚建立的理解,做一个自己的小扩展。比如加一条规则:价格是负数的行也算异常。把这条规则按第 5 章的方式写成验收条件,交给 AI 补实现和测试,再用刚才这套顺序读一遍它改的那一小段,确认改动只落在价格判断上。
这套顺序有个名字:PRIMM
Section titled “这套顺序有个名字:PRIMM”刚才走过的五步,是一套有名字的读码顺序:PRIMM,来自计算机教育研究者 Sue Sentance 等人提出的教学法,五个字母对应五步:
- Predict(先猜):不运行,先预测输出。
- Run(再运行):跑一遍,和预测对照。
- Investigate(探查):针对对不上的地方查清原因。
- Modify(改一处):只改一小处,验证理解。
- Make(做一个):基于理解做一个自己的版本。
这是真实的教学术语,不是本书自造的说法。它对不会写代码的人尤其管用,因为每一步的判断都落在「运行看到了什么」上,不落在「能不能读懂整段语法」上。
把读明白的东西画下来
Section titled “把读明白的东西画下来”把刚才读明白的东西画成一张程序结构图:
输入:一段多行文本(每行:品名,单位,含税价) │关键处理: ├─ 按行拆分,逗号分隔成三段 ├─ 单位为空 → 标记缺失 └─ 价格为空或数值为 0 → 标记缺失 ← 刚才出错又改好的地方 │输出:每行一个对象 { name, unit, price, missing }失败位置:价格判断处,字符串 '0' 与数字 0 混淆这张图记的不是每一行代码,是「数据从哪进、经过哪几处关键判断、从哪出、最容易在哪一处出错」。有了它,下一次这段程序再出问题,能直接指向可疑的那一处,而不必从头重读。
第二幕到这里完整了:从裁决验收条件,到让测试先红,到让 AI 在判定下循环变绿,再到读懂关键的一小段。