能跑不算完成,能升级才算
能跑不算完成,能升级才算
一个 demo 和一个软件之间,隔着的不是功能多少,是「升级」这两个字。
这是我最近几个月改得最彻底的一个观念:以前我把发布当终点,现在我知道发布才是承诺的开始。
一、七个地方,同一个版本号
HexoManager 每发一个版本,同一个版本号要出现在七个文件里:
1 | version.json ← 我自己的版本源 |
再加上一份 CHANGELOG.md。一共八个位置,漏一个就出问题:
- 漏
tauri.conf.json:打出来的安装包版本是旧的,自动更新判定直接失效 - 漏
Cargo.toml:二进制里嵌的版本号对不上,用户看到的和实际跑的不是一个东西 - 漏
constants/index.ts:「关于」页显示的版本永远比实际落后一格
前几次发布我就是靠人肉核对。第三次漏了 Cargo.lock 之后,我把它写成一张清单,每次发布照着走一遍。这件事一点也不高级,但它是「能升级」的地基——如果连版本号都不可信,自动更新就只是个装饰。
二、自动更新是一条链,不是一行配置
Tauri 的自动更新看起来简单:客户端请求一个 latest.json,比较版本,下载、校验、替换。真做起来,它是一条完整的链:
- 构建:三个平台分别出包(macOS / Linux / Windows),签名要对得上
- 发布:CI 把资产传到 Release,同时生成
latest.json - 判定:客户端读
latest.json,比版本、比平台、比架构 - 回退:新版本启动失败时,不能把用户卡在半路
我在这条链上踩过的坑,基本都是「本地能跑但发布后不对」:本地手打的包没签名、CI 的 draft 开关让 Release 一直是草稿(客户端根本读不到)、平台资产命名不一致导致匹配不上。
X-Note 那边我干脆做了双源更新:GitHub 和 Gitee 各放一份 manifest。原因很实际——国内网络下 GitHub 的 Release 下载经常卡住,Gitee 是逃生通道;两个源都失败时,至少还能手动下载覆盖。
用户不会因为你「只支持一种下载渠道」而原谅你,他只会觉得「这软件更新坏了」。
三、发布是承诺,具体是三条
我把「能不能交付」拆成三件可以检验的事:
1. 升级不丢数据。X-Note 的笔记就是标准 .md 文件,这是我最在意的设计:即使软件彻底坏掉,用户打开文件夹还能看到自己的东西。云同步插件写盘时也走同一套格式,绝不引入私有数据库作为唯一副本。
2. 失败能回滚。HexoManager 每次升级前会备份站点配置;X-Notepad 的全库替换会先留一份原始字节快照(只保留最近 20 份),替换完能一键撤销。这些功能一点都不酷,但它们是「敢按升级按钮」的前提。
3. 旧版本能读懂新东西。
这一条最难,也最少人提。用户不会都在同一版本上:你改了配置文件结构,老版本读到新格式可能直接崩。所以我在加字段时尽量只增不改,读的时候给默认值——兼容不是美德,是不让用户替你承担升级的后果。
四、把「质量」变成脚本
我慢慢发现,靠自觉守住的标准,最后都会松。所以能写成脚本的,我都写成了脚本:
HexoManager的 i18n 覆盖率校验:缺译文时退出码 1,直接当 CI 卡口。当前 1356 条词条、1466 条字典、缺译文 0——这个数字不是我数出来的,是脚本算的xfm带npm run bench:一千篇中文文档冷构建 4.4–5.5 秒,改性能前后跑一遍,不靠感觉X-Notepad的插件签名:Ed25519 校验内容摘要,对不上直接拒装——把「信任」变成一次可验证的计算
脚本的意义不是自动化,是把判断标准从「我觉得」变成「它必须」。人会在赶时间的时候放宽标准,脚本不会。
五、心态的变化
早期我对「发布」的理解是:打包、上传、发个帖子。所以我的项目常常停在「我这台机器上能跑」的状态——那其实不是软件,是一次演示。
真正的转折点是有一次我给别人装 HexoManager,他问:「以后更新要重新下载吗?」我愣了几秒,因为那时候确实要。那一刻我才意识到,我从来没把自己当成「要为用户的下一次打开负责」的人。
后来我给自己定了个很土的标准:
交付不是「我今天跑通了」,而是「三个月后有人在旧版本上点更新,能顺利变成新版本,而且数据还在」。
按这个标准,很多我以为完成的项目其实只完成了一半。于是我开始补自动更新、补版本清单、补回滚、补兼容——这些工作量加起来可能比原功能还多,但它们决定了一个东西是「作品」还是「玩具」。
评论