边界:拆分的依据

拆分从来不是「因为大了所以拆」。拆分是沿着边界切——先识别出两个部分之间的分界线在哪,再动手。入口与业务的拆分是边界,系统拆子系统也是边界,函数拆函数、文件拆文件同样是边界。边界贯穿所有粒度,而且边界不止一种:职责边界、变化边界、生命周期边界、依赖边界、物理边界。系统拆分只是边界的一种应用,不是全部。


一、拆分不是按大小,是按边界

很多人理解拆分是「代码太长就拆」「文件太大就拆」。但核心原则反复强调:

职责决定拆分,重复决定封装,边界决定分层,依赖决定接口。

为什么不是按大小拆?因为「长」不是拆分理由,「边界在哪」才是:

1
2
3
4
5
6
7
8
一个 1000 行的文件,但只做一件事
→ 不该拆(没有边界)

一个 200 行的文件,同时做「解析参数」和「扫描内存」
→ 该拆(有明确的职责边界)

一个函数同时处理「玩家移动」和「碰撞检测」
→ 该拆(两个职责之间有边界)

拆分 = 找到边界,沿边界切。 大小只是表面现象,边界才是依据。


二、边界的类型

边界不是一种,至少有五类。每一类回答不同的问题:

1. 职责边界——「是不是在做好几件事」

1
2
一件事 → 一个单元
几件事 → 沿职责边界拆
  • 函数只做一个操作(函数级职责边界)
  • 类只封装一个对象(类级职责边界)
  • 组件只承担一个功能(组件级职责边界)
  • 入口管接收调用输出,业务管具体处理(入口/业务边界)

2. 变化边界——「是不是因为不同原因变化」

1
2
变化原因相同 → 放一起
变化原因不同 → 拆开
  • 界面要变、业务要变、底层平台要变 → 三者拆开(UI/Service/Infrastructure)
  • 「因为不同原因变化的东西不应该绑在一起」——这是分层最深的理由

3. 生命周期边界——「有没有独立的生命周期」

1
2
有生命周期(要初始化/运行/释放)→ 独立子系统/类
无生命周期(即调即用) → 函数/接口
  • 有状态、要 start/stop 的 → 框架化子系统(类封装)
  • 无状态、即调即用的 → 接口化(函数导出)
  • 生命周期边界是「组件 vs 子系统」的分界线

4. 依赖边界——「谁可以依赖谁」

1
2
单向:上层 → 下层
禁止:下层 → 上层
  • 依赖方向一旦确定,就成了一条不可跨越的边界
  • 反向依赖(循环依赖)= 边界被破坏

5. 物理边界——「编译/运行/部署上是不是独立单元」

1
2
3
文件    → 编译单元边界
库 → 编译/发布/复用边界
进程 → 运行/隔离/部署边界
  • .h/.cpp 是编译期物理边界
  • 静态库是发布期物理边界
  • 独立进程是运行期物理边界

三、边界贯穿所有粒度

边界不是系统层才有的概念。从最小的函数到最大的系统,每一层拆分都是沿边界切

1
2
3
4
5
6
7
8
9
粒度          拆分动作          依据的边界
────────────────────────────────────────────
函数 拆函数 职责边界(一件事 vs 几件事)
类 拆类 对象边界(一个对象 vs 多个对象)
文件 拆文件 职责/编译边界
组件 拆组件 功能边界(一个功能 vs 多个功能)
子系统 拆子系统 生命周期/功能边界
系统 拆系统 物理/职责边界
入口/业务 拆入口与业务 变化边界(界面变 vs 业务变)

所以:

系统拆子系统、入口与业务拆分,只是「沿边界拆分」这个统一动作在高层粒度的表现。

低粒度和高粒度的拆分是同一种动作——识别边界,沿边界切。这也是为什么「边界」应该独立成一条线:它解释了所有拆分为什么发生。


四、先识别边界,再动手拆

正确顺序:

1
2
3
4
5
6
7
观察混乱

识别边界(哪些东西是两回事、会因不同原因变化、有不同生命周期)

沿边界拆

用依赖方向验证边界是否正确(拆完还循环依赖 = 边界切错了)

拆错边界的典型症状:

1
2
3
4
5
❌ 按大小拆:代码长就硬拆 → 把一件事拦腰切断,两部分互相传内部状态
❌ 按技术类别拆:把完整功能拆散到 data/algorithm/utils 三个目录
→ 一个功能横跨多目录,改动牵一发动全身

✅ 按职责/变化/生命周期边界拆 → 每个单元是一个完整功能

「组件化 vs 传统模块化」的对比正是这个:

1
2
3
4
5
传统模块化:先按技术类别切(service/data/algorithm/utils)
→ 一个完整功能被拆散到多个目录

组件化:先按功能/职责边界拆,再在组件内部按数据/算法/接口组织
→ 功能+实现+接口+依赖形成一个完整单元

五、边界的验证:拆完看依赖

拆完怎么知道边界对不对?看依赖方向

1
2
正确的边界:拆完依赖是单向的(上→下),没有循环
错误的边界:拆完出现循环依赖 → 说明两个部分其实耦合在一起,边界切错了
1
2
入口层 → 业务层 → 基础设施层        ✅ 单向,边界正确
入口层 → 业务层 → 入口层 ❌ 循环,边界错误

所以边界和依赖是硬币的两面:边界定义「在哪切」,依赖验证「切对了没有」。


六、边界与分层的关系

「边界决定分层」——为什么分层?因为存在边界:

1
2
3
入口与业务有变化边界(界面变 vs 业务变)→ 分成两层
业务与基础设施有变化边界(业务逻辑 vs 平台细节)→ 再分一层
有生命周期与无生命周期有边界 → 子系统与接口分离

每一层都是一条被固化的边界:

1
2
层 = 边界的制度化
边界存在 → 需要分界 → 分成层 → 层之间规定依赖方向

所以分层不是目的,边界才是。层只是把边界变成结构的手段。


七、和其他线的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
边界 ←→ 依赖关系
边界定义「在哪切」,依赖验证「切对没有」
边界决定分层的依据,依赖决定接口的依据

边界 ←→ 从指令到系统主线
主线讲「拆分动作」的演化史(拆函数→拆文件→分层→拆系统)
本线讲「为什么拆」——因为存在边界

边界 ←→ 接口(11-模块与构建单元/01)与依赖关系主题
生命周期边界 → 有状态类封装/无状态函数导出
依赖边界 → 依赖接口而非实现

边界 ←→ 系统拆分
系统拆子系统是边界在高层粒度的应用
入口/业务拆分也是边界应用

收束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
拆分不是按大小,是按边界
职责决定拆分,重复决定封装,边界决定分层,依赖决定接口

边界有五种:
职责边界(几件事?) 变化边界(为什么变?)
生命周期边界(有状态吗?) 依赖边界(谁能依赖谁?)
物理边界(独立单元吗?)

边界贯穿所有粒度:
函数/类/文件/组件/子系统/系统 的拆分都是同一种动作
入口与业务拆分、系统拆子系统,只是边界在高层粒度的表现

正确顺序:观察混乱 → 识别边界 → 沿边界拆 → 用依赖方向验证

层 = 边界的制度化;边界才是目的,分层只是手段

所有拆分的依据都是边界。 理解了边界,就理解了软件为什么长成现在的形状。