为了"未来扩展"我白烧半年预算:架构YAGNI实战复盘

2018年,我负责重构一家B轮电商公司的核心交易系统。当时团队斗志昂扬,我也急于证明技术实力,在设计文档里写满了"高并发"、“解耦”、“未来支撑千万级用户”。

为了所谓的"极致扩展性",我们在没有一个真实用户下单前,就引入了全套微服务、分布式事务(Seata)、以及一套极其复杂的规则引擎。

结果呢?上线半年,日均订单量不到2000单。那套为"千万级用户"准备的架构,不仅每个月白白烧掉3万多的云服务器成本,更致命的是,每开发一个简单的"满减活动",都需要跨越4个服务、修改3个代码库。团队疲于奔命,业务方怨声载道。

配图

这就是典型的**YAGNI(You Aren’t Gonna Need It - 你不需要它)**违例。

这几年做架构咨询,我发现不仅是我,80%的中小团队架构师都容易陷入"简历驱动开发"或"防御性设计"的误区。今天,我想结合这几次惨痛的教训,聊聊如何在架构设计中真正落地YAGNI原则。

一、 别被"微服务"绑架,模块化单体才是真香

很多技术负责人在项目初期,容易产生一种幻觉:不用微服务,架构就不够先进。

在那个电商项目中,我们团队只有6个后端开发,却拆出了用户、商品、订单、支付、营销等12个微服务。

真实痛点:

  • 排查困难: 一个简单的报错,我要在ELK里搜集4个服务的日志才能拼凑出真相。
  • 基建拖累: 为了这套架构,我们不得不维护一套复杂的CI/CD流水线,每次全量部署需要40分钟。
  • 数据一致性噩梦: 分布式事务带来的复杂度,远超业务本身的逻辑复杂度。

修正方案: 一年后,我们不得不进行"架构降级"。我并没有一步退回到大泥球(Big Ball of Mud),而是采用了模块化单体(Modular Monolith)

我们把12个服务合并回一个SpringBoot工程,但严格通过Maven Module进行物理隔离:

// 错误示范:在单体里随意穿透
userService.getUserById(id); // 订单模块直接调用用户Service实现类

// 正确示范:模块化单体,通过明确的Public API交互
// Order Module -> User Module API -> User Module Implementation
public interface UserApi {
    UserDTO getUserInfo(Long userId);
}

结果: 合并后,部署时间缩短到5分钟,服务器成本下降了60%。更重要的是,因为都在一个进程内,方法调用代替了RPC网络调用,系统响应速度反而提升了。

给架构师的建议: 只有当你的团队规模超过"两个披萨"(约10-12人),或者某个模块需要独立的扩缩容策略(如秒杀模块)时,再考虑拆分微服务。在此之前,定义好边界比物理拆分更重要。

二、 警惕"通用性"陷阱,拒绝过度抽象

我曾在代码评审中见过一位资深开发写的"通用报表导出引擎"。

起因是业务方提了两个Excel导出需求。这位兄弟觉得:“以后肯定还有几百个导出需求,不如写个通用的配置化引擎。”

他花了整整两周,设计了一套基于XML配置SQL、反射处理字段映射、甚至支持动态策略模式的框架。

现实打脸: 接下来的两年里,业务方统共只提了5个导出需求,而且每个需求都有极其怪异的定制逻辑(比如:某些行要标红,某些列要合并),那个"通用引擎"根本配置不出来,最后还得硬编码。

我的反思: 这便是为了"可能存在"的未来,牺牲了"当下确定"的效率。

我现在遵循Rule of Three(事不过三原则)。在代码中,我允许适度的重复。

  • 第一次: 直接硬编码实现功能,越快越好。
  • 第二次: 复制粘贴代码,做少量修改。是的,你没看错,Ctrl+C/V在早期是最高效的解耦。
  • 第三次: 当我也在写第三块类似逻辑时,我才会停下来,寻找共性,进行重构和抽象。

“过早的抽象是万恶之源。一段重复的代码容易修改,但一个错误的抽象很难被移除。”

落地技巧: 下次当你听到团队里有人说"为了以后方便扩展,我加了一层…“时,请让他停下来。除非他能拿出具体的、在这个迭代就会发生的扩展场景,否则请坚持简单直接的实现。

三、 别让"技术选型"成为运维灾难

2020年,我接手过一个物联网(IoT)初创项目。前任架构师引入了Kafka、HBase、ClickHouse、Kubernetes全家桶。

但他忽略了一个关键事实:公司并没有专门的运维团队,甚至连一个专职DBA都没有。

真实场景: 某天深夜,线上系统突然崩了。原因是Zookeeper集群的一个节点磁盘满了,导致Kafka不可用,进而阻塞了整个业务流程。

因为团队里没人精通ZK和Kafka的底层原理,大家围着屏幕查了4个小时Google,最后只能暴力重启服务器。那晚我们损失了约20%的客户数据。

对于中小团队,“可运维性"远比"性能上限"重要

修正方案: 我们后来把Kafka换成了Redis List(对于当时的吞吐量完全够用),把HBase换成了MySQL(分表),虽然在理论性能上降级了,但系统的稳定性却大幅提升了。因为MySQL和Redis是每个开发都会用的组件,出了问题谁都能修。

架构决策卡: 每当我需要引入新技术栈时,我会强迫自己填写一张"引入理由卡”:

  1. 必须要解决的问题是什么?
  2. 现有的技术栈真的解决不了吗?
  3. 如果这个组件半夜挂了,团队里有几个人能修?

配图

如果第3题的答案少于2人,无论这个技术多牛,我都不会通过。

写在最后

架构设计不是搭积木,而是一场关于资源的博弈。

YAGNI原则的核心,不是让你写烂代码,而是让你保持专注。专注于解决当下的业务痛点,专注于让系统尽早交付价值,而不是为了感动自己去设计一座空中楼阁。

作为架构师,我们要有承认"我无法预测未来"的勇气。

互动时刻: 在你目前的系统中,有没有那种"设计得很精妙,但从来没被用过"的功能或代码? A. 有,而且很多,看着头疼。 B. 没有,我们都是一把梭,全是硬编码(这可能是另一个极端)。

欢迎在评论区告诉我你的现状。

行动建议:

  1. 本周五下午进行一次"代码扫除”: 找出项目中那个最复杂的"通用工具类"或"中间件",检查它的实际引用率。如果低于2处,标记为"待移除"。
  2. 建立"技术黑名单": 明确规定在当前阶段,哪些"高大上"的技术栈(如K8s、Service Mesh)严禁引入,直到业务量达到特定阈值。
  3. 拥抱Copy-Paste: 在下一次Code Review中,不要急着批评重复代码,先问问:“如果把它抽象出来,维护成本会变低还是变高?”