读:轻量 ≠ 简陋——从 Stupid Light 看技术选型的度
Martin Tournoij 在博文 Light, Light, Light! 中借用了一个户外运动的概念:Stupid Light。
超轻量徒步(ultralight hiking)圈子里,大家追求装备极致轻量。帐篷薄一点、睡袋小一点、背包轻一点,每一项省一点,累加起来就很可观。但减重也有个边界。Stupid Light 就是减到了愚蠢的程度:帐篷不抗风、食物带不够、急救包精简到一张创可贴。省了重量,但人不舒服、装备不可靠、必需品没带齐。
Tournoij 把这概念移植到软件工程中:追求轻量是对的,但轻量到某个点之后,和臃肿一样糟糕。
选型场景
数据存储:文本文件 / SQLite / 大型数据库
最轻量的方案是根本不用数据库。配置项、小规模数据存在文本文件里,程序启动时读进来完事。这在很多场景下确实是正确的选择,省去了数据库的安装、配置、运维成本。
但过犹不及。有些项目为了坚守"不用数据库"的原则,用文本文件存结构化数据,自己处理并发写入、数据完整性、事务回滚。结果手写的存储层比直接用 SQLite 还复杂,bug 还多。这就是 stupid light,原则压倒了务实判断。
中间道路是 SQLite,有事务、并发控制、结构化查询,但没有独立进程、没有网络端口、零配置。很多"轻量至上"的开发者跳过了这个中间选项,直接从文本文件跳到"绝不引入任何数据库依赖"的极端。
反方向的过度工程同样常见:一个最多存几百条配置的小工具,非要架上 PostgreSQL 加一套 ORM。这头是 stupid light,那头是臃肿,中间那条路叫"够用就好"。
依赖管理:手写轮子 / 最小依赖 / 全家桶
引入一个库之前先算笔账。如果只需要库中一个简单的函数,抄过来或者自己重写一段确实更轻量。但前提是你能把边界情况处理好。有些项目手写 JSON 解析器、YAML 解析器、CSV 解析器,结果要么漏了转义字符,要么在 Unicode 上翻车,要么性能奇差。为了省一个依赖,反倒制造了更多隐患。
反过来,为了一两个 API 调用就引入整个 HTTP 客户端库加全套重试机制,也是常见的过度工程。
合理的做法是评估成本:抄一段代码或重写,代价是长期维护这份代码。引入依赖,代价是版本兼容、安全更新、构建时间。哪个更小,选哪个。
功能设计:简陋 / 够用 / 臃肿
API 接口和软件功能遵循同样的逻辑。去掉不必要的功能是好事,每增加一个接口都是长期的维护负担。但如果把功能精简到用户需要变通才能用,那已经不是简约,是简陋。
举例来说:一个日志工具,核心功能是写日志文件。如果它连日志轮转都不支持,用户就得自己写 cron 脚本来切割日志。工具本身"轻量"了,但把复杂度转移给了用户。这不是"少即是多",是"少即是推卸责任"。
好的功能取舍不是砍到最少,而是砍到用户刚好够用、不多不少。
判断"度"的原则
几个场景看下来,问题都一样:怎么判断这个度在哪里?
原则一:把"不做"的代价也算进去。 不引入数据库省了安装配置,但代价是手写并发控制。不引入依赖省了版本管理,但代价是维护自研代码。决策时两边都要算,不能只算"省"的那边。
原则二:考虑你的用户,不只是你的代码。 你的程序"轻量"了,但用户需要做更多额外工作来弥补缺失的功能。那整体负担并没有减轻,只是转移了。真正的轻量是全链条的轻量。
原则三:中间方案往往是正确答案。 不是非 SQLite 即 PostgreSQL,不是非手写即全家桶。中间往往有一个充分的选项,工作量刚好、能力刚好。找它。
结语
Tournoij 在文章结尾说了一句话很到位:我们习惯问"真的需要这个吗?"来避免臃肿,但同样值得问"没有这个真的行吗?"来避免 stupid light。
两个问题都问一遍,决策才完整。