我曾经以为,所谓的"架构能力",就是能把简单的业务画出最复杂的架构图。
直到2020年,我负责一个企业内部报销系统的小项目。当时为了展示技术深度,我强行引入了微服务、CQRS(读写分离)和全套分布式事务方案。结果呢?一个本该2周上线的CRUD项目,硬生生拖了1个半月。最讽刺的是,上线后第一周,运维报警群炸了——不是因为流量太大,而是因为我引入的中间件太多,服务器内存爆了。
那个深夜,看着控制台满屏的报错,我意识到:在中小团队里,过度设计不仅是浪费,更是一种技术负债的犯罪。
很多技术同学,尤其是有追求的开发者,很容易在技术评审时陷入"自嗨"模式。今天我们就来聊聊,那些打着"长远考虑"旗号的过度设计坑,以及如何避开它们。
一、 “万一以后用户量破百万怎么办?"——拒绝预设性扩容
这是评审会上最常听到的一句话。当我们讨论一个只有500个日活的内部系统时,总有人会跳出来问:“现在的MySQL单表只能抗两千万,万一以后业务爆发怎么办?我们要不要先上分库分表?”
真实案例:
2021年,我们做过一个针对供应商的采购管理系统。需求很简单:下单、发货、对账。当时团队里的一位资深开发老张,坚持要引入RabbitMQ做异步解耦,理由是"为了应对未来可能的高并发下单”。
结果是灾难性的:
- 开发成本激增:本来直接写库的操作,变成了 生产消息 -> 消费消息 -> 处理失败 -> 死信队列 -> 补偿机制。代码量翻了三倍。
- 排查难度地狱级:采购员反馈"订单状态没变",我们得去查MQ日志、查消费端日志、查数据库,链路长得让人绝望。
- 真实数据打脸:系统运行了两年,峰值QPS从未超过5。为了这5 QPS,我们维护了一套高可用的MQ集群。
避坑方法:10倍原则
我现在做技术评审,坚持一个原则:只为当前规模的10倍做设计,而不是100倍。
如果现在只有1万数据,按10万数据的规模设计,单表绰绰有余;如果未来真的到了1000万,那时候的业务逻辑和现在的代码大概率已经重构过两轮了。为了那"万一"的未来,牺牲"确定"的当下,是极不划算的。
二、 “我想试试这个新技术”——警惕简历驱动开发 (RDD)
这是一个很隐晦但普遍存在的现象。很多开发者(包括当年的我)在选型时,潜意识里想的不是"哪个技术最适合业务",而是"哪个技术写在简历上更值钱"。
真实案例:
去年我们接手维护一个遗留项目,是一个简单的内容管理后台(CMS)。前任负责人为了"拥抱前沿",强行使用了GraphQL替代RESTful API,并且后端用的是一个非常小众的Rust异步框架。
后果不堪设想:
- 招聘困难:那个Rust框架文档极少,招进来的Java/Go背景的新人根本看不懂,导致一个简单的字段修改都要耗费两天。
- N+1问题泛滥:因为GraphQL使用不当,前端一个查询触发了后端几百次数据库查询,DBA差点提刀来见。
- 最后结局:我们花了两个月时间,把这个"高大上"的项目重写回了SpringBoot + MyBatis。虽然技术栈"土",但新人半天就能上手干活。
避坑方法:创新代币 (Innovation Tokens)
借用丹·麦金利的一个概念:每个项目只有3枚"创新代币"。
- 选用一个成熟的语言框架(如Spring Boot)?不消耗代币。
- 选用一个PostgreSQL数据库?不消耗代币。
- 要引入Rust?消耗1枚。
- 要引入一种新的RPC协议?消耗1枚。
一旦3枚代币用完,剩下的所有选型必须用最无聊、最成熟、最普通的技术。把"无聊"的技术留给基础设施,把"创新"的精力留给业务逻辑。
三、 “我要写一套通用的…"——抽象过早是万恶之源
“这个导出功能,虽然现在只有Excel,但为了通用性,我要设计一套支持PDF、CSV、甚至未来对接第三方API的各种Strategy模式…”
听起来很美,对吧?
真实案例:
我曾负责过一个营销活动的规则引擎。当时我想做一个"万能活动配置器”,通过极其复杂的抽象类、接口继承和反射,试图涵盖"满减"、“折扣”、“拼团"等所有场景。
代码写得非常"优雅”,全是设计模式。结果业务方提了一个需求:“拼团成功后,如果是周五,额外送一张优惠券”。
我的"万能架构"瞬间崩塌。因为我的抽象层把逻辑封死在了"拼团"和"发券"两个独立的模块里,为了打通这两个模块,我不得不破坏原本的封装,写了一堆恶心的if (instanceof Friday)代码。
如果当初直接写两个独立的Service,哪怕代码有重复,改这个需求只需要10分钟。而维护那套"通用架构",我花了一整天。
避坑方法:事不过三 (Rule of Three)
不要预先抽象。
- 第一次做:直接硬编码,怎么快怎么来。
- 第二次做类似功能:复制粘贴,修修补补。
- 第三次做类似功能: 也就是当你有三份重复代码时,这才是进行重构和抽象的最佳时机。
过早的抽象,往往是对业务理解不深的表现。 只有当你真正看到了三次重复,你才能通过对比,准确地识别出什么才是真正需要"通用"的部分。
结语与落地工具
避免过度设计,本质上是克制技术人员的"炫技欲"和"掌控欲"。我们做技术的,终究是为了交付价值,而不是交付代码。
我每周五下午做Code Review时,都会反复问团队一个问题:“如果把你写的这段复杂代码删掉一半,业务还能跑通吗?“如果答案是肯定的,那就删掉它。
为了方便大家在评审中落地,分享一个我自用的**「技术方案极简性自查表」**,建议每次评审前对照一遍:
### 🛠️ 技术方案极简性自查表 (Anti-Over-Engineering Checklist)
**1. 规模性自查**
- [ ] 方案是否是针对当前数据量级设计的?(是/否)
- [ ] 如果业务量增长10倍,此方案是否依然可用?(是,通过;否,需重新评估)
- [ ] 是否引入了为了解决"未来假设性问题"而存在的组件?(如有,请从V1版本移除)
**2. 复杂度自查**
- [ ] 新引入的技术栈/中间件,团队内是否有至少2人精通?
- [ ] 这一层抽象逻辑,新入职的初级开发能否在30分钟内看懂?
- [ ] 是否为了复用10%的代码,增加了50%的耦合度?
**3. 维护性自查**
- [ ] 出现Bug时,是否需要跨越3个以上的服务/组件才能定位?
- [ ] 如果该核心开发离职,这套方案是否可以被低成本接手?
最后,给项目经理和开发者的3个具体行动建议:
- 砍掉一半的中间件:在V1版本中,能用数据库解决的,绝不引入MQ;能用单体解决的,绝不拆微服务。
- 建立"技术负债墙”:如果确实因为赶进度写了烂代码,不要为了面子用设计模式包装它。直接在代码里标记
// TODO: Tech Debt,并在物理墙上贴出来,排期偿还。 - 多问"为什么”:在评审会上,当有人提出复杂方案时,连续问三个"为什么要这么做?不这么做会怎样?",通常问到第三个,过度设计的理由就站不住脚了。
技术是用来解决问题的,不是用来制造问题的。保持简单,才是最高级的技术能力。