02-边界-拆分的依据
边界:拆分的依据
拆分从来不是「因为大了所以拆」。拆分是沿着边界切——先识别出两个部分之间的分界线在哪,再动手。入口与业务的拆分是边界,系统拆子系统也是边界,函数拆函数、文件拆文件同样是边界。边界贯穿所有粒度,而且边界不止一种:职责边界、变化边界、生命周期边界、依赖边界、物理边界。系统拆分只是边界的一种应用,不是全部。
一、拆分不是按大小,是按边界
很多人理解拆分是「代码太长就拆」「文件太大就拆」。但核心原则反复强调:
职责决定拆分,重复决定封装,边界决定分层,依赖决定接口。
为什么不是按大小拆?因为「长」不是拆分理由,「边界在哪」才是:
1 | 一个 1000 行的文件,但只做一件事 |
拆分 = 找到边界,沿边界切。 大小只是表面现象,边界才是依据。
二、边界的类型
边界不是一种,至少有五类。每一类回答不同的问题:
1. 职责边界——「是不是在做好几件事」
1 | 一件事 → 一个单元 |
- 函数只做一个操作(函数级职责边界)
- 类只封装一个对象(类级职责边界)
- 组件只承担一个功能(组件级职责边界)
- 入口管接收调用输出,业务管具体处理(入口/业务边界)
2. 变化边界——「是不是因为不同原因变化」
1 | 变化原因相同 → 放一起 |
- 界面要变、业务要变、底层平台要变 → 三者拆开(UI/Service/Infrastructure)
- 「因为不同原因变化的东西不应该绑在一起」——这是分层最深的理由
3. 生命周期边界——「有没有独立的生命周期」
1 | 有生命周期(要初始化/运行/释放)→ 独立子系统/类 |
- 有状态、要 start/stop 的 → 框架化子系统(类封装)
- 无状态、即调即用的 → 接口化(函数导出)
- 生命周期边界是「组件 vs 子系统」的分界线
4. 依赖边界——「谁可以依赖谁」
1 | 单向:上层 → 下层 |
- 依赖方向一旦确定,就成了一条不可跨越的边界
- 反向依赖(循环依赖)= 边界被破坏
5. 物理边界——「编译/运行/部署上是不是独立单元」
1 | 文件 → 编译单元边界 |
.h/.cpp是编译期物理边界- 静态库是发布期物理边界
- 独立进程是运行期物理边界
三、边界贯穿所有粒度
边界不是系统层才有的概念。从最小的函数到最大的系统,每一层拆分都是沿边界切:
1 | 粒度 拆分动作 依据的边界 |
所以:
系统拆子系统、入口与业务拆分,只是「沿边界拆分」这个统一动作在高层粒度的表现。
低粒度和高粒度的拆分是同一种动作——识别边界,沿边界切。这也是为什么「边界」应该独立成一条线:它解释了所有拆分为什么发生。
四、先识别边界,再动手拆
正确顺序:
1 | 观察混乱 |
拆错边界的典型症状:
1 | ❌ 按大小拆:代码长就硬拆 → 把一件事拦腰切断,两部分互相传内部状态 |
「组件化 vs 传统模块化」的对比正是这个:
1 | 传统模块化:先按技术类别切(service/data/algorithm/utils) |
五、边界的验证:拆完看依赖
拆完怎么知道边界对不对?看依赖方向:
1 | 正确的边界:拆完依赖是单向的(上→下),没有循环 |
1 | 入口层 → 业务层 → 基础设施层 ✅ 单向,边界正确 |
所以边界和依赖是硬币的两面:边界定义「在哪切」,依赖验证「切对了没有」。
六、边界与分层的关系
「边界决定分层」——为什么分层?因为存在边界:
1 | 入口与业务有变化边界(界面变 vs 业务变)→ 分成两层 |
每一层都是一条被固化的边界:
1 | 层 = 边界的制度化 |
所以分层不是目的,边界才是。层只是把边界变成结构的手段。
七、和其他线的关系
1 | 边界 ←→ 依赖关系 |
收束
1 | 拆分不是按大小,是按边界 |
所有拆分的依据都是边界。 理解了边界,就理解了软件为什么长成现在的形状。
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
