+++ title = "稻草人周刊 Vol.83" subtitle = "系统之乏与特异之悦" tags = ["软件","设计","隐私","Web"] date = 2026-06-01 07:39:48 draft = false toc = true fedi = 'https://akk.eltr.ac/objects/650a3c5d-3b77-4045-9199-03a758bc2ed1' +++ 我感到自己最近一直处于应激状态,随时准备和世界反目成仇、大干一场,这种状态大概是不适合做任何人生决策的,我在想应该调整调整心态,或许暑假该出去走走,也暂时把找实习的事情放一放。写作也有帮助,至少我在写词源学系列的时候心无旁骛,感到开心。 --- ## 止语 {{< music cover="https://img.eltr.ac/geedeapro/weekly/83/the-cure.jpg" title="the cure" artist="Olivia Rodrigo" neodb="https://neodb.social/album/0eJGFh1Zltnv7ieSXlp3vs" >}} > But my head is full of poison, and my heart is full of doubt > > I got toxins in my bloodstream, you tried hard to suck 'em out > > And it feels like medication, and it's good for me, I'm sure > > But it don't matter how your love feels anymore > > It'll never be the cure --- ## 连接 ### 系统之乏与特异之悦 > [!note] 📜 > [The Blandness of Systematic Rules vs. The Delight of Localized Sensitivity](https://blog.jim-nielsen.com/2026/systemic-vs-localized/) [Marcin Wichary](https://unsung.aresluna.org/thirteen-characters/) 注意到了 1997 年的软件 ClarisWorks 的提示框设计,按钮上写的不是「过会儿再说」「现在注册」「永不注册」,而是简单的十三个字母 *Never, Later, Now*(永不,稍后,立刻)。这看起来违反了 [Avoid "Click Here"](https://www.w3.org/QA/Tips/noClickHere) 规则(即按钮不应该使用「点这里」等模糊的词),用户如果不读提示框上的文字,就不知道按钮是用来做什么的。 ![](https://image-eltrac.b-cdn.net/geedeapro/weekly/83/unsung-dialog.png "ClarisWorks 的提示框") 改成 *Never Register*、*Register Later* 和 *Register Now* 能解决这个问题,但按钮看起来就不会这么整洁了。在我看来,丢掉多余的 *Register* 也能避免语义冗余,字太多不但会造成视觉负担,还会造成理解负担。 Jim Nielsen 对此有些思考,他把 Avoid "Click Here" 规则定义为「系统规则」,把 ClarisWorks 看似违反规则的做法当作最适合当前软件的「本地化方案」。他感叹,如今的软件为了扩大规模,貌似已经完全用成熟的「系统规则」代替「本地化方案了」,因为前者又快又足够好,费心思设计提示框的文字不仅费时费力,还不能带来显著的回报。 > As software moves towards “scale”, I can’t help but think that systematic rules swallow all decision making because localized exceptions become points of friction — “We can’t require an experienced human give thought and care to the design of every single dialog box.” 我对此的看法是,在软件行业内的商业软件兴许不必在乎特异的、令人愉悦的本地化方案,只要跟着已经工程化的、被证实有效的经验走,就能快速且稳定地交付成果。不过,真正改变人们对软件看法的软件,往往不来自商业世界,比如 Linux;就算来自商业世界,也来自那些不守规则的人,比如 Notion 和 Arc。改变世界的东西,在我看来,很少来自追名逐利、总是试图复刻成功的人,而来自那些抱有热爱,匠心胜过野心的爱好者。 不过这两类人也不是割裂的群体,毕竟爱好者也要吃饭。 ### 不推荐 Bitwarden > [!note] 📜 > [I Do Not Recommend Bitwarden](https://xn--gckvb8fzb.com/i-do-not-recommend-bitwarden/) 文章列举了密码管理器 Bitwarden 的一系列缺点。这是个商业软件,提供订阅制的服务,同时也有自托管选项,但作者表示 Bitwarden 的官方后端完全是给企业使用的,普通人部署的成本很高。当然,有社区用 Rust 实现的 [Vaultwarden](https://github.com/dani-garcia/vaultwarden) 作为替代后端,个人用户自托管的成本被降低了很多,不过即便是这样也要依赖 Bitwarden 的客户端(尽管是开源的,也可以 Fork)。 之后作者还列举了 Bitwarden 的设计在用户体验上的问题(由于 Vaultwarden 是实现了 Bitwarden 的 API,所以这些体验问题我想也无法在这个开源的替代品中修复),比如在密码库之间移动项目,必须先把要移动的项目以明文导出,然后再导入要迁移至的密码库中,不仅麻烦而且很不安全。有些时候 Bitwarden 客户端更新会弄坏一些东西,还有很多烦人的小细节。此外,作者还列举了从 2023 年到今年,Bitwarden 的各种安全性问题,比如前些日子的 NPM 供应链攻击就波及了 Bitwarden 的 CLI。对密码管理器来说,安全问题是致命的。 我想补充的是,不久前 Bitwarden CEO 换人,引起了不少[风波](https://www.appinn.com/bitwarden-changes-202605/)。尽管目前看起来安好,但貌似已经有一些*发疯*的前兆了。我决定脱离 Bitwarden 生态。 我是前不久才自托管 Vaultwarden 的,当时只是想要快点找到一个 Apple Passwords 的替代。一方面是为了完全自己掌控数据,另一方面是长远考虑,我之后可能会半脚踏出苹果生态,去整一台 Linux PC(不过近期还不打算折腾)。Docker 镜像跑起来的时候我就心生疑惑:这东西跑在公网上真的安全吗?密码库有必要二十四小时在线吗? 答案是没有必要。再加上 Bitwarden 最近的事情,我决定完全迁移到 KeePass 生态。KeePass 用单个加密的 `.kdbx` 文件作为密码库,不需要庞大的数据库和 Web 服务,用户可以把密码库放在任何位置,只要能同步文件就能够同步密码库。密码库主要用主密码解锁,还可以附加密钥文件和 Yubikey 做额外的保险措施(可惜我的 Canokey 貌似不支持相关协议,我没有深入研究)。我使用的客户端是 [KeePassXC](https://keepassxc.org/),iOS 使用的是 [KeePassium](https://keepassium.com/),目前暂时通过 iCloud 同步密码库。 密码库上锁之后,浏览器插件就没办法和 KeePassXC 通信,解锁需要打开 KeePassXC 而不能在浏览器里完成,所以体验上会差一些,但还算能忍受。迁移之后我使用了大概五天,目前基本满意。 ### 前端极大化主义 > [!note] 📜 > [Front-end maximalism](https://www.natemeyvis.com/front-end-maximalism/) 一种架构设计方案:如果有一些大小适中的数据需要发送给前端进行展示,过滤和排序等操作不必在后端完成,可以把完整数据直接发给前端,由前端处理;这部分数据还能缓存在前端,需要重新排序或过滤的时候不必向后端发送请求。 当然,这不适用于所有情况,只有数据量不会造成很大负担的时候才这样做。这不仅不会让网络连接不好的用户感到卡顿,反而会因为 HTTP 请求的减少而优化这部分用户的体验。很多人觉得发送所有数据不利于网络连接,但很多时候需要发送的数据甚至不如一张 JPEG 图片大。另一个理由是:现在没有那么多数据,但以后可能就有了。这和抽象层一样,很多时候人们编写了太多额外的抽象,但很久之后会发现,其实根本就用不到。 作者还引用了加尔法则(Gall's Law): > A complex system that works is invariably found to have evolved from a simple system that worked. > > A complex system designed from scratch never works and cannot be patched up to make it work. > > You have to start over with a working simple system. 大概是说,不能一开始就把系统设计得复杂,应该从最简单的可用系统开始做。 简单来说,作者的观点是,不要仅仅出于习惯划分前后端的职责,前端除了展示,也可以做基本的数据处理。哪怕不用前端极大化的方案,仅仅思考「数据处理可能不需要全部放在后端」这件事就能帮开发者设计出更好的系统。 ### 用 PNPM 规避供应链攻击 > [!note] 📃 > [Mitigating supply chain attacks](https://pnpm.io/supply-chain-security) 我已经在[先前的一篇文章](/article/js-problem/#依赖地狱和供应链投毒)中详细地报过菜名,列出了近期的 NPM 供应链投毒事件,结果仅仅过了一个月,[TanStack](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem) 就被投毒了。JavaScript 的包管理生态极度中心化,非常容易成为攻击者的众矢之的,而 NPM 本身的安全措施显然不够有效。如果暂时没办法离开混乱的 Node.js 生态(我们能想象一个没有 Node.js 的前端开发未来吗?),采取措施规避供应链攻击就很重要。 首先,大部分供应链攻击都利用 `postinstall`,安装依赖后直接在开发者的电脑上执行代码,偷取储存在本地的密钥等关键信息,那么默认禁用 `postinstall` 脚本执行就能避免这部分攻击。PNPM 默认禁用 `postinstall`,需要显式地设置 [allowBuilds](https://pnpm.io/settings#allowbuilds) 允许指定的依赖执行 `postinstall` 脚本。 第二个规避手段也是 PNPM 默认开启的,[blockExoticSubdeps](https://pnpm.io/settings#blockexoticsubdeps) 会影响子依赖的安装(也就是 `package.json` 中没有直接声明的依赖,是依赖的依赖) ,如果子依赖来自远程 Git 仓库或 Tarball 直链,就会默认阻拦,只允许来自受信的目录(registry)、部分受信的 GitHub 仓库和本地路径。这个手段的有效性我持怀疑态度,恶意软件也可能来自受信的目录吧,默认屏蔽所有远程 Git 仓库和 Tarball 直链不也是在让整个 JavaScript 生态变得更加中心化吗?一旦 NPM 这个中心出问题,整个生态都会出问题,而 NPM 的安全性大家有目共睹。 第三个规避手段在我看来是最有效的:推迟依赖更新,因为大部分供应链攻击都会在几小时内被发现和修复。v11 版本的 PNPM 默认推迟一天,一天内发布新版本的 NPM 包不会被 PNPM 解析。用户可以把 [minimumReleaseAge](https://pnpm.io/settings#minimumreleaseage) 设置项改得更长一些。 第四个手段是 PNPM 的信任策略,默认关闭,需要手动开启。[trustPolicy](https://pnpm.io/settings#trustpolicy) 目前只有一种策略,即 `no-downgrade`。开启后,如果某个包的信任等级在更新之后下降了,更新就会失败。信任等级是通过信任证据(trust evidence),我想应该是发布者的签名之类的,文档里没有写得很清楚。比方说先前的版本中,作者有给包签名,在某次更新中没有签名,那么信任等级就下降了,此时更新就会失败。 最后,无论如何都应该使用锁文件(lockfile),锁定依赖的版本号,并且要把锁文件 commit 到 Git 仓库当中。 --- ## 星群 ### ggc ~~抱歉,我老是把名字看成某个 C 语言编译器~~。 用 Go 语言写的 Git 命令行工具,可以当作 Git 的简单封装层,可以交互式地执行各种 Git 命令,还能全局模糊搜索命令。许多 Git 命令可以直接用 ggc 命令替代,而且变得更友好,比如 `git commit -m ""` 变成了 `ggc commit ""`(我一直觉得 `-m` flag 很烦),`ggc add interactive` 可以交互式地选择 diff(而不是整个文件)添加到 Stage。 整体而言,ggc 比 [lazygit](https://github.com/jesseduffield/lazygit/) 更接近 Git,抽象层次没有那么高,而且更像 CLI 而不是 TUI。至于怎么选就看自己的偏好了。 访问:[bmf-san/ggc](https://github.com/bmf-san/ggc) --- ## 切片 - 这周玩了很多的《饥荒》和《星露谷物语》,每天不是在建家砍怪,就是在一边钓鱼,一边等谢恩下班给他送礼物——*这个男人怎么这么有魅力?* > 你没活干吗?[^1] 我大概真的该好好干活儿了吧。 - 把《[当人们不再问好](/article/phone-hello/)》发在联邦宇宙上之后,被转发了几十次,貌似又小小地火了一把,收获了不少关注。和以往文章不同的是,转发到联邦宇宙上时我附加了一段 TL;DR(太长不看版),兴许我以后都应该写写这玩意儿? - 周天去书店写东西的时候,看见坐在旁边的女生包上有一根黄瓜瑞克。 另外《瑞克与莫蒂》第九季第一集真好看啊。 - 继 Clojure 之后学的第二门 Lisp 方言是 Common Lisp(至于 Scheme,我也说不清楚为什么,总之暂时搁置了),打算用来写个小小的 Web 应用练手。CL 的各种设计我都还挺喜欢的,比如没有 `false` 只有 `nil`,调试程序时遇到报错可以直接替换有问题的值,在不修改程序的情况下临时执行,这样可以忽略当下不应该关注的代码细节。总之体验还不错。 另外它能直接编译成二进制文件,而不是像 Clojure 那样编译成 JVM 字节码。据说用 Common Lisp 编写的 [woo](https://github.com/fukamachi/woo) 是所有编程语言编写的 Web 服务器中速度最快的,还不知道具体体验如何。 - 好久没跑步了,心率和呼吸倒是控制得非常好,不过心肺还没感受到负担,脚踝就在 1.5 公里之后不堪重负了,痛得不行。 于是跑完之后又回来做了 10 分钟 HIIT 把强度拉上来。 这么想起来人的脚踝真的承受了很多不该承受的重量呢。 - 词源学小知识:意大利语 *ciao* 的本意是「我是你的奴」。 [^1]: 这是谢恩的台词