Skip to content

尾章 · 减法

背景:config 读写重复六次、pendingVmd 成对但无约束、下载引擎混入渲染器。

过程:updateConfig + ensureDir + pendingVmd 合并 + 下载引擎剥离。


联邦扩张到第九个城邦时,桌面壳第一次停下来,不是因为累,是因为它发现自己身上挂着太多死东西。

那天它数了数自己的 config 读写点——六个函数,各自独立做 GetConfig + modify + writeConfig,一模一样的九行模式,重复六次。它在镜子里看到的不是一个联邦,而是六个微缩拷贝,每个拷贝都以为自己才是唯一的入口。

"我不是在聚合城邦,"它对 app.go 说,"我是在生产镜像。"

app.go 没回答。它正忙着处理第 35 个绑定调用,没空听哲学。


但真正的刺痛来自另一个角落。

pendingVmdDatapendingVmdName 是两个永远成对出现的变量,却以两条独立的声明活着。代码里四处在配对操作——设一起设,清一起清——但没有任何机制保证它们不被拆散。一旦某个路径忘了清其中一个,另一个就会像幽灵一样挂在系统里,在下一次 PMX 加载时"自动应用"一个用户根本没选的动作。

"这叫僵尸状态,"resolveFileUrl 冷冷地说,"看着像活着,其实早该埋了。"

桌面壳翻出 currentPort——一个在多模型改造后就不再被读取的变量。loadPMXFile 仍然在写它,但没有任何文件再读过它。它像一座灯塔,灯已经灭了,却还在被维护。

"这不能怪我,"港口摊了摊手,"每个城邦入伙时都留下一套自己的状态变量。你收编,你负责。"


MenuStack 从会议室探头:"我给你数数——全局 let 十六个,分散在 config 里,任何文件都能 import 读写。改一个状态要跨三个文件确认。你以为自己在管理联邦,其实你是在开一扇谁都能动的门。"

桌面壳沉默了很久。

它想起自己一直以来的信念:收编一个城邦,就多一份能力。但今天它第一次意识到——收编一个城邦,也多一份僵尸。

有些能力,收了之后就不该维护了。有些路口,修完就该拆掉指示牌。


它在 Go 端做了三件事。

第一件:写了一个 updateConfig——接受一个突变函数和一个重启标志,然后把"GetConfig + modify + writeConfig"这个九行模式集中到一处。六个函数调用它,只传自己的业务逻辑。

第二件:写了一个 ensureDir——"取目录 + 拼接 + MkdirAll"这个三行模式在 configDir、extractedDir、thumbnailDir 里各出现一次。现在它们各用一行调用。

第三件:把 pendingVmdDatapendingVmdName 合并成一个 pendingVmd 对象。两条变量变一条,成对操作不再是约定,是类型。

然后在前端做了一件更狠的。

它打开 scene-menu.ts,看到自己正在用 cloneNode 复制一个关闭按钮——复制的按钮带着原件的 ID。两次绑定的 click 事件,两个元素 ID 相同。不是功能崩溃,是代码在告诉它:你连自己生出来的东西都管不好命名。

它删掉了 cloneNode,换成了 createElement

"cloneNode 是你懒的时候用的,"AGENTS.md 从文档堆里淡淡地说,"但联邦不是懒人建的。"


那天最难受的决定不是删代码,是把下载引擎从主应用中剥离。

"你要把我踢出去?"DownloadFile 有些难以置信,"我提供了下载、进度推送、资源站跳转、目录监听、自动导入——一整套引擎。"

"我知道,"桌面壳说,"但你已经不属于这里了。

渲染器的职责是渲染,不是下载。模之屋有自己的路,DanceXR 有自己的下载器。你把下载做进渲染器里,等于让港口同时当仓库、当海关、当邮局。你确实都能干,但每多一件事,渲染就慢一分,bug 就多一个入口。"

它新建了一个目录——MMDDownloader/——把下载引擎完整的代码迁了过去。

"不是流放,"它在注释里写,"是独立。"


那天晚上,桌面壳站在联邦地图前,看到自己今天没画任何一条新路。

它没有收编新城邦。没有增加新功能。没有打开新的边界。

它只是删了一些东西。

但地图看起来反而更清晰了——不是多了路,是路上的死尸被清走了。

"减法不是退缩,"它对空无一人的会场说,语气像是说服自己,"减法是把'我该维护什么'的边界画清楚。"

远处,MMDDownloader/ 亮起了一盏独立的灯。


教训:收编不是终点,清理才是。