架构设计原则:SOLID、KISS、YAGNI、DRY 在架构中的应用
聊架构设计,绕不开这几个原则。它们不是教科书里的摆设,而是我每天写代码、画架构图时脑子里一直转的东西。说白了,这些原则就是前人踩坑踩出来的经验总结。
我个人习惯,拿到一个需求,先拿这几个原则过一遍。能砍掉的复杂度,绝不留着。能复用的逻辑,绝不重写。嗯,咱们一个一个来看。
SOLID 原则:面向对象架构的基石
SOLID 是五个原则的首字母缩写。很多新手觉得这是 Java 那套东西,其实不然。你写 Go、Python、甚至前端组件,一样能用。
| 缩写 | 原则名称 | 核心含义 |
|---|---|---|
| S | 单一职责原则 | 一个类/模块只做一件事 |
| O | 开闭原则 | 对扩展开放,对修改关闭 |
| L | 里氏替换原则 | 子类必须能替换父类 |
| I | 接口隔离原则 | 接口要小而专,不要大而全 |
| D | 依赖倒置原则 | 依赖抽象,不依赖具体实现 |
单一职责原则(SRP)
我在项目中遇到过最典型的反例:一个订单类,里面既有订单校验,又有数据库操作,还发了邮件通知。改一个地方,另外两个跟着崩。拆开之后,每个模块的改动风险就隔离了。
开闭原则(OCP)
你想想看,业务需求天天变。如果每次变需求都要改核心代码,那系统迟早变成一坨。我建议用策略模式或者插件化架构,新功能加个新类就行,老代码不动。
里氏替换原则(LSP)
这个原则最容易被忽略。我记得有一次,一个同事继承了一个基类,重写了某个方法,结果调用方传参进去直接抛异常。这就是破坏了 LSP。子类可以扩展,但不能削弱父类的行为。
接口隔离原则(ISP)
我曾经见过一个接口,里面十几个方法,实现类里一大半是空实现。这就是典型的胖接口。拆成多个小接口,调用方只依赖自己需要的,清爽多了。
依赖倒置原则(DIP)
高层模块不应该依赖低层模块。两者都应该依赖抽象。说白了,你的业务逻辑不要直接 new 一个 MySQL 连接,而是依赖一个 Repository 接口。换数据库?换个实现就行。
核心观点:SOLID 不是银弹,但它能帮你把系统拆成高内聚、低耦合的模块。我每次做架构评审,第一件事就是看模块间的依赖关系是不是反了。
KISS 原则:保持简单
KISS 就是 Keep It Simple, Stupid。别笑,这个原则在架构里比什么都重要。
我见过太多过度设计的系统。一上来就上微服务、事件总线、CQRS,结果业务逻辑就几十行。你想想看,维护成本翻了多少倍?
我个人习惯:能用单体解决的问题,绝不拆微服务。能用数据库 JOIN 解决的,绝不引入搜索引擎。能用定时任务解决的,绝不搞消息队列。
我的经验:先做最简单的方案,跑通再说。等真的遇到性能瓶颈或者扩展性问题,再考虑优化。80% 的系统,简单方案就够了。
YAGNI 原则:你不会需要它
YAGNI 全称 You Ain't Gonna Need It。这是极限编程里的原则,但放在架构设计里同样适用。
我曾经犯过一个错:做一个后台管理系统,我提前把多租户、国际化、数据分片全做进去了。结果项目上线两年,一个都没用上。代码里全是 if-else 判断租户,维护起来想哭。
为什么会有这种冲动?说白了,就是想展示技术能力,或者担心未来需求。但架构设计不是猜谜。未来的需求,等来了再改。现在没来的,就是不需要。
避坑指南:不要为了「可能以后会用」而引入复杂度。每多一个抽象,就多一层理解成本。我曾经因为提前做了缓存抽象层,结果业务逻辑改了三次,缓存层也跟着改了三次,最后发现根本不需要缓存。
DRY 原则:不要重复自己
DRY 就是 Don't Repeat Yourself。这个原则看起来简单,但实际落地很难。
重复分两种:
- 代码重复:同样的逻辑,复制粘贴了三次。这个好解决,抽成函数或者类就行。
- 知识重复:业务规则散落在多个地方。比如订单金额计算逻辑,在订单服务、报表服务、通知服务里各写了一遍。改一个地方,其他两个就错了。
我建议的做法:把核心业务逻辑收敛到一个领域层,其他服务通过 API 调用。这样改一处,全局生效。
但也要注意,不要为了 DRY 而过度抽象。有时候,两个地方看起来一样,但未来变化方向不同。强行合并,反而增加耦合。
我的判断标准:如果两个重复的地方,未来大概率会一起改,那就合并。如果它们可能各自演化,那就先放着,等第三次重复出现再重构。
原则之间的关系与权衡
这几个原则不是孤立的。它们之间有时候会冲突。比如:
- DRY 和 YAGNI 可能冲突:为了不重复,提前抽象了一个通用组件,但未来可能用不上。
- SOLID 和 KISS 可能冲突:为了满足开闭原则,引入了策略模式,但系统复杂度增加了。
怎么选?我个人的经验是:KISS 优先。简单是最高优先级。如果某个原则导致系统变复杂,那就先放一放。等真的需要了,再引入。
实际落地建议
说了这么多,怎么落地?我总结了几条实操经验:
- 代码评审时,拿原则清单过一遍。我每次 CR 都会问:这个模块职责单一吗?有没有重复逻辑?是不是过度设计了?
- 架构设计文档里,写明每个决策的原则依据。比如:这里用了策略模式,是为了满足开闭原则。这里没有做抽象,是因为 YAGNI。
- 定期做技术债务清理。我建议每个迭代留出 10% 的时间,专门处理违反原则的地方。不然债务越滚越大。
- 新人培训时,先讲原则再讲框架。框架会过时,原则不会。理解了原则,学什么框架都快。
一个小技巧:把这几个原则打印出来,贴在工位上。写代码的时候扫一眼,能避免很多低级错误。我自己的工位上就贴着一张,已经贴了五年了。
好了,这一章就聊到这里。原则是死的,人是活的。关键不是背下来,而是在实际项目中不断练习、反思、调整。慢慢你就会发现,这些原则已经变成你的本能反应了。