开项目评审会的时候,有个老同事冒出一句:“这个先别上调试器了,咱们 caveman 一下。”坐在旁边的新人一脸茫然,后来偷偷问我:啥叫 caveman?我当时乐了——这个词在程序员黑话里,指的就是最原始、最笨拙、但意外有效的那套做法。它一方面被人笑话“像穴居人一样土”,另一方面又总在关键时刻救场。今天我想认真聊聊这个“caveman”,聊聊它背后的一套反潮流方法论:为什么工具链越先进,我们反而越需要保留一点“原始人”的直觉和手段。
1. “caveman”在程序员的黑话里,其实代表一套反潮流的方法论
1.1 最出圈的其实是“caveman debugging”
程序员圈子里流传最广的 caveman 用法,就是caveman debugging(穴居人调试法)。什么意思呢?就是不用断点、不用调试器、不上分布式追踪,而是在代码里埋print、console.log、error_log、System.out.println之类的打印语句,把变量值、执行路径、时间戳直接打出来,靠肉眼观察输出定位问题。
这名字起得损——仿佛你是个没进化好的原始人,只会往地上扔树枝标记路线。但真干过活的人都懂,这种“土办法”在大量场景里就是比精装工具好使。我见过不少团队,一遇到线上问题就先开 APM、调链路追踪,折腾半小时还没定位到具体代码行;而旁边老师傅默默加了一条日志,重新跑一下,十秒钟就锁定了问题函数。你说谁是原始人?
值得说明的是,caveman debugging 不是说调试器没用,而是强调在最直接的证据面前,绕弯子的查法反而低效。打印出来的日志,是程序在真实运行路径上留下的痕迹,不需要复现、不需要断点命中条件、不需要走完整个调用链,它就是第一现场。很多复杂的并发问题、偶发 bug、外部系统交互问题,你用调试器根本挂不上去,只有靠日志里的蛛丝马迹拼出全貌。
1.2 从调试法延伸出来的“原始人哲学”
如果只把 caveman 理解成“多写几个 print”,那格局就小了。我发现,程序员在说某个人或某套方案“很 caveman”的时候,其实是在表达几个更底层的东西:
- 先做能跑的东西,再做优雅的东西。原始人手上的石头棒子虽然不好看,但是一棒子下去真能把猎物放倒。对应到写代码,就是先考虑功能正确、路径可走通,再谈设计模式、代码结构。
- 不要迷信工具,要相信眼睛和常识。很多工程问题不是“不知道用什么工具”,而是“没仔细看过日志、没认真数过数字”。原始人做法要求你先把真相看清楚,再决定上什么家伙什。
- 复杂问题往往需要退到简单层面去解。系统越是纠缠不清,你越不能跟着它一起绕。把问题剥离到“输入、输出、状态”三个层面,像原始人一样只看眼前的火堆和猎物,反而能看透本质。
说白了,caveman 这个词在网络热词里带一点自嘲和调侃,但在工程师语境里,它是一种有意为之的“降维”策略。当周围全是微服务、容器编排、可观测性平台的时候,你敢不敢拿一台服务器、一条grep命令、一本草稿纸去硬解问题?敢的人,才真正理解了什么叫 caveman。
2. 为什么越先进的工具链,越暴露出“原始方案”的不可替代性
2.1 调试金字塔的另一面:打印调试为什么难以被淘汰
有人会把调试手段分金字塔:底层是print日志,中间是断点调试,顶层是一整套可观测性体系(指标、追踪、日志平台、告警)。听起来越高越厉害,但实际干工程的人知道,这个金字塔有个反直觉的特点——越底层的工具,适用面越广,越不可替代。
断点调试有个天然的致命缺陷:需要在你本地环境里复现问题。而生产环境的问题,常常是“这台机器上有问题,换一台就没有”“压测的时候出现,平时不出现”“用户账号是某个特定数据才触发”。你让调试器怎么挂上去?分布式系统里,一个请求要经过三五个服务,断点只能断在你这一台,你永远不知道上游送过来的数据具体长什么样。这时候唯一能还原现场的东西,就是别人打印出来的日志。
可观测性平台当然更系统化,但它的建设成本和维护成本都不低。小公司只有两台机器,你却给整个系统接上链路追踪、指标采集、日志采集三件套,本质是在用大炮打蚊子。还有更尴尬的情况:平台本身出问题了,链路数据迟迟刷不出来,指标面板一片空白,告警都哑了。这种“工具瘫痪”的时刻,恰恰就是 caveman 手段的高光时刻——只要服务还在响应请求,哪怕只是把输出写到文件里,你就有机会用tail、grep、awk这些“原始工具链”把真相挖出来。
2.2 被人嘲笑“土”的 caveman code,反而守住了质量底线
再说写代码。我见过最近几年的一个趋势——代码越来越“精致”,但系统越来越难伺候。很多年轻团队喜欢把简单功能拆成七八个抽象类、引入一堆运行时框架依赖、给每个小操作做泛滥的 AOP 切面。结果就是:代码评审的时候大家互相吹捧“设计得很优雅”,生产一出问题,谁都说不清楚这条链路到底经过了几层代理、几次序列化、几个异步队列。这叫什么?这叫用精致掩盖模糊。
相反,被嘲笑为“caveman code”的代码看起来就太朴实了:函数直接、命名直白、链路清晰,没有太多“聪明”的写法。你读这种代码,就像看原始人凿石头,每一步意图都很清楚:这里要磨尖、那里要加把手、整体就是一根棍子。这种代码也许不够时髦,但它有几个实打实的好处:
- 出问题的时候容易定位。逻辑就摆在那儿,藏不了猫腻。
- 新人接手成本低。不需要理解一堆隐晦的抽象约定,看一遍就能干活。
- 测试能写扎实。因为状态量少、依赖直接,测试逻辑跟着代码走就行。
我不是说抽象和设计模式是错的,而是说很多团队为了抽象而抽象,忘了代码的第一读者是人,第一目标是把事情做对。如果一套“优雅方案”连作者本人都要画半天图才能讲清楚它运转的边界条件,那它在真实环境中就已经是超高风险资产了。我自己写代码的偏好,是默认先按“最直白”的方式写,什么时候真觉得重复代码扎眼了,再做封装重构。这其实就是一种 caveman 精神——先用最笨的方式把事做扎实,再谈优化。
3. 一次线上故障的复盘:我如何靠“穴居人式排查”在40分钟内找到根因
3.1 事故特征与当时的工具瘫痪情况
光讲理念容易飘,讲一段真实经历。去年夏天我们服务过的一个电商类客户,某个下午线上接口开始偶发 500,高峰期失败率爬到 8% 左右。我接手的时候,手里的工具是这样一种状态:APM 平台的采样数据断断续续,拓扑图上有一半节点显示异常,但点进去看不到有效报错;日志平台的检索服务也慢得像蜗牛,搜一个关键字要转十几秒,经常连结果都查询不出来。监控面板倒是有数据,但只能看到整体 QPS 和错误率,完全不能告诉我们应用内部发生了什么。
按照常理,这种“系统级排查无路可走”的局面,最容易把人逼进死胡同:反复刷监控、频繁点刷新、指望平台自己恢复数据。但我当时跟团队说了一句话:“别等了,找一台出错的实例,我们直接上机器看。”这就是 caveman 式决策的开端——既然高级工具指望不上,那就回到最原始的证据收集方式:登录服务器,看进程,看端口,看实时日志,看系统资源。
3.2 退回到 shell、日志和直觉的排查链路
我大致说一下当时在机器上做的事情,这套链路大家可以记下来:
- 登上一台仍在返回 500 的实例,先用
top看整体负载,确认 CPU、内存都很正常,于是第一排除资源耗尽。 - 再跑
ss -antp | head -50(或老的netstat)看连接状态,发现大量 TIME_WAIT 和少量 SYN_SENT,说明网络层面有连接建立困难。 - 之后直接看应用日志:
tail -f /data/logs/portal/error.log,连续刷了几个请求的报错。报错内容指向的是数据库连接获取超时——Connection pool exhausted,简单说,连接池里的连接被拿光了,新的数据库请求都排队等不到空闲连接。 - 为了确认这不是偶发,我用了“原始人统计法”:
grep 'Connection pool exhausted' error.log | wc -l,算了下最近十分钟这个错出现的频次;再awk '{print $NF}'之类的方式提取报错里的线程池大小、活跃连接数等关键数字,手工对比应用配置。
一个很有意思的环节是,当时我们默认怀疑是数据库被打满了,于是我也跑了下 MySQL 侧的show processlist和慢查询日志,结果发现数据库负载很低,活跃 SQL 不多。这反而给了我关键信号:问题不在数据库,而在应用侧怎么分配连接。翻出最近一次的发布记录,果然——前一次发布把连接池的最大连接数从 50 调到了 20,理由是“为了降低数据库压力”,结果 QPS 一上来,20 个连接根本扛不住,所有请求都在排队等连接。配置的人是好心,可没做容量评估,就直接把连接池掐到原来的四成。
3.3 事后验证与团队复盘
定位到根因之后,解决起来其实很快:把连接池上限恢复到 50,再加一个等待超时时间的阈值设置,然后灰度发布。我在机器上守着日志,连续观察了二十分钟,Connection pool exhausted的错误完全消失,接口错误率从 8% 降到 0.2% 以内。整个过程从登录服务器到调整配置加验证,大概 40 分钟,真正的有效排查时间更短。
复盘会上大家聊到一个很关键的点:如果当时死等 APM 平台恢复,可能一两个小时都没法定位,甚至会被不完整的拓扑图误导。那为什么我们敢直接上机器?因为《平台数据是“二手证据”,服务器现场是“一手证据”》这个判断标准一直在起作用。caveman 方式给我们的核心价值,是在噪音中直接抓取确定性——日志文件里就写着报错,连接数就摆在那里,数据库负载清清楚楚,这些证据不需要被采样、不需要被聚合,它们是真实状态的切片。
那一次之后,我养成了一个习惯:遇到线上问题,先别急着开各种工具的面板,而是想清楚“这个问题最直接的证据在哪里?我怎样才能以最短路径拿到它?”如果条件允许,优先拿一手证据;平台数据当辅助参考。这套习惯,说白了就是 caveman 一点,但它救过我好几次。
4. 把“caveman哲学”变成工程决策:什么场景该原始,什么场景该精致
4.1 适合做“穴居人”的几个典型场景
不是所有问题都适合 caveman 式处理。根据我的实战经验,下面几类场景特别适合调用“原始方案”:
- 线上环境、生产事故、无法本地复现的问题。工具越高级越依赖复现条件,而线上事故往往只有一次现场,你必须靠日志、进程瞬时状态、系统调用这些一手数据说话。
- 需要快速划分责任面的跨系统排查。比如一个请求经过了 A、B、C 三个服务,失败不确定在哪一环。这时候与其调各种链路追踪,不如在三个服务入口各加一行带请求ID的日志,跑一遍看日志落到哪里断了——这种“插桩-观察-定位”的流程虽然原始,但极高效。
- 复杂度已经超出了你心智带宽的时候。当一个人同时维护七八个微服务,脑子里塞满各种抽象概念时,最有效的手法是退到具体物理层面:看端口通不通、看进程在不在、看日志报什么错。把思维“焐热”回归到实物层面,往往能瞬间减压、看清路径。
- 团队里有新手需要培养排查感觉的时候。让新人从一开始就用断点调试和可视化面板,他很难建立起“逻辑链路”的直觉;但如果让他用日志和命令行把一次问题从头到尾挖出来,他对系统运转的理解会深得多。
4.2 不适合做“穴居人”的场景:过度原始同样是罪
既然讲边界,就得诚实地说,caveman 方式也有明显的坑。第一个坑是可扩展性极差。假设你的系统有几百个节点,每次都靠人肉登机器查日志,那就是原始人用双腿追汽车,追不上的。这时候你还是需要一套集中式的日志检索和指标告警能力,哪怕是用开源方案自建也行。
第二个坑是临时打印留成了永久垃圾。很多团队调试的时候往代码里插了一堆print,问题解决后忘了删,结果生产环境里大量无意义日志滚动刷屏。日志量大到一定程度,反过来会拖垮 I/O 和日志存储成本,到时候你不是在 caveman,你是在给自己凿坟墓。我自己的习惯是:临时调试日志一定带一个统一的特殊标记,比如TMPDEBUG-2025-0714这种格式,修复完问题用grep TMPDEBUG全局搜一遍,保证删干净,并加一条CI检查禁止这种标记出现在主干合并请求里。
第三个坑是混淆了“直接证据”和“你以为的证据”。即使是用日志定位,也先确认日志格式是否带上了时区、请求ID、线程名等上下文信息。如果代码里打印的本身就是一个错误的局部变量,你照样会被带偏。caveman 不代表可以不看证据质量,恰恰相反,原始人更依赖对自己眼睛的判断力——你得能分清“看到的是现象”还是“看到的是真相”。
4.3 一套简单的判定标准
为了把这个哲学真正落进日常决策,我总结过一个两问判定法,直接套用就行:
- 第一问:我手里有没有一手证据?有日志文件、有进程快照、有系统状态——那就优先用它们,不要绕道平台。没有一手证据,再考虑花成本去搭工具、拉面板。
- 第二问:完成这件事,最低需要几层中间环节?如果答案是零,就直接做;答案是一,就评估那个中间环节是否可信;如果答案超过二,那就强烈怀疑整套方案是否被过度设计了。
举个例子:你要确认一个服务是否活着。最 caveman 的做法是ps aux | grep 服务名或者直接curl一下健康检查接口,一步到位;你要是为了这个场景专门去搭一个监控大屏做轮询展示,那只能说杀鸡用了牛刀。反过来,如果你要观测几百台实例的趋势性变化,靠ps就不够用了,因为人眼没法同时盯几百个进程列表——这时候集中式监控不是“精致过度”,而是必要基础设施。原始和精致,从来不是天然对立,而是要在问题规模和工具成本之间找平衡点。
5. 做个清醒的原始人:关于这套理念,我最后想说的几件事
写了这么多,其实就是想表达一个观点:caveman 这个热词背后,藏着很多工程师真正应该坚守的思维方式——“永远先找最直接的事实,永远不被工具绑架判断力”。它不是一个贬义词,更像是一种自我提醒:在你被各种复杂系统包围、越来越依赖平台时,别忘了你有眼睛,有直觉,有最朴素的逻辑能力,这些才是最重要的。
我个人在实际操作中的体会是,这套理念最合适的落地方式,不是全盘否定现代工程体系,而是在每个环节里保留一个“原始逃生通道”:
- 代码里,留几条高质量的关键日志,别让打印变成噪音;
- 排查时,先想清楚一手证据在哪,再决定要不要拉平台;
- 架构上,保持系统简单到“默写一遍调用链”不会出错的程度;
- 团队里,多组织几次“不许用监控面板,只准用命令行排查”的演练。
最后再分享一个我觉得很实用的小技巧:平时你自己折腾任何软件、脚本、服务,可以试着准备一个固定的“caveman 工具包”,里面就几样东西——一个能开终端的 SSH 工具、一个日志文件路径的速查清单、一份系统常用命令的备忘卡(top、ss、ps、tail、grep、awk、curl),再加上你自己的好奇心。这些东西在任何一台 Linux 机器上都存在,不需要安装、不需要授权、更不会随版本迭代消失。你把这些用熟了,就会发现在这个充满框架和平台的年代,做一个“清醒的原始人”反而是一种难得的踏实。