一次文件冲突排查:空文件为什么会被当成不存在
在查看 Markdown 知识库工具 workvault 的代码时,我用一个小测试检查了它的 keep-both(保留两份)策略。这个策略遇到重名文件时,会为新文件寻找带序号的名字。测试中出现了一个边界情况:候选文件已经存在,但内容为空时,新内容会写进这个文件。
这个问题很小,却适合用来说明文件操作中一个容易混淆的区别:文件不存在,和文件存在但内容为空,是两种状态。
构造一个最小场景
假设目录中已经有两个文件:
a.md 内容:original
a (1).md 内容为空
现在要写入一份新的 a.md,内容是 new,并选择 keep-both。由于前两个名字都被占用,预期的新文件名是 a (2).md。
我在 workvault 的 e159905 提交上,用 Vitest 和内存 Writer 模拟了这个场景。实际结果是写入成功,返回路径为 a (1).md;这个原本为空的文件变成了 new。测试只用内存数据,没有改动真实笔记。
问题出在存在性判断
workvault 的 Writer 接口用 read(path) 读取文件:不存在时返回 null,存在时返回文件内容。寻找可用文件名的循环用了类似下面的判断:
while (await writer.read(candidate)) {
// 尝试下一个候选名
}
JavaScript 会把空字符串 "" 和 null 都视为假值。所以候选文件虽然存在,只要内容为空,循环就会停下,把它当成可写的新位置。
按照这个接口的约定,可以明确区分 null 和空字符串:
while ((await writer.read(candidate)) !== null) {
// 候选路径已存在,继续寻找
}
这里的改法针对当前 Writer.read 的返回约定。其他项目如果用独立的文件存在性接口,也应明确区分“不存在”和读取失败,避免把权限错误等情况误当成可覆盖。
测试时可以多看一眼什么
文件冲突测试除了普通的同名非空文件,也可以放入空文件、多个连续编号的文件,再检查最终文件名和原文件内容。对 keep-both 来说,测试不应只检查函数有没有返回成功,还要验证原有路径是否保持不变。
这次复现使用的是内存 Writer,验证了名称选择和写入调用;具体文件系统上的并发创建等情况需要另外测试。问题已按实际复现过程提交到 workvault Issue #69,后续状态以项目页面为准。
- 点赞
- 收藏
- 关注作者
评论(0)