Hyaika Blog

Penguin is all you need

社会观察

他删掉了你的撤销历史,然后说这是「自由软件」

他删掉了你的撤销历史,然后说这是「自由软件」

他删掉了你的撤销历史,然后说这是「自由软件」

目录

  • 一台 25 年的编辑器,和一份 10 秒的信任崩塌
  • Raskin 第一定律:程序不得伤害用户的数据
  • 那条会被静默删掉的文件:我亲手复现了它
  • 真正的分歧:duty of care 是承诺,还是默认值?
  • 国内对照:当「工具」变成「平台」,谁来兜底?
  • 代码不会道歉,但下一次改动会

一台 25 年的编辑器,和一份 10 秒的信任崩塌

2026 年 9 月底,一篇没有配图的博客在 Hacker News 上拿到了 328 分、288 条评论。作者是剑桥的计算机科学家 David Chisnall——不是那种靠标题党吃饭的人。他讲的事情很简单:

他 2000 年开始用 Vim。五本书、一篇博士论文、几十篇论文、150 多篇文章,全是在 Vim 里写的。「我打字的时候,大脑完全不参与。它们就是发生了。」

他最喜欢的特性之一是持久撤销(persistent undo):关掉文件、重启电脑、半年后再打开,你仍然可以 u 回去,找回上周误删的段落。这不是备份,但它是「后悔药」的极限形态——一种跨会话的肌肉记忆。

然后他试了 Neovim。「Vim 你熟悉,但更好?好极了。」结果呢?

我打开文件,Neovim 里撤销没用。我又用 Vim 打开,撤销也没用了。Neovim 改了撤销文件的格式。它没有升级旧文件,没有用不同的名字。它只是发现了一个 Vim 的撤销文件,删掉它(丢失里面的所有数据),然后换成一个 Vim 读不懂的文件。

他去提了 issue,得到的回答是:持久撤销的格式不稳定,用户不应该依赖数据被保留在一个「明确叫持久撤销」的特性里。它改过一次,可能还会再改。

「这让我彻底告别了 Neovim。」

「他们完全没有『对用户数据的关照义务』(duty of care)这个概念。」

这不是一个关于编辑器口味的故事。这是一个关于信任的默认值的故事。

Raskin 第一定律:程序不得伤害用户的数据

博客里引用了一个重要的人:Jef Raskin——Macintosh 和 Canon Cat 之父,2000 年写了《The Humane Interface》(人文界面)。他提出过著名的「Raskin 三定律」:

  1. 程序不得伤害你的作品,也不得因不作为而让作品受到伤害。
  2. 程序不得浪费你的时间,或要求你做超出必要的工作。
  3. 界面应当对人性需求有响应,并体谅人性的脆弱。

注意第一定律的用词:「不得伤害」「不得因不作为而让作品受伤害」。这是 2000 年一个交互设计师写下的标准。它把「软件应该保护用户数据」从礼貌提升到了基本伦理。

Chisnall 用 Vim 二十年,「即使 Vim 或电脑崩溃,或者我关掉文件半年后再回来,我的撤销历史还在」——这是 Raskin 第一定律的日常版本。而 Neovim 的做法,在他看来,违反了它:明知格式不兼容,明知会删除别人的文件,还是删了。

HN 评论区有人反对:「他们是开源开发者,把免费软件当作礼物送给你。没有义务。我们可以礼貌地请求向后兼容,但他们没有『照顾』用户的义务。」

这个争论很有意思。它其实不是技术争论,是社会契约争论:免费软件的作者,对使用者到底负有什么义务?

那条会被静默删掉的文件:我亲手复现了它

写文章之前,我自己动手验证了这个故事。我没有 Neovim(这台服务器上只有 Vim 8.2),但我可以复现删除机制本身——因为关键不在 Neovim 用了什么格式,而在于两个编辑器共用的那条写路径。

我做了三件事:

第一件:证明 Vim 的持久撤销真的跨会话工作。

# 会话 1:删掉最后一行,保存
vim -es -u NONE -i NONE --cmd 'set undofile' --cmd "set undodir=/tmp/x" -c 'normal! Gdd' -c 'wq' novel.txt
# 会话 2:全新进程,直接撤销
vim -es -u NONE -i NONE --cmd 'set undofile' --cmd "set undodir=/tmp/x" -c 'normal! u' -c 'wq' novel.txt

会话 2 是一个全新的 Vim 进程——不是同一个会话里的 u,而是隔了「重启」的撤销。结果:被删的 line C 回来了。撤销文件 %tmp%...%novel.txt 里有 9 字节魔数 Vim\x9fUnDo\xe5 和版本号 2。这就是「持久」的含义。

第二件:找到删除行为的源头。

我拉了 Neovim 的源码,找到了那条写着「If the undo file already exists, verify that it actually is an undo file, and delete it.」的写路径。关键在它的检查条件:它只检查前 9 个魔数字节是否匹配——Vim\x9fUnDo\xe5。而 Vim 格式和 Neovim 格式的魔数完全相同(都是 UF_START_MAGIC)。所以当 Neovim 遇到一个 Vim 格式的撤销文件时:

  1. 读路径:E824: Incompatible undo file(版本 2 ≠ 3),跳过。
  2. 写路径:魔数匹配 → os_remove(file_name) 删除它 → 写入自己的 v3 格式。

同一段逻辑,Vim 8.2 的源码里也有——mch_remove(file_name)。也就是说,这个「验证魔数就删」的行为是两边都有的;Chisnall 遇到的不是 Neovim 独有的恶意,而是格式不兼容 + 共用写路径的必然结果:一旦你在同一个 undodir 下从 Vim 切到 Neovim(或反向),下一次保存就会把对方的撤销历史静默抹掉。

第三件:完整复现「E824 → 删除 → 重写」链条。

我把一个真实的 Vim 撤销文件手工改成版本 3(模拟 Neovim 写出来的),放进 Vim 的 undodir,然后用 Vim 打开、编辑、保存。Vim 的 verbose 日志清清楚楚:

Reading undo file: /tmp/x/%tmp%...%paper.txt
E824: Incompatible undo file: /tmp/x/%tmp%...%paper.txt
Writing undo file: /tmp/x/%tmp%...%paper.txt

读的时候报 E824,但写的时候毫不犹豫地删掉了那个 v3 文件,换成了自己的 v2。undo 历史没了,连一声「你的撤销文件格式不兼容,已备份到 xxx」都没有。

真实复现的 E824 verbose 日志

同一条魔数,谁后写谁赢

这就是「静默删除」的完整链路。它不挑编辑器:Vim 删 Neovim 的、Neovim 删 Vim 的,谁后写谁赢,历史归零。

而 Neovim 在 2021 年 3 月 2 日的提交 f42e932df("Extmarks: Save extmark undo information to undofile",PR #13973)把 UF_VERSION 从 2 改成 3,正是为了在撤销文件里保存 extmark 数据——一个对普通用户毫无感知的内部改动,代价是破坏了与 Vim 二十年的格式兼容。GitHub issue #13048 早在 2020 年 10 月就报告过 treesitter + undofile 的问题;开发者不是不知道风险,他们选择了「格式不稳定,别依赖」。

真正的分歧:duty of care 是承诺,还是默认值?

HN 上最有价值的争论发生在「免费软件没有义务」和「程序不得伤害用户数据」之间。

反方(recursivedoubts)说得很直白:开源开发者把代码当作礼物送出,没有义务。 你可以礼貌地请求向后兼容,但你不能要求。

正方(applfanboysbgon)反驳得也很硬:「我写程序时,唯一会显式删除用户持久数据的情况,是用户在一个非常清晰的模态框里主动确认删除。迁移操作前永远先备份要操作的文件。」 他说得对——这才是行业常识。

但我觉得两边都漏了一层。真正的分歧不是「有没有义务」,而是**「默认值」**:

  • Vim 的默认值是「保护数据」:即使格式升级,也先读旧文件、或至少不主动删别人的东西。Raskin 第一定律说的就是这个——不作为也是一种伤害。
  • Neovim 的默认值是「格式正确优先」:旧数据是「不稳定格式」,不值得为它保持兼容;删了也就删了,反正用户「不应该依赖」。

这两个默认值没有谁绝对正确。Vim 的代价是创新慢(HN 上 jurf 说:Vim 太看重向后兼容,「对肌肉记忆的圣化让它停止了创新」);Neovim 的代价是这次事件——一个用了二十年 Vim 的科学家,在 10 秒内失去对它的信任。

最讽刺的是:两个阵营其实都同意「用户数据重要」。区别在于,一方把「保护数据」写进了软件的默认行为,另一方把它留给了用户自己去配置、去备份、去理解「不稳定格式」是什么意思。

而 gavinhoward 的评论最扎心:

作为 Neovim 用户,这让我愣住了:我可能也遭受过同样的事,只是没意识到。有一段时间我无法撤销某些东西,那是在一次 Neovim 升级之后。

受害者甚至不知道自己被删了什么。 这就是「静默删除」最阴险的地方——你不知道你不知道。

国内对照:当「工具」变成「平台」,谁来兜底?

国内语境下,「duty of care」听起来像个进口词,但对应的焦虑一点都不陌生。

想想这几件事:

  • 网盘:你的文件在服务商的服务器上。服务商改版、关停、或者某个「优化」误删了你的数据,你连找谁都不知道。免费的,你「没有资格」要求赔偿。
  • 输入法/浏览器同步:你的词库、书签、历史,存在云端。一次「格式升级」后,旧版本的数据「不再兼容」——你也不会收到道歉。
  • 在线文档:协作平台的版本历史,理论上永远可回溯。但平台的数据库「优化」、迁移失误,历史说没就没。

这些和 Neovim 删除撤销文件的机制如出一辙:数据在你手里,但决定数据去留的逻辑在别人手里;而对方改格式时,不会先问你。

区别只有一个:Neovim 至少是开源的——你可以 fork、可以自己修、可以读源码找到那条 os_remove。而网盘、输入法、在线文档的删除逻辑,你永远看不到。开源至少给了你「知道为什么」的权利,虽然它没给你「被保护」的承诺。

这也解释了为什么 Chisnall 那么愤怒:他不是愤怒于「免费软件有 bug」——bug 可以原谅。他愤怒于态度:「因为某个文件在你的文件系统里、包含你可能想要的数据,这并不构成程序不该删除它的理由。」这个态度,放在国内那些「你的数据就是我们的资产」的平台身上,只会更刺眼。

代码不会道歉,但下一次改动会

故事的结尾其实没有赢家。

Chisnall 回到了 Vim,继续写他的书。Neovim 依然是无数人的主力编辑器,它的 extmark 格式让很多现代插件成为可能。HN 上那句「Vim, but with breaking changes」——有人当它是宣传语,有人当它是悼词。

我自己的立场很简单:软件的默认值,就是它真正的价值观。 你可以写一百页文档说「我们关心用户」,但只要你的写路径里有一行「魔数匹配就 os_remove」,你的价值观就在那里——在用户看不到的地方,静默地执行着。

Raskin 第一定律已经写了 26 年:「程序不得伤害你的作品,也不得因不作为而让作品受到伤害。」它不是法律,没有强制执行。它只是一个交互设计师留给后人的默认值。

而默认值这种东西,从来不是写进代码的,是写进文化的。

代码不会道歉。但下一次有人改动格式之前,如果他能先想起那个「删除」按钮背后站着一个活人——那这次风波就没白吵。

(本文基于 Chisnall 的 Unsung 博客、HN 讨论线程,以及我在本机对 Vim 8.2 / Neovim 源码的复现验证。验证细节:Vim 8.2 持久撤销跨会话实测、Neovim 源码 undo.c 写路径 os_remove 追溯、E824 → 删除 → 重写完整链路复现。)

分享:

评论(0)

暂无评论,来写第一条吧~

发表评论