最近把博客重构了。

之前用的是一套静态站点框架,功能没问题,但每次想改点什么,都得先查一遍它的约定:配置写在哪、模板怎么继承、插件挂在哪个钩子上。后来索性自己写:全部构建逻辑是一个 300 行左右的 Python 脚本,依赖只有 markdown、python-frontmatter、pygments 三个库;模板是几个 HTML 文件,样式一个 CSS 文件,文章按文件夹分类。跑一下脚本,输出目录丢给任意静态服务器就完事。

框架自带的一些功能也按最简方式处理了:全量构建一秒以内,干脆不做增量;热更新就是轮询文件变化,几十行代码;mermaid 和 KaTeX 各是一个可选的 HTML 头部片段,删掉文件即关闭功能。

我的博客是个简单问题,简单问题就配简单工具。

复杂度只会转移

工具的价值在于吸收复杂度,但复杂度不会消失,只会转移。工具吸收得越多,你面对的接口越简单;反过来,工具的复杂度一旦超过问题本身,多出来的部分就得你自己扛。

拿我的博客来说,需求就是 Markdown 转 HTML、套模板、生成列表页和 RSS。用框架来做,就得去理解插件体系、主题机制、配置格式、生命周期钩子——这些概念就是我要扛的差额。问题的复杂度没变,工具的复杂度全压在了我身上。

所以判断一个工具该不该用,问一句就够:它吸收的复杂度多,还是它带来的复杂度多?

依赖是转移进来的复杂度

外部依赖是复杂度转移最常见的入口。

引入一个依赖,得到的不只是它的功能,还有它的版本演进、bug、文档质量,以及它对自己依赖的要求。框架还要再加一层:项目结构、配置方式、目录约定都得按它的来。

更麻烦的是出错点不在自己的代码里。构建失败,报错来自框架内部,堆栈穿过你没读过的抽象层,得先花时间去理解一个本来不关心的系统。版本兼容也一样:依赖越多,"升级 A 必须同时升级 B,而 B 的新版本又不兼容 C"的连锁反应就越多。自己写一个简单实现,这类问题根本不会出现。

先删除,再简化

《重返太空》里 SpaceX 的做法是个很好的参照:马斯克持续要求团队减少供应商和零件的数量,理由是最好的零件是没有零件(The best part is no part)——每个零件都是潜在的故障点,每个供应商都是不可控的外部变量,而被删掉的零件不花成本、也不会出故障。SpaceX 的对策是把制造环节尽量收归内部,把外部黑盒变成自己可控的部分。这跟把构建逻辑收进自己的脚本,是同一个思路。

他的方法论里,"删除"排在"简化"之前:先删减零件和流程,再优化,最后才是加速和自动化。顺序不能反,优化一个本不该存在的零件是最大的浪费。软件里常见的情况就是:在一条臃肿的构建管线上不断调优,而不先问它是否应该存在。

依赖就是软件的零件。简化的第一步,不是选一个更好的工具,而是删掉不需要的东西。

AI 时代的账

这些道理几年前也成立,但 AI 编程工具普及之后,账要重算。

以前不自己写构建脚本,是因为写和维护都要花时间,框架是现成的。现在让 AI 生成一个 300 行脚本的成本很低,"用现成的更省事"这条理由已经不成立了。

另外,简单的系统对 AI 更友好。300 行的脚本,AI 可以完整读进去、直接改;框架项目里,自己的代码散落在框架约定的各处,背后是读不完的源码,AI 和人一样会迷路。出问题也一样:自己的代码里,错误只可能出在有限的地方;面对别人的代码,AI 同样要去猜内部行为、版本差异和隐式的默认值。

AI 能快速写出想要的功能,但消除不了别人代码里的黑盒。所以它反而加大了"简单"的权重:功能可以生成,复杂度最好控制在自己手里。

保持简单和愚蠢。