最后一个知道完整答案的人

文件保存成功。
日志里清清楚楚地写着:
saved 14,276 total
只是昨天,这个数字还是 28,060。
笔者最近在整理一个公开网页资料库。前面几年已经陆续收好,每一条都有正文。机器为了这些数据断断续续跑了很多天。
接下来只是想再补一年。
2016 年预计有 1,454 条,所以事情本来应该是一道很简单的加法:
28,060 + 1,454 = 29,514
最后变成了 14,276。
更奇怪的是,文件并没有坏。
它可以正常打开,格式正确,每一条记录都能读。没有乱码,没有半截文件,也没有报错。
如果不是还记得昨天的数据量,甚至很难意识到出了事。
事情从一次很普通的卡顿开始。
Codex启动的采集任务跑到一半不动了。Codex 关掉后台窗口,准备重新启动。
窗口确实关了,然而Codex没有意识到里面那个 Python 程序却没有真正退出。
于是新的任务重新启动以后,后台其实有两个程序,都在处理硬盘里同一个数据文件。
真正麻烦的地方,是那个文件原来的保存方式。
程序每采集完一批,就把内存里的全部数据重新写回硬盘里的文件。两万八千多条正文加起来大约 1.4 GB,完整写一遍需要一点时间。
这是个很简单、也很常见的写法。
只是它有一个问题。
写文件的时候,旧版本已经开始被覆盖,新版本却还没有写完。
有点像维护一本很厚的账。
每次更新,不是在简单最后补几页,而是把桌上的旧账先记一份在脑子里然后擦掉,再从第一页开始重新抄一本并加入新的内容。
而抄到一半的时候,新账本看起来依然很正常。字迹完整,页码连续,随便翻一页都没有问题。
它唯一的缺点,是后半本还没抄回来。
根据后来留下的数字,最可能发生的事情就是这样。
旧程序正在重新写那 28,060 条数据,刚写到 13,826 条时,新程序打开了同一个文件。
对新程序来说,它看到的没有任何异常。
13,826 条记录,格式正确,全部能读。
它不知道几个小时前这里还有 28,060 条,于是把眼前这些当成了完整的历史数据,又加入自己刚刚新采集到的 450 条:
13,826 + 450 = 14,276
然后认真保存。
于是最后留下来的,不是一个损坏的文件。
而是一个非常完整、非常健康、只是少了一半内容的文件。
后来回头看,这件事其实本来很好避免。
文件更新时,可以先在旁边写好一份新的,确认完整以后,再把旧版本换掉。至少不应该让一个只写了一半的文件,有机会被另一个程序当成正式版本读进去。
数据库领域早就有很成熟的办法处理这种事情。
只是当时没人特别去想。
Codex 要做的是继续采集数据。原来的代码能跑,保存也一直成功。任务很多,上下文也很多,一个不起眼的文件写入方式,没有理由突然成为注意力中心。
直到有一天,这个原本不起眼的选择突然变得很贵。
笔者发现数字不对以后,要Codex 检查后台,很快Codex发现可能同时跑着两个 Python 程序。
接下来Codex的动作也很自然:
先把俩进程都关掉。
别再让它们继续互相干扰。
两个进程被结束以后,机器终于安静了。
正心疼好多天的活被意外删除了,笔者突然意识到一个问题。
等一下。
那个老程序启动的时候,不是已经把完整的 28,060 条数据读进内存了吗?
很可能是。
磁盘上的文件后来虽然已经少了一半,老程序自己的内存里,却未必跟着变少。
换成刚才的账本故事,就是桌上的正式账已经抄坏了,但旁边那个老会计手里,很可能还拿着抄写前的完整底稿。
大家发现账乱了,于是赶快清场。
然后把老会计也解雇了。
等办公室终于安静下来以后,才突然有人想起来:
刚才被赶走的那个人,好像是最后一个还拿着完整账本的人。
可这时已经晚了。
程序一关,对应的内存空间也就被回收。
第一次Codex犯错误,让磁盘上的数据少了一半。
第二次Codex犯错误,则是把最后一次恢复的机会也一起清掉了。
这件事倒没有让笔者觉得 Codex 不好用。
恰恰相反。
现在的 Coding Agent 已经足够好用,好到很多过去需要自己一行一行写、一遍一遍检查的东西,真的可以放手让它往前做。
它会写程序,会启动任务,会看日志,会发现异常,会重跑,也会帮你清理后台留下来的进程。
很多时候,它比人快得多。
但也正因为事情变得这么顺,有些原本会在亲手实现过程中顺便想一遍的问题,反而很容易一起滑过去。
比如这一次,真正重要的问题其实只有一句:
这份文件如果只写到一半,会发生什么?
当时没人问。
不是因为这个问题很难。
而是因为还有太多别的事情要做,而且程序一直工作得很好。
Codex 也不会天然知道,眼前这份文件和一个随时可以重新生成的临时文件有什么不同。除非上下文里已经把这件事变成一个需要特别保护的边界,否则一个简单、快速、平时也完全够用的实现,很自然就会被保留下来。
人自己写一次性脚本时,其实也常常如此。
只是 Coding Agent 让实现变得更快了。很多以前会因为麻烦而停下来想一想的地方,现在几分钟就过去了。
有时候这是巨大的好处。
有时候,也会把一个原本不起眼的小决定,一路带到很远的地方。哎,或许如果人来做,想到这1.4GB数据前前后后采集了好多天,会心疼三秒从而可能会采取防御性保存的方法。
好在数据最终还能重新采集。
只是又要花不少时间,而且重新拿到的网页,也未必和那一天完全一样。
现在回想起来,最让人记住的倒不是 28,060 为什么变成了 14,276。
而是机器终于安静下来以后,才突然意识到:
最后一个知道完整答案的人,刚刚被我们解雇了。