我最早听到"caveman"这个词,不是从化石纪录片里,而是自己的一个极简项目上线后,同事看了一眼仓库说:"你就用这么朴素的方式做?"我说了一句"嗯,因为它是caveman"——一个刻意保持原始的、只用最小工具链的项目代号。这个名字后来被我延伸成了一套做事的思路:刻意把复杂度压到不能再低,低到像穴居人一样只用最顺手的那几样东西,却依然能解决真实问题。
如果你也是一个人维护项目、在小团队里写代码、或者长期被"现代工程最佳实践"压得喘不过气,这篇文章会很对你胃口。我会从一个真实工具的开发过程出发,拆解caveman式工作法的三个底层原则、完整实现路径、在几个场景里的实战结果,以及哪些情况你必须果断放弃它。全程不空谈理念,只有可以直接复用的判断标准和操作方法。
1. 项目背后:caveman 到底在抵抗什么
1.1 从一次"连不上网就干不了活"的经历说起
我做过一个内部小工具,功能不复杂:把散乱的文件按命名规则批量分类。最初版本是现写的Python单文件,大概400行,没有任何第三方依赖。运行方式也简单,python tidy.py --source ./downloads,完事。
后来我本着"规范化"的想法,引入了配置管理、日志系统、参数校验库,还接了一套构建脚本。结果有次出差,在高铁上想临时改一下分类规则,发现项目跑不起来了——因为那套"工程化"依赖需要从内部镜像拉包,凭证过期了。我盯着终端里的报错,突然意识到:这个工具真正需要的,就是单文件、Python标准库、一行命令。我花了整整一个下午,把它的所有额外依赖全部删掉,回到caveman状态。
那次之后我明白一件事:大多数工程复杂度不是一开始就需要的,而是"觉得迟早会需要"才引入的。Caveman式思路抵抗的,正是这种基于假设的复杂度。它要求你诚实回答:如果没有这些工具,我的核心流程到底会卡在哪里?大多数时候,答案是不会卡。
1.2 所谓"现代工程能力"里有多少是真实需求
你我心里都清楚,今天的项目仓库里塞着多少"防御性配置"。我见过一个实际逻辑只有五百行的项目,却配了ESLint、Prettier、Husky、Commitlint、Playwright、Docker、多环境变量文件、GitHub Actions。你说这些没用吗?有,但那是给十人团队、持续三年迭代、多人交接的场景准备的。如果项目是自己维护的小工具,这些配置的实际效果只是把每次改动从五分钟拖成二十五分钟。
Caveman式思路不是反技术、反工具。它只是把工具按"需要"而不是"流行"来做选择。一个判断标准我用了很久:如果你去掉了某个环节,项目的核心价值依然成立,那它就不是必需品,而是多余的护卫。真正的工程能力,不是堆砌了多少现代工具,是你知道在什么粒度上停下来。
2. Caveman 的底层三原则:依赖最小化、抽象随用随取、流程一眼看穿
2.1 依赖是负债,不是资产
几乎每位工程师都经历过"更新依赖后老代码跑不通"的至暗时刻。第三方库在你引入那一刻,就同时注入了三笔长期负债:安全通告要跟进、API变化要适配、行为差异要排查。
我给自己定了三条硬性规则,每一次想引进新库之前都必须过一遍:
- 它省下的时间,是否远大于它未来五年可能吃掉的时间?
- 它解决的是核心问题,还是边角料?
- 如果明天卸载它,我的改动量有多大?
举个例子。在Python里处理JSON,很多人习惯引入pandas或requests这类重武器。但其实标准库json加上pathlib已经能覆盖八成需求。你要做的只是把JSON文件读进来、改几个字段、再写出去,标准库干净利落,还不用考虑pandas版本和Python版本的匹配问题。
Caveman式依赖原则绝不是"零依赖",而是"每一个依赖都要有明确的服役期"。如果某个库只负责一个很小的功能,你完全可以自己写那二十行代码,把控制权留在自己手里。
2.2 抽象延迟到第三次重复
"不要重复自己"(DRY)是很多人从入行就听到的金科玉律,但也正是它成就了大量过度设计。我见过有人为了两个地方共用一段差不多的逻辑,提前造了一个配置驱动的抽象层,结果三个月后需求一变,那段抽象层成了唯一没人敢改的东西。
Caveman式抽象原则很朴素:第一次出现直接写,第二次出现可以复制再小改,直到第三次重复才抽取公共函数或组件。
举个具体场景。我在做文件分类工具时,最开始需要提取"日期+项目名"这种命名模式,分别在处理图片、文档、压缩包三个分支里用到。前两次我直接写了相近的逻辑,等到第三个分支出现时,我才抽出extract_meta(filename)这个函数。这时它的入参、返回结构和调用方式都已经经过真实场景验证,抽象得恰到好处。如果一开始就抽,我大概率会猜错参数设计。
记住,抽象必须要由真实重复驱动,而不是由对重复的恐惧驱动。
2.3 构建与运行流程要像"点火"一样简单
Caveman项目有个直观特征:跑起来的方式简单到不用看文档。通常就是一条命令,或者直接执行一个脚本文件。
我倾向于把事情拆成一个"运行时最小集":语言运行时、标准库、一个文本编辑器、一个终端。就这四样。不需要包管理器?不,可以用,但要在项目真正开始依赖别人之前,始终问一句"我真的需要它吗"。
下面这个对比表是我在一次分享里列过的,基本概括了我对两种方式的理解:
| 维度 | 现代工程化方案 | Caveman方案 |
|---|---|---|
| 运行环境 | Docker容器、云服务、多阶段构建 | 本机语言运行时 |
| 依赖数量 | 数十个到数百个 | 0到5个 |
| 启动命令 | 多步流水线、配置环境变量 | 一条命令 |
| 故障排查范围 | 需要查依赖链、构建缓存、网络配置 | 直接看代码和数据 |
| 扩展方式 | 加模块、加队列、加服务 | 先加函数,不行再加文件 |
这不是说"现代方案"错了。它的复杂度大部分是响应规模化需求的必要成本。但caveman式的意义在于:如果你不需要规模化,就不要提前付这笔成本。一个运行流程如果要经过五步配置才能跑通,那么出问题时你很难判断是代码问题还是环境问题。
3. 用 Caveman 思维从零做出一个可用工具
3.1 从"需求的一句话描述"开始
Caveman式开发不是想到哪写到哪,它有非常严格的收敛过程。第一步,把所有模糊想法压成一句话。
我当时的需求是这样写的:"输入是一个目录,处理是将其中所有文件名按日期和类型重命名归类,输出是整理后的目录结构,失败时打印出未处理的文件名和原因。"
一句话里必须说清四件事:输入是什么、处理做什么、输出是什么、失败怎么办。很多时候项目做歪,是因为只定义了前两项,根本没定义后两项。一旦你写出这句话,你的项目边界就定了,后面的所有代码都是在兑现这句话。
然后把它扩展成可以验收的几条标准:
- 能处理至少两类文件:图片类和文档类。
- 重命名规则统一:
YYYY-MM-DD_projectname_type。 - 无匹配规则的文件原样保留,并在结束时列出来。
- 整个操作可以一键撤销(备份日志)。
有了这些,我就可以把"做得多豪华"的冲动收起来了。
3.2 物理边界:单文件、单命令、单输出
Caveman式工具最典型的形态是单文件。为什么强调物理边界?因为单文件意味着:
- 你打开它就能看懂全部逻辑,不需要在十几个模块之间跳转。
- Git跟踪、代码评审都很轻松,diff一目了然。
- 拷到任何一台有Python环境的机器上就能跑,没有安装依赖一说。
- 重构成本极低,重写一版也就是半小时的事。
我当时的结构是这样组织的:
tidy.py - main() - parse_args() - scan_files(source_dir) - extract_meta(filename) - resolve_conflict(target_name) - move_and_rename(file) - backup_log()每个函数负责一件具体的小事,函数之间只用返回值传递数据,不用类、不用全局状态。这样就算没有任何框架,逻辑依然清晰。
3.3 实现路径:从第一行能跑的代码到可用
我一直强调,开发时不要试图一次写完美。用caveman式方法,尤其要遵守这个节奏:
第一步,先把主线跑通。忽略所有异常处理,只做"扫描目录、提取信息、重命名、移动文件"这条最开心的路径。这一步能跑,你心里就有底了。
第二步,加入失败路径。考虑文件不存在、目标重名、文件被占用、没有匹配规则。resolve_conflict()就是在这一步引入的:如果发现目标文件已存在,自动在文件名后追加_1、_2。
第三步,加入帮助说明。--help可读、参数名直观,这是给未来的自己看的,不是给用户看的。
第四步,用三个测试目录实测。一个乱序目录、一个含重名文件的目录、一个空目录,每个都跑一遍,记录输出和日志。
整个过程中我没有引入任何一个第三方库。处理路径用pathlib,移动文件用shutil,解析参数用argparse,全部是Python标准库。最后统计下来,核心代码也就三百多行。
3.4 验收标准与"删代码"清单
验收阶段我只做两件事。第一,对照之前写的验收标准逐条检查;第二,认真地删代码。
删代码这件事,很多人不会当回事。但在cavema式开发里,"删"和"写"同等重要。我整理了一份删代码清单,每次交付前都会过一遍:
- 有没有为"未来可能用到"写的配置项?删掉。
- 有没有为"万一用户想这样"定义但现在还没人用的参数?删掉。
- 有没有"对称地"实现了一个正向功能的反向功能?比如有了"归档"就顺手写了"取消归档",但需求里根本没有后者?删掉。
- 有没有为了某个设计模式硬拗出来的接口层?删掉。
我在那次重构里删掉了大约一百五十行"防御性代码"。删完再跑一遍验收标准,全部通过。那种感觉就是:代码变得诚实了,每一行都有存在的理由。
4. 实战验证:Caveman 方式在三个场景下的表现
4.1 命令行批处理:最大的坑是"顺手引入的可靠性负担"
命令行批处理是caveman式思路最舒服的领域,但它也藏着一个坑:你以为引入一个库会让程序更可靠,结果它反而带来了可靠性负担。
我有一次处理日志数据,要提取时间戳和错误码。本来用标准库re写个正则,二十分钟搞定。但我当时"顺手"引入了一个名为dateutil的库来解析时间,理由是"规范"。结果它和系统里的另一个包的版本冲突,解析明明正确的格式却抛异常。那一刻出了问题都不在业务逻辑,全在依赖关系上。
那天之后我给自己立了一个规矩:批处理脚本里,能用标准库就绝不用外部库。脚本的生命周期通常很短,短到不值得为它维护一份依赖清单。如果脚本真活过了一年,到时再按需引入也不迟。
4.2 极简前端页面:不用构建工具时的边界
有人可能觉得caveman式只适合后端脚本,前端离了框架根本没法活。我实际做过一个内部报告页,用的是纯HTML加CSS加原生JavaScript,一个单文件,没有构建工具,没有框架,甚至没有专门的服务端渲染。效果出乎意料地好。
它的典型优点:拷贝到任意一台电脑上用浏览器打开就能看,不需要Node环境、不需要npm install、不需要处理跨域代理。对于一个每周自动生成一次、给三五个人看的数据报告,这套方案的学习成本和维护成本都接近于零。
但我也要诚实说说它的边界。当页面开始需要复杂状态管理、重复交互组件、多视图路由时,原生方案的代码量会急剧膨胀,可维护性开始崩坏。这时候引入一个轻量框架是合理决策。关键在于:是"按需"引入,而不是"默认"引入。Caveman式前端不是永远不用框架,而是在不用框架也能解决问题的时候,敢于不用。
4.3 个人知识库:文件优先而非数据库
第三个场景可能出乎意料,是个人知识库。我用了好几年纯Markdown文件的方式管理笔记、文章草稿和项目文档,目录结构靠手动维护,检索靠文本搜索工具。有人会问:怎么不用双链笔记、怎么不用数据库?
因为caveman式存储有一个无可替代的优势:数据不锁死。所有内容都是可读的、可批处理的、可迁移的纯文本文件。换任何工具都能带走,任何编辑器都能打开。即使我对某篇笔记的内容已毫无印象,只要记得一个词,就能在文件里搜到那次记录的全部上下文。
它的缺点也明显:当你需要"所有2023年包含某标签但未完成的文章"这种结构化查询时,纯文件方式就力不从心了。我的经验是:当查询复杂度超过五种固定模式时,才值得引入数据库。在此之前,文件系统加上一套好用的命名规范,比任何花哨系统都更caveman、也更可靠。
5. 什么时候必须放弃 Caveman 思维
5.1 团队协作与多人维护:约定价值超过极简价值
Caveman式思路有一个致命弱点:它高度依赖"一个人能完整理解整个系统"。当团队人数超过四个人左右,问题就来了。别人不会像你一样清楚"为什么这个函数放在这里""为什么没有类型标注""为什么异常处理这么潦草"。你们必须靠显式的契约来协作,包括接口定义、类型系统、文档、甚至标准化的提交信息。
这不是说团队不能极简,而是极简的对象要从"代码结构"转换成"依赖数量"和"流程步骤"。团队项目里我依然坚持依赖最小化,但我会牺牲一部分代码形态上的自由。类型要写、接口要定、模块划分要明确,这些约定是团队协作的润滑剂,属于必要的复杂度。
5.2 高频变化与并发要求的场景
Caveman式方案适合长期稳定、低频变更、单线程直白的逻辑。一旦场景变成高并发任务、分布式状态、数据一致性要求,它的简单性就反噬了。
举个例子,你用一个单文件脚本处理任务,机器上跑着100个并发实例都往同一个目录写文件,文件名冲突、并发写损坏、部分失败重试,这些问题会一拥而上。此时caveman式"直接操作文件"的直觉方案,远不如一个成熟的队列系统可靠。不是你的逻辑不行,而是你正在用单线程的思路管理一个多线程的世界。
在这种场景里,果断放弃部分极简,引入成熟的基础设施,才是正确决策。
5.3 我的三步决策框架
每个项目开始之前,我会用三个问题决定要不要采用caveman式思路。这三个问题后来几乎刻进了我的肌肉记忆:
第一问:失败代价有多大?如果工具出问题只是损失几分钟重跑一次,放心用caveman。如果出问题会导致数据丢失、金钱损失、客户投诉,立刻走向工程化。
第二问:维护者有多少、时间跨度多长?只有我一人维护、项目预计生命周期在一年内,caveman是最高效的选择。三到五人协作、计划维护三年以上,需要更多规范和结构。
第三问:变更频率如何?核心逻辑稳定、需求基本定型,caveman省心可靠。如果需求还在快速探索期、每周都在改,你可能需要更强的测试保障和更强的模块化,意味着更多工程化。
这个决策框架帮我处理过很多纠结:有些项目我坚持用单文件脚本,有些项目一上来就规规矩矩搭结构。关键不是哪一种更高级,而是哪一种和实际情况更匹配。
6. 三个 Caveman 式习惯,我用到现在依然受用
6.1 先给文件命名,再写代码
我会在写任何代码前先想清楚:这个工具应该叫什么文件名?比如这次是tidy.py,下次可能是merge_pdf.py、dedupe_csv.py。文件名一旦定了,项目的使命就定了。这个习惯看起来简单,却能有效阻止项目失控扩展——一个叫tidy.py的文件,你不太好意思在里面加"顺便实现一个网页爬虫"。
6.2 把"version 1"写进文件头
有人觉得用Git就不需要写版本注释了。但caveman式工具往往是散落在各个目录里的独立脚本,并不都纳入版本管理。我在每个脚本文件头部留三行注释:用途、最初创建日期、最后修改日期。这几行字在半年后重新捡起旧脚本时,能省掉大量猜测时间。
6.3 定期"断舍离":每月清一次项目
我每个月会花半小时列出自己的所有项目,问一个问题:如果这个项目今天消失了,我会不会在三个月内因为没了它而难受?答案是不会,就把它归档或者删除。这不是浪费,这是给未来的自己减负。项目不是越多越好,能维护的项目才是资产。caveman式思维最终指向的,是对"拥有"这件事的克制。
我自己在反复实践caveman式思路之后,最大的收获不是代码变短了、依赖变少了,而是对系统有了完整的掌控感。我清楚地知道每一条命令会触发什么、每一个文件在哪、每一个改动影响什么。真要说什么是caveman精神,大概就是:用最少的中间环节,保住亲手做出东西的能力。