ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

caveman式开发:拒绝过度设计,用最简单工具解决工程问题

caveman式开发:拒绝过度设计,用最简单工具解决工程问题 “caveman”这个词我第一次认真对待是因为同事在代码里留了一行注释“TODO: caveman fix this”——意思非常直白别绕弯子了直接改。当时我还是个刚工作不久的新人觉得这种写法不够“专业”。几年后我彻底转变了想法因为被一套又一套精密系统坑过之后我发现真正的工程智慧往往是反着来的能用锤子解决的问题就别先造一台机床。这个季度我处理了一个困扰团队两周的线上问题最后救场的不是监控大屏不是全链路追踪平台而是一条grep命令和一份最原始的日志文件。从那时候起我开始认真思考什么叫做“caveman式开发”并且刻意把它训练成自己的默认方法论。如果你也受够了无休止的方案评审、层层嵌套的抽象封装、以及那些听起来很厉害却总在关键时刻掉链子的基础组件这篇文章应该能提供一些真正有用的思路。我所说的caveman不是让大家都去当数字野人而是倡导一种原始主义工程观在动手解决问题之前先把手边最简单的工具用透把问题的第一现场看清楚而不是第一时间引入新的复杂度来“现代化”地处理它。这套方法不反对工程化但反对为了工程化而工程化。1. “caveman”到底是什么技术圈的返祖现象1.1 从洞穴人画像说起最原始的工具为什么依然可靠每个人脑海里都有一个洞穴人形象裹着兽皮手里攥着石斧没什么多余的家当。遇到野兽第一反应不是掏出手机搜索“如何优雅地赶走猛犸象”而是举起石斧就抡过去。这个画面放到软件开发里就是所谓的caveman风格——用现成顺手的手段直接解决问题在确认问题真的存在之前不为它设计宏伟的解决方案。举个最日常的例子。排查线上故障很多人一上来就打开APM系统翻分布式调用链看火焰图查慢SQL。这套流程没毛病但有一个前提你得先知道故障到底发生在哪个节点。多数一线工程师都有过这种体验——链路追踪平台明明显示整个链路都是绿色的但用户反馈就是超时。这时候你翻遍监控面板都找不到答案反而直接登录服务器grep一下那个时间段的应用日志三分钟就能还原出调用时间线。这就是caveman思维先看石头底下压着什么再决定要不要叫重型机械过来。“原始工具”往往更可靠是因为它们贴在数据旁边中间没有任何翻译和抽象层。日志文件是程序直接写出来的grep直接读它数据库直接查结果就是记录本身。而链路追踪、调用链、监控面板这种系统再好也是在原始数据外面套了一层又一层模型和推断。模型和推断就意味着误差意味着某些边界情况会被吞掉。1.2 为“现代工程化”付的成本真的都值得吗我见过很多人包括以前的我自己陷入过一个误区把“用了多少先进工具”当成团队技术水平的度量衡。微服务上了服务网格上了分布式事务也上了最后做一个只有三个接口的内部管理系统。折腾两个月团队最大的成就是把一个五分钟能解决的问题变成了一个需要协调三个小组才能上线的“系统”。这不是说工程化不好工程化解决的问题全是真实的高并发下的水平扩展、多团队并行开发、复杂状态的一致性和多活容灾。但问题是大多数业务系统在百分之九十以上的时间里根本遇不到这些场景。为一个可能永远不会发生的极端情况提前支付了一整套架构的维护成本这是现代软件项目最常见的浪费。有一次我被拉进一个技术选型评审会需求很简单管理后台要一个定时任务每天凌晨同步一份数据到另外一个库数据量不超过一万条。有人提出要搭建分布式调度平台理由是“以后任务会变多”。我问了一个很caveman的问题“以后是多久以后现在这个任务用crontab跑有什么问题”沉默了很久最后方案改成了单机crontab 一个失败告警脚本半小时上线。项目跑了两年每天的任务没出过一次问题。后来任务确实变多了但多到需要分布式调度平台的时候我们自然就知道了——因为那个时候简化方案会先出现无法承受的痛苦而不是我们先铺好路等它。2. 现代项目是怎么一步步把自己“做沉”的2.1 每一层架构都是在现有复杂度上再叠一层我习惯用一个词形容复杂度叠加的过程套娃。本来是一个单体应用为了“解耦”拆成十几个微服务每个微服务需要自己的注册中心、配置中心、链路追踪、日志收集、监控面板。为了保证服务之间通信可靠引入消息队列为了保证消息不丢又引入事务消息和死信队列死信队列没人消费于是又搞了一个告警平台来盯着它。到了最后业务代码只占整个仓库的一小部分大部分代码都是在解决“因为引入了某个组件所以必须配套的设施”。这不是我虚构的场景是我在几个项目里真实见过的路径。每个组件单独看都有充分的引入理由但它们加在一起的体积和认知负荷远远超过了它们解决的问题。你每引入一个中间件团队里所有成员的大脑里就得多装一份概念模型——这个组件怎么配置、怎么排查、怎么升级、它和另一个组件之间有哪些坑。而caveman思维恰恰是对这套逻辑的反动每加一层复杂度之前强制自己回答一个问题——“这个组件解决了哪个现在正在痛的环节”如果答案是“我们觉得以后可能会需要”那这个组件就不应该进系统。2.2 那些让人哭笑不得的“过度设计”现场我印象最深的一个项目是为了做权限控制团队专门设计了一套基于规则引擎的动态权限系统支持可视化编排、灰度发布、实时生效。结果上线半年规则引擎的页面总共被打开过三次所有实际权限需求用代码里的一个硬编码数组就能覆盖。那个规则引擎占用了两个人将近三个月的人力。还有更离谱的一个内部统计分析工具数据量不超过千行非要引入OLAP引擎和数据仓库原因是“数据分析就应该用分析型数据库”。其实这类场景里原始方案写一个Python脚本读一遍Excel或者CSV出个统计结果可能连一分钟都花不到。我把这种过度设计归因于一个心理机制工程界普遍崇拜“复杂度”因为它看起来更像在做正经事。负责的设计方案、复杂的架构图哪怕后续毫无价值至少当下能让人有一种“我们很专业”的错觉。caveman式的方法论要求人从这种错觉里清醒过来方案本身没有价值能稳定、简单、可维护地解决问题才有价值。2.3 用一张“复杂度清单”来逼着自己做减法现在我对每一个新功能都会先列一张原始方案清单。格式固定如下这个问题如果不用任何组件只靠操作系统自带能力和一门脚本语言怎么做如果必须引入组件哪一个组件是最小可用级别它带来的最核心复杂度是什么如果这个功能完全不做了业务会有什么损失损失是否可接受当前方案比原始方案多解决的每一个子问题最近一个月内是否真实出现过这张清单和架构评审最大的区别是它先把所有花哨的东西剥掉逼着你看见问题的底座。大多数情况下你会发现底座本身能承载绝大部分需求。那些额外叠加的功能要么是纸面上的想象要么是极低概率的边缘场景。我常用一个比喻来解释这件事盖房子当然要打地基但不是每一间工具房都需要找设计院出桩基图纸。先盖一间朴素的工具房等真的发现需要拓宽成仓库时建材和经验都已经齐了不会浪费什么。3. 三个亲身经历的“原始方案完胜”案例3.1 用grep和日志干掉了链路追踪查不出的故障上面提到过这个问题具体展开说说。当时我们接了一个新业务上游系统调用我们我们再调用一个第三方服务整体链路已经接入了公司统一的分布式追踪平台。用户反馈高峰期接口经常要等七八秒。按照惯例我们打开追踪面板去看链路耗时分布结果发现无论刷新多少遍那个第三方服务的span耗时都是绿的平均只有200毫秒。诡异就诡异在这里。理论上调用链完整展示了一次请求的每一跳耗时但实际上追踪系统依赖上游服务正确传递trace header。如果上游某台老机器没升级SDK请求就没带trace信息平台端根本采集不到这一部分调用链的数据于是只能看到“什么都没发生”。这个现象在业内其实很常见链路追踪平台永远不会告诉你它漏掉了哪些数据。后来我放弃追踪平台找一台边缘机器模拟高峰期的请求同时直接去跑接口的服务器上把请求日志捞出来。因为日志里只打印毫秒时间戳和耗时我把同一批用户请求的日志按时间排序手动拼出一条时间线立刻看到问题请求在网关层等了四秒钟第三方服务实际只处理了不到半秒。最终定位是网关一个连接池参数在峰值时被耗尽和第三方服务本身毫无关系。整个排查过程用了不到半小时而之前依赖链路追踪平台整整绕了两天。这两个方法之间的差别本质上是链路追踪是对的但它的数据完整性依赖于所有参与者都正确埋点。而日志是程序直接吐出来的事实哪怕没有监控事实也一直在那里。遇到工具失效先回归到最底层的事实这可能是caveman方法论最核心的一种姿态。3.2 用一台crontab和心跳日志替代了分布式调度平台当时公司内部有一款调度平台宣传语很响亮支持分布式高可用、分片任务、失败重试、弹性伸缩。我们新接了一个对账需求每天凌晨两点从主库导出数据跑一遍对账逻辑结果写到另一个库。数据量很小总共几千条但发布到生产之后团队里的架构师说“不能随便拿台机器跑crontab至少用调度平台吧不然机器挂了怎么办”。这个说法听起来很合理但我强行做了一次回归测试把对账任务手动挂到一台闲置机器上用crontab触发。任务逻辑本身是幂等的即使重复执行结果也不会错而且它每天只运行一次一次最多半分钟完全不存在资源竞争。至于“机器挂了怎么办”——最坏的情况是某一天对账没有跑第二天补跑就行业务完全可以接受。于是我在crontab之外加了一行“心跳”任务跑完写一条带时间戳的记录到独立的健康检查文件再由一个外部监控脚本每分钟探一下这个文件的新鲜度。如果超过36小时没更新告警自动发出。就这样原本评估要两周才能接入调度平台的方案压缩成了半个小时的部署工作。这个任务上线至今一年半没有一次漏跑没有一次告警。这个案例让我越发坚信所谓“稳定性”不是组件堆出来的而是“失败影响是否可控”决定的。如果失败代价低那么单点执行完全可以只有在失败代价高、需要秒级恢复的场景分布式调度才是必要项。否则就是用百分之百的成本去解决百分之二的风险。3.3 用静态JSON文件绕开了整个权限配置中心还有一次产品提了一个需求某些资源在某段时间内对某些用户开放白名单运营可以在后台直接开关。需求一报技术方案会上自然又有人提出用权限配置中心说可以把开关下发给所有节点并且支持灰度。我听完觉得哪里不对劲仔细想了一下开关本质上是“一个可以随时改的布尔值”它需要热更新吗需要按节点灰度吗不需要。最后实现方案非常原始把这个开关定义在一个JSON文件里文件带版本号打包在应用配置中。要改的时候产品发一个变更工单开发改JSON、提交、走CI、自动发布整个流程大约十分钟。上线半年这个开关被改过两次每次都是运营提前一天提出根本谈不上“实时紧急”。很多团队的默认值都是“越高级越好”caveman的默认值是“越省事越好”。配置中心本身不坏但为了一个低频、低风险、可容忍短暂生效延迟的布尔开关去引入一套需要维护客户端和后台的系统属于拿大炮打蚊子。真正需要配置中心的应用都是配置变更频繁、影响面大、没法容忍分钟级生效延迟的系统。在遇到那个场景之前文本文件发布流程永远是最务实的选项。4. 把caveman思维落地成可执行的四条原则4.1 原则一先到第一现场拒绝“隔着屏幕猜”很多工程问题之所以拖得久不是因为难而是因为所有人都在二手信息里打转。线上出故障了第一反应是拉群由值班同学贴一张监控截图然后所有人开始凭空猜测是缓存、是数据库、还是网络问题。正确姿势只有一个第一时间拿到证据——日志原文、请求报文、进程状态、资源使用率甚至直接复现。这句话听起来像废话但实操中大量工程师在信息不足时就开始开脑洞。caveman式排查有一个硬性要求在没有看到足够多的事实之前不允许讨论“可能的原因”。我自己现在排查问题前十分钟基本只做一件事——把所有相关服务的日志抓出来按时间对齐。大多数技术债和隐藏故障在时间线对齐的那一刻就无处遁形。剩下的功夫人均都能做。4.2 原则二写方案时先问“十年后还有人看得懂吗”我给自己的方案定了一个主观准入门槛这个方案的结构和代码如果一个没参与这次讨论的同事一年后或十年后打开仓库能不能在十分钟内看懂如果答案是“要先了解这三个设计模式和一个框架的内部原理”那这个方案大概率过度设计了。技术圈里有一个普遍误解觉得用了设计模式就是架构好用了复杂抽象就是扩展性强。实际上大部分系统根本等不到需要扩展的那一天就被重写了。如果一个方案需要讲解PPT才能让别人理解它本身就变成了团队的认知税。真正的简洁方案自带解释能力——它的文件名、函数名、数据流和业务逻辑一一对应不需要补充任何额外文档去解释“为什么代码长这样”。4.3 原则三单一数据源是避免复杂度的总开关我发现大量复杂系统的复杂度根源都是同一个数据被复制了多份还需要保证同步。最经典的就是配置一份配置放在Git里一份放在配置中心一份放在数据库改的时候谁知道哪个才是生效的版本而caveman式的智慧是物理上把“唯一真值”放在一个地方其他地方要么引用要么不存放。这样不管出了任何问题都有一个绝对基准可以去对。这个原则同样适用于权限、规则、甚至业务状态。如果一份数据必须要多份存储至少要让其中一个成为权威源并且所有的读取路径都站在同一个位置上而不是各自为政。很多无休止的“数据不一致”bug根上都是没有守住单一数据源。4.4 原则四允许手动步骤存在但手动步骤必须能被脚本复现顺着前面三个原则很多人会以为caveman就是“什么都手工来不接受任何自动化工具”。其实不是。我建议的原则是初期用手工步骤快速跑通流程但每完成一次手动操作就顺手把它复制成一段脚本或一份文档。这样既避免了启动时过度设计又不会让知识只存在某个人的记忆里。比如那个crontab案例最开始的对账脚本是手工在命令行里敲的。确认逻辑正确之后我很快把它落成脚本文件并纳入版本管理同时写好启动说明。这既保证了快速试错又确保了后续可重复执行。手动不丢人不可复现的手动才丢人。5. caveman的边界什么时候必须放弃“原始”5.1 需要无人值守的高可用场景不能迷信单点谈完了优点还是得泼一盆冷水。caveman并不是在任何场景下都适用。有一种情况我必须老老实实承认确实需要复杂的工程化——当系统承担核心资金、核心用户数据、或者故障代价以秒计费时单点脚本和“事后补跑”的思路就完全不够了。举一个很直接的反例支付回调对账如果漏跑或延迟直接导致资损那这个任务就必须是多副本、失败自动重试、有完善的监控告警和补偿机制的。因为此时它的失败代价已经高到无法承受而分布式调度系统带来的复杂度是为了对冲不可接受的后果。这个判断标准很简单如果损失很小单点方案可以接受如果损失成指数放大复杂方案就是保命的保险。5.2 多人协作和跨团队场景需要契约不能靠默契caveman的另一个适用局限在“单人或小团队拥有完整上下文”的前提下。当系统需要多个团队、甚至十几个团队共同维护时接口契约、版本兼容、数据模型这些“结构性设计”就成了必需品不是为了装高级而是不同团队之间没法靠默契协作必须靠一份公用的契约来对齐预期。这时候如果还坚持“先写个临时脚本谁需要谁自己改”系统会在极短时间内变成一团乱麻。所以我的建议是caveman思维最适合用作团队内部的默认值但当跨团队协作成为常态该画的边界、该定的规范、该建的基础设施一条都不能省。这里的复杂度是有效复杂度。5.3 安全、合规与审计场景结构化模型不是可选而是刚需还有一种情况原始方案天然不适用就是在需要完整审计轨迹、权限最小化、访问控制和数据保留策略的场景里。比如敏感数据访问、生产环境变更、用户隐私处理这类操作不能被某个人“手敲一下命令就完成”必须有身份认证、审批记录、操作日志留痕。这种复杂度不是被技术驱动的而是被外部合规要求硬性约束的。我自己在碰到这种需求时会毫不犹豫地放弃“最简单”的方案转而选择经过验证的权限模型和审批流程。因为在这个特定领域里caveman方案即使能跑通功能也无法回答“谁在什么时间什么设备上改了数据”这类问题。对这种场景任何简化都意味着风险。最后分享一个我坚持了很久的习惯每个新功能动工之前我会先用五十个字在笔记里写下它的“原始版本”——它需要什么输入、产出什么结果、手动执行会不会出错。如果原始版本已经能覆盖八成需求我就先照这个做然后把剩下的两成当作演进的方向。这个习惯帮我砍掉了大量不必要的架构设计也让我真正感受到了“不被复杂系统绑架”的轻松感。建议你找一个本周的小需求试着用caveman方式做一遍做完你会发现少一点精致其实离问题更近。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进