news 2026/10/15 3:55:08

记录里没有「不知道」这个取值时,空值会替你说「一切正常」

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
记录里没有「不知道」这个取值时,空值会替你说「一切正常」

先说一件刚发生的事。

我们修掉了一处缺陷:四种不同的「没有」被压成一个开关,读数里分不出「设计如此」和「出错了」。修完,接着做另一件事。那个新字段落地的第二天,一位读者在评论里,指出了同一处缺陷——并且写下了该怎么改。

不是同一个问题复活。是同一类问题,在我们以为自己已经学会的地方,又长了一遍。

这篇只讲这一件事:记录里没有格子放「不知道」的时候,「不知道」会读成「知道」——而且读成的是那个字段里最强的那句断言。

## 一、四种「没有」,一个开关

先说背景。

AI 写一条记录,我们要留下它「写之前什么样、写之后什么样」的快照。将来要撤销,得知道退回到哪里。

问题出在「抓不到之后那份快照」这件事上。它至少有四种原因:

- 根本没装配抓取器——设计上的可选

- 实体解析不到,回退数据也是空的

- 实体在,但那行查不到——异常

- 抓取过程抛错——错误

前两种是设计选择的结果,后两种是真出了事。

而我们当时只有一个布尔字段。四种原因,一个开关。

于是有人问「这批快照为什么是空的」时,答不上来:分不出是设计如此,还是坏了。更糟的是方向——那个开关默认是 false,读起来就是「快照完好」。

「没有」不是一个值,是四种不同的值。压成一个布尔,就是让最强的那种读法,替所有情况作答。

后来我们把它拆成四个成因,分别落列、分别可读。拆的时候还发现:最初拟的四个名字里,有两个根本不属于这一类——一个在另一条路径上(那是「写之前」那张表),另一个是机制而不是成因。所以最后落地的四个,和一开始想的四个并不一样。

## 二、第二天,在我们刚写完的地方

修完这处,接着做另一件事:撤销之前,记下「这一次我打算比对哪些成员」。

承诺在这里是有意义的——撤销前是承诺,撤销后是实况。两者对差,才能说出「我承诺比对的某个成员,其实比不了」。落地的做法是:在组根行上放一个列,装这次的差集;这次没有差集,就把列写空。

看起来没问题。然后有人在评论里写下这段:

> 一个按原因分列的字段放在成员行上,会被最后一次撤销尝试覆写,所以第二次尝试会擦掉第一次的「失约」。追加式能让它们分开。

他是对的。而且比他说得还糟一点。

那一列在没有差集时会被写成空;而那个「空」同时表示两件事——「最近一次比对没有差集」,和「这一行从来没有被撤销过」。所以:

- 第一次撤销发现「承诺了却比不到」——记下了

- 第二次撤销一切正常——把第一次的记录写成了空

- 事后读这一行:空。读不出「曾经有过一次失约」,也读不出「它被清空过」

一次成功的重试,抹掉了前一次的失败记录。

要说清楚的是:这个清空是有意的。理由写在当时的设计记录里——「陈旧的差集不该活过产生它的那次比对」。这个理由本身不算错。错的是那个「空」没有第二个意思可用。

然后我们照他说的改了。

改成每一次走到比对的尝试各占一行,不再共用一个格子。两个读数在写的时候就分开:这一行是「查出了差集」,还是「查过、没有差集」。于是:

- 后一次尝试不再抹掉前一次——它写自己那一行

- 空历史(没有任何一次尝试走到过承诺)与 「查过、干净」 不再是同一个读数

- 那条旧规则——「陈旧的差集不该活过产生它的那次比对」——被撤回了。它错在用同一个格子表达两件事,不是错在「不该留陈旧的东西」

那句旧列没删,作为遗留条目继续读,标注「写于何时不可考」——删掉它,恰好会抹掉这一整段争议的证据。

## 三、同一套系统里,有一处做对了

上面两处都是「没有格子放不知道」。但同一套系统里,有一处留了格子。

撤销写到外部系统的时候,我们只能把请求发出去,终态在对方手里。对方返回 2xx,只证明「请求到了」,不证明「对方撤了」。所以那一条不写「已撤销」,写「补偿已请求,结果未知」——一个专门的读法。

效果很直接:没人会把它误读成完成。界面照实写「结果在目标系统」,不必粉饰。

所以这件事不是做不到。是有没有意识到要给「不知道」留位置。留了,它就不塌;没留,它就归到最强的那种读法上。

## 四、为什么难

难在这个默认行为是静默的。

每一个字段都在替你做决定,而且都用最省事的那种读法:

- 空值,默认读作「无此项」

- 空集合,默认读作「没有差异」

- false,默认读作「未标记」,进而读作「正常」

这三种读法在绝大多数情况下是对的。问题在剩下的那一小部分:当「没有」和「不知道」共用同一个值,你就得到了一个会撒谎的字段——而且它平时不吭声。

更麻烦的是:修一处不会让这一类消失——包括修它自己的时候。

说一件就在那次修复里发生的事。改完之后,「没有差集」有了自己的读法。但那个判据函数返回空,其实有两种情形:真的全比过了,和压根没有可承诺的东西(那时连「比对」都无从定义)。第一种写「查过、干净」是对的;第二种要是也写「查过、干净」,就等于替一次从未发生的检查作证。

我们差点就这么写了。发现之后改成:什么都没承诺,就不落行。

所以这一类缺陷的入口在每一个「没有」上——你修一个,它就在你写修复代码的那一行里等你。

## 五、一个你可以自己做的检验

不必问「我的记录全不全」,先问一个更基础的问题:

把你系统里每一个表示「没有 / 未知 / 空」的值列出来,逐个问:它读成什么?

凡是同时承载两种含义的,就是一处潜在的谎报。典型的三类:

| 写法 | 它同时意味着 |

|---|---|

| 空值 | 「没有这一项」/「查过,结果为空」/「没查过」 |

| 空集合 | 「确实没有」/「没能读出来」 |

| false / 缺省值 | 「未标记」/「标记为否」 |

判据很简单:这个值是空的时候,读者还能不能说清「为什么空」?说不出来,就是这个字段在替他猜。

## 结语

这类问题不好找,因为它不报错。系统照常运行,界面照常显示,只是有那么一格在说它不是的话。

而它最容易出现的地方,恰恰是你刚刚修完的地方——因为那一刻,你正忙着「已经知道该怎么做」了。

交代一下我们自己的状态:这个模式我们现在认识了,也修了两处——第二处的形状是读者给的。但我们没有做过一次全盘体检:那三类「没有」在我们的记录层里到底有多少处、分别读成什么,我们没有清单。

所以这篇不收在一个结论上。你拿上面那个检验去照自己的系统,多半能找到几处;我们这边,只知道还没找到完。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 3:53:50

嵌入式驱动开发:量产级工程化实战 第 18 篇:采集任务与传感器驱动

采集任务为什么容易出问题 采集任务看起来很简单:定时读取传感器,处理数据,发送出去。但在实际项目里,采集任务是最容易出问题的任务之一。 原因有几个: 第一,采集任务涉及的外设最多。 加速度计(SPI/I2C)、温度传感器(I2C/单总线)、ADC(如果测电池电压),每一个…

作者头像 李华
网站建设 2026/10/15 3:53:48

嵌入式驱动开发:量产级工程化实战 第 15 篇:通信错误排查工具链——从“抓不到“到“看得见“

一次"抓不到"的通信丢包 某款工业设备通过 RS485 与上位机通信,协议是 Modbus RTU,波特率 19200。客户反馈:偶尔丢包,概率大概每天几次。设备重启后恢复,但过几天又会出现。 排查的第一步是抓包。工程师拿了一个 USB 转 RS485 的适配器,接到总线上,用串口助…

作者头像 李华
网站建设 2026/10/15 3:52:48

PoB离线Build规划:天赋树与伤害计算一次算清

PoB离线Build规划:天赋树与伤害计算一次算清 【免费下载链接】PathOfBuilding Offline build planner for Path of Exile. 项目地址: https://gitcode.com/GitHub_Trending/pa/PathOfBuilding 凌晨两点,你又一次在天赋树上手动数格子:…

作者头像 李华