Notes

不用积木了,直接写

一个现象

这个站有 41 个工具、7266 行代码,0 个依赖:没有 package.json,没有 node_modules。

而它做的事,按这几年的常规做法大概会是这样一套积木:

模板引擎(handlebars / ejs)· Markdown 渲染(marked)· 本地 dev server(live-server)
图片处理链(sharp)· 截图(puppeteer)· 部署(一套 deploy 脚本包)· 数据校验(zod)
……再加上它们各自带进来的传递依赖,两三百个包

这些包一个都没用。不是刻意"零依赖",而是一路写下来发现:要做的那件事,直接写掉比接一个包更快。

这篇想说的不是"依赖不好" —— 那种说法又廉价又不诚实。我想说清三件事:积木模式在什么条件下是对的、那个条件怎么变了的、以及直接写要付什么代价。

一、先承认:积木模式是对的

在"写代码很贵"的年代,引入一个包是唯一理性的选择。

写一个能处理边界情况的模板引擎,是几天的活;写一个真正可用的 Markdown 渲染器,要读规范、处理嵌套、处理转义,是几周甚至几个月的活。而 npm install 是三秒钟。

那时候"到处 import"不是愚蠢,是用别人的时间换自己的时间 —— 而且换得很划算。生态的价值也是真的:边界情况被别人踩过了、性能被优化过了、坑被填过了。

所以这一节不是客气话。如果写代码的时间成本没有变,"直接写"就是错的。

二、变的是代价表

变的是中间那一行:

                                 积木模式               直接写
写一段目的明确的代码             几天 → 引入一个包        几分钟
读懂 / 维护别人的代码            不变,甚至更高           发生一次,之后都在自己手里
依赖的隐性代价                   升级、供应链、黑箱       没有

三行的关系是这样的:

  • 第一行塌了。 写代码的时间成本从"几天"掉到"几分钟"。这不是"AI 写得快"这种笼统的说法,而是很具体的:一个 200 行的模板引擎、一个 200 行的 Markdown 子集渲染器、一个 80 行的本地服务器,都是一次对话的时间。
  • 第二行没变,甚至更贵。 读懂一个第三方包的源码、跟上它的版本变化,成本一点没降 —— 而且它有自己的发布节奏,你的日历要跟着它走。
  • 第三行没变,而且随着生态膨胀在变大。 一个包的传递依赖可能是几十上百个,你看不见里面的代码,却要承担它的升级、它的安全公告、它维护者不再更新的风险。

三行合起来,结论只有一句:不是依赖变坏了,是"划不划算"的界线挪了位置。 门槛移动了,同样的判断在新位置会得出不同的答案。

三、这个站的分法:不是两分,是三分

把"依赖"当成一个整体来讨论是错的。这个站实际上是三分的:

自己写借平台引入包
模板引擎 215 行图像处理 → sips0 个
Markdown 渲染 193 行传输 → ssh / rsync / tar
本地服务器 80 行HTTP 服务 → nginx
截图工具 70 行浏览器渲染 → Chrome 的调试协议
检查网(单测 10 项 + 闸门)语言运行时 → Node

关键在第二列。 它既不是"自己写",也不是"引入包" —— 它是平台能力:随操作系统、随标准、随已经装在这台机器上的东西存在。

这个区分比"零依赖"重要得多。sips 不是包:它不需要被跟踪版本、不会因为某个维护者不干了就烂掉、也不会有传递依赖。它就在系统里,/usr/bin/sips。

所以严格说,这个站不是"什么都自己写"。它是 "第三方包零个,平台能力大量借用"。

四、借平台是这套做法的支点 —— 也是它最大的软肋

借平台的好处是:不用维护。代价是:你绑定了那个平台。

这个站最诚实的一处软肋就在这里:sips 是 macOS 的命令,而图片处理是构建里绕不过去的一环(照片的每档尺寸、favicon、分享图都由它生成)。这意味着整个构建流程绑在 macOS 上 —— 换一台 Linux 机器,构建就跑不起来。

这不是"疏忽",是权衡:"为了不引入 sharp(一个带二进制依赖的图形库),我接受了绑定 macOS。" 换个人、换个场景,这个权衡可能应该反过来。

五、几个自己写的东西,长什么样

不是为了证明"自己写更好",是想让你看到它的体量和边界。

模板引擎(215 行)。 支持三种东西:插入片段(可嵌套)、插入数据(站点名、年份这类)、以及一条规则 —— 占位符所在行如果值为空,整行删掉(这样可选块不用写 if 语法)。就这些。积木模式下这是 handlebars 加它的语法、编译和运行时。

Markdown 渲染(193 行)。 只支持自己会用到的那一小撮:标题、段落、列表、引用、行内代码、链接、加粗。这也是这套做法最危险的地方 —— 它不支持的东西不会报错,只会渲染成普通段落。我必须永远记得自己支持什么、不支持什么。

本地服务器(80 行)。 就一个静态文件服务器加几个响应头。积木模式下是 live-server 加它的一整棵依赖树。

截图工具(70 行)。 这里又是借平台:不自己渲染,直接开一个无头 Chrome,用它的调试协议截图。

favicon 生成(167 行)。 用 sips 把源图切成各种尺寸,再手写 ICO 容器。积木模式下是 sharp + png-to-ico。

每一个都不"高级"。它们只是恰好够用,而且每一行我都能读懂。

六、代价,不回避

① 有些东西不能自己写。 编解码、加密、字体排版、完整的 Markdown 规范、HTTP 协议解析 —— 这些不该自己写。它们的正确性要求远远超过"能跑",而且错了很难被发现。这类东西该引入包,或者该借平台。

② 失去了"很多人用过"的背书。 一个被下载了几百万次的包,你默认它踩过大部分坑。自己写的代码没有这个默认值,只能靠检查补。这个站的补法是:10 项单元测试跑在构建闸门的第一步、部署前一道比对闸门、以及一套我一直维护的界面探针。

③ AI 写的不等于对。 这个项目踩过的坑能列一长串:增量转换会跳过"输出比输入新"的文件、macOS 的索引还没建好时 EXIF 会读到不一致、删除照片时远端对象不会跟着消失、时区在提交信息里错成前一天。这些都是代码写出来了、跑起来了、然后悄悄错了。直接写降低了写代码的成本,没有降低写错代码的成本。

这篇随笔自己就撞了一次:我在正文里提到模板引擎的语法,把那段语法原样写了进去 —— 它进入列表页的 data-text 属性,被模板引擎的"产物里不该残留未展开语法"的检查拦了下来,构建直接失败。这次是检查赢了;但如果那个检查不存在,坏的是一个页面的属性,而且不会有任何报错。

④ NIH 是真实存在的病。 "不想用别人的轮子"会让人重写一切,包括那些根本不该由个人重写的东西。这条界线必须自己守,而且没有一份通用答案。

七、这套做法适合谁

明确划个边界,因为不加边界就等于在吹牛。

适合:一个人维护、长期存在、规模不大、行为可以枚举、检查能覆盖得住的项目 —— 个人网站、脚本工具、内部小工具。这个站就是典型:17 个页面、108 张照片,所有行为都在一个人脑子里。

不适合:大团队协作、需要规模效应、领域复杂度高的(音视频、加密、协议实现)、或者要发布给别人用的库。这些场景里,生态的价值远大于自己写的成本。

所以它不是"更好的编程方式",它是"在特定约束下更便宜的方式"。约束变了,答案就该变。

八、几条可操作的原则

如果只留几条能用的话:

  1. 先问"平台有没有"。 系统命令、标准库、浏览器能力 —— 优先于任何第三方包。
  2. 再问"能不能在一两百行内写完,并且我能全部读懂"。 能,就自己写;不能,就去找包。
  3. 如果引入包,问一句:"它带进来多少我看不到的代码?" 一个包和它的一百个传递依赖,是同一件事的两面。
  4. 引入就必须能被检查。 锁版本,并且有一条能失败的东西验证它在正常工作 —— 否则它坏的时候你不会知道。
  5. 不可自行实现的不自己写。 加密、编解码、协议解析 —— 这几类的正确性不能靠"看起来对"。

结语

积木模式不是错的,它是在"写代码很贵"时唯一划算的解法。AI 没有让依赖变坏,它只是让"写一段只解决这个问题的代码"变得便宜了。

于是那道题从"我该用哪个包",变成了"这件事值得引入一个包吗"。

这两个问题看起来很像,但它们之间隔着一整张代价表。

Get in Touch

联系

这篇写完之后又踩了新的坑,或者你有别的看法 —— 欢迎写信来聊。