返回博客

构建实录 / 2026-07-25

我的脚本比网站还大:一次关于"伪进展"的项目审查

SILLITER.WANG 分享图

给这个站做了一次独立审查,第一个结论就很难看:scripts/ 目录 5168 行,而 src/ 里的 JS/JSX 产品逻辑只有 4391 行。 为一个 11 条路由模板、构建出九十多个静态页面的站服务的机器,规模已经超过了产品逻辑本身。

这里必须先把口径说清楚,否则这个对比就是耍流氓:src/ 全部文件其实有 11370 行,其中 4141 行是 CSS,剩下是文章 Markdown 和数据。同口径比整个目录的话,5168 对 8532,倒挂并不成立。 我用的是"机器代码 vs 产品逻辑代码"这个口径——理由是 CSS 和 Markdown 不构成需要被维护的控制流。这个口径的选择性我承认,后面还会打自己一次脸。

更难看的是第二个结论:这个站存在的理由——让第一次认识我的人在 30 秒内看懂我是谁、做成了什么——从来没有被任何真实访客验证过。而工程侧的每一项检查都是绿的。

一、AI 编程时代最容易犯的错:把"能自证明的事"当进展

回头看,偏差是这么产生的:写代码变便宜之后,我系统性地把力气花在了能被自己验证的事情上。

加一道构建门禁——立刻能看到它变绿。写一个 887 行的材料投递工具链——功能齐全、有 schema、有校验器。做一套 2000 行的浏览器 Smoke——断言数一路涨到 132,每次都有"覆盖更全了"的成就感。

而"找三个真人看一眼这个站"这件事,不可控、会被拒绝、还可能得到难看的结论。于是它一直排在后面。

门禁能自证明,真人不能。所以我一直在做前者。

这不是懒惰,恰恰相反——它伪装成勤奋。每一轮迭代都有实打实的产出,commit 记录很漂亮,检查全绿。但北极星的进度是 0。

二、清理:删掉跑 0 次的机器

第一批删的是 materials:private:* 工具链,6 个脚本 887 行。它服务的流程叫"材料履约"——别人索取项目材料,我脱敏后交付。

这套机器有 init、discover、plan、stage、check、report 六个命令,有 manifest schema,有校验器。唯一的问题是:它从建成到删除,一次都没跑过。 真实的材料请求次数是 0。

这是典型的"先建基础设施,再确认需不需要"。删。

第二批是两个公开发现资产:silliter-material-drop-kit.mdmaterials-manifest.schema.json。它们描述的正是刚被删掉的 manifest 工作流——继续挂在线上,等于对外发布一份指向不存在流程的说明书。删。

第三批更微妙一点:materials-needed.mdmaterials-status.json。这两个文件生成正常、内容属实、和数据源自洽,检查也全过。但它们在站内零页面引用,只被 robots.txtllms.txtprofile.json、vCard、media kit 和项目索引这六份面向机器的资产广播,而内容是"我还缺哪些证据材料"的内部待办清单。

一个想让访客相信"证据在这里"的站,同时向搜索引擎公开广播"我还缺这些证据"。事实为真,不等于应该对外发布。 删。

三批加起来约 1400 行。删完之后脚本 3817 行,低于 4288 行的 JS/JSX 逻辑,倒挂消失(前两批删完时是 4047 行)。

三、第二次审查推翻了第一次的部分结论

这是我觉得最值得写的一段。

清理完之后我做了第二次审查,这次用了多个独立视角并行:价值方向、技术架构、一致性审计、流程风险,外加一个专门找"这次清理弄坏了什么"的对抗性角色。然后对所有高危结论再做一轮独立核验,核验者的任务是尝试反驳,只有自己动手拿到证据才能确认。

结果是核验推翻了不少东西。

第一次审查说我"连续第三次推迟方向决策",定为最高优先级风险。核验去查了 git 历史,发现这个计数是错的:那几轮里有一轮是真的在做访客验证(建了证据门禁、记录了招募授权),是我自己在中途取消的。取消是正当的产品选择,不是推迟。

第一次审查还说"清理是逃避决策的代偿"。核验指出这是主观归因,且与文档矛盾——清理有独立的书面理由(机器反超源码),而方向推迟是我署名写下的、读过审查之后的主动排期。

四条最高优先级结论,全部被下调。整体偏差等级从 4 降到 2。

这件事的启示不在于"审查错了",而在于:单次审查的结论必须被独立核验,尤其是那些听起来很尖锐、很像洞察的判断。 尖锐和正确是两回事。一个措辞强烈的结论如果没人去查 git 记录,它就会以"深刻洞察"的身份留在文档里,然后影响后面所有决策。

四、顺手撞见一个真问题:发布门禁已经红了

清理过程中例行跑了一遍 npm run security:audit,退出码 1,5 个 high 漏洞

这不是我改出来的,是 npm advisory 数据库新增了公告,覆盖了当前锁定的版本。要命的地方在于:CI 的第一道质量门禁就是这道审计。也就是说,在我发现之前,这个仓库处于"一 push 就失败、根本部署不了"的状态,而我毫不知情。

其中一条特别讽刺:postcss 的路径遍历漏洞(CVSS 7.5),影响 <=8.5.17,而 package.json 里有个 override 把它精确钉死在 8.5.16。翻 git 记录才发现,那条 override 当初正是为了修一个 postcss 漏洞才加的(commit 叫 fix: override vulnerable postcss)——为修漏洞而钉死的版本,两周后被新公告重新划进了漏洞范围。 钉死是当时对的选择,但钉死本身就是一种会过期的决定。

修的时候有个判断点。先升了 postcss 和 wrangler,剩下两条挂在 sharp 上,这时 npm audit --json 给出的 fixAvailable 是把 Next.js 降到 14.2.35,标着 isSemVerMajor: true——一个大版本倒退,会砸掉整个项目。(这是那个中间状态的输出;换个时点重跑,advisory 数据变了,建议也会变。)

真实情况是 sharp 版本过低,而它有两条来路:既是 Next 的可选图片优化依赖,也是 wrangler → miniflare 的硬依赖。这个站是纯静态导出、images.unoptimized = true,Next 那条路根本不调用它。所以修法是 sharp override 提版本 + wrangler 升小版本两件事一起做,而不是降级框架。

自动化工具给出的修复建议,需要判断它是不是在解决你实际有的问题。

五、我推翻了自己设的一个目标

审查建议把 2000 行的 Smoke 脚本"降到 1300 行以下",我把它写进了迭代目标。

真做的时候才发现这个数字站不住。实测行数分布:checkHttpAssets 361 行、checkPage 301 行、checkSeoContracts 138 行——全是真实的检查逻辑,不是重复代码。结构性重复(中英文各写一份页面 spec)只有大约 200 行。

我把那 200 行抽成了工厂函数,中英文结构单份定义、文案按语言注入,加一个页面不再需要在两处同步改。诚实说,这次重构本身只净减了 30 行(1942 → 1912);从 2027 到 1942 的那 85 行是上一步停发 materials 资产顺带减掉的,跟去重无关。真正的收益不是行数,是加一页不用再改两处——文章后面那个数字反而削弱了要点。

要继续降到 1300,只能删真实的检查覆盖。那和"断言数不减少"直接冲突。

所以我没删,而是回去把自己写下的目标改了,并在文档里写明原因。为了凑一个拍脑袋的数字去删检查,是本末倒置——那个数字本来就是照搬建议、没做结构分析定下的。

六、留下的东西

删完之后,这个站还剩:数据单一源、构建期五道门禁、_headers 安全头、CI-only 发布门禁(Cloudflare 的 Git 自动部署是关掉的,只有 Actions 全绿才用锁定版 Wrangler 部署)。这些几十到一百多行的东西,是真正扛时间的部分。

而那些跑 0 次的工具链、无人消费的发现资产、为了覆盖率而增长的断言——它们看起来像资产,实际是负债,每一行都要在未来的每一次改动里被绕过、被维护、被解释。

还有一件事值得记下来。这篇文章在发布前被独立核验过一轮,结果被挑出十一处问题:开头那个"机器比源码大"的对比排除了 4141 行 CSS 却没说明口径、删完后的行数写成了中间态、"只被 robots.txt 广播"实际是六份资产、postcss override 的来历我记反了。我写了一篇讲"单次审查的结论必须被独立核验"的文章,然后它自己没能通过核验。 上面那些数字是改过的版本。

最后一句留给这轮最大的收获:在 AI 让写代码变得极其便宜的时候,"我写了很多"已经不再是进展的证据了。删掉的那 1400 行,每一行当初都是认真写的、能跑通的——讽刺的是占比最大的那 887 行连一个测试都没有,也从没进过任何一道门禁。它们唯一的问题是没有人需要。