Swift Package Manager 系列 04|如何拆分模块,代码才适合放进 Package
真正难的不是“敢不敢拆”,而是怎么避免把耦合关系原样搬进多个新模块
很多团队一开始做模块化时,最容易犯的一个错误就是:
先决定要拆模块,再想模块为什么存在。
于是最后的结果通常不是模块化,而是“把原来的耦合复制到更多目录里”。
表面上模块变多了,实际上依赖关系没变,甚至更乱。
所以这篇文章我更想讲的是:什么样的代码才适合放进 Package,以及拆分时最常见的结构性误判是什么。
一、判断该不该拆,先别看文件数量,先看边界是否天然存在
一个模块值不值得拆出来,首先要看它有没有天然边界。
所谓天然边界,通常体现在:
- 它承担的是相对单一的一组职责
- 它的依赖方向比较清楚
- 它不需要知道太多宿主工程上下文
- 它未来很可能被多个场景复用
如果这些条件都不满足,只是因为“这个文件夹现在有点大”,那拆出来很可能只是换个地方继续耦合。
模块化的第一原则不是“切开”,而是“边界本来就存在,我只是把它显式化”。
二、最不适合第一批拆出去的,往往是和页面强耦合的大业务块
很多人刚开始做模块化时,最有冲动拆的反而是:
- 首页
- 账号中心
- 支付流程
- 内容详情
这些模块当然重要,但它们也往往和这些东西深度耦合:
- 路由
- 宿主 App 生命周期
- 页面状态
- 埋点
- UI 资源
如果第一次拆分就从这些地方下手,边界还没想清楚,很容易出现:
- 模块自己还要反向依赖宿主
- 为了共用一点东西,多个包互相引用
- 公共层被业务层污染
所以第一次更适合拆的,通常不是“最大块”,而是“边界最清晰的块”。
三、适合放进 Package 的代码,通常有三种典型形态
1. 基础能力模块
例如:
- 日志
- 网络封装
- 本地缓存
- 配置读取
这些能力共同特点是职责清楚、页面无关、复用空间大。
2. 领域模型或领域服务模块
例如:
- 用户模型与用户资料查询
- 内容领域对象与内容仓库
- 订单领域逻辑
这类模块更偏业务,但只要边界清晰,也很适合 Package 化。
3. 稳定的 UI 基础组件模块
例如:
- 设计系统基础组件
- 通用样式系统
- 若干无业务耦合的交互组件
前提是它们真的稳定,而且没有偷带页面专属语义。
四、一个非常常见的误区:按目录拆,而不是按依赖方向拆
很多团队模块化失败,不是因为拆太少,而是因为拆法错了。
最典型的问题就是:
原来文件夹怎么分,现在模块就怎么分。
比如:
Home/User/Common/Utils/
表面看像是模块了,实际上你很快会遇到:
Home依赖UserUser又依赖CommonCommon里其实混着一堆业务逻辑Utils最后成了什么都往里塞的垃圾桶
这说明你拆的是目录,不是边界。
真正更稳的拆法,应该从依赖方向出发去看:
- 哪些模块只能向下依赖
- 哪些能力是公共底层
- 哪些是业务领域层
- 哪些是宿主层专属
只有依赖方向先清楚,模块拆分才会长期稳定。
五、不要为了模块化把依赖关系藏起来
有些项目在拆模块时,会为了避免循环依赖,开始用各种折中手段:
- 超大的公共模块
- 到处抽协议
- 中间塞一层“桥接层”
这些技巧不是不能用,但如果只是为了“让模块图好看”,很容易把真正的耦合藏起来,而不是解决它。
所以我更在意的是:
- 当前依赖关系是不是业务上合理
- 模块是不是因为真正职责不同而分开
- 还是只是为了规避工具上的限制
真正健康的模块化,不是没有耦合,而是耦合被清楚地放在正确方向上。
六、一个实用判断:这个模块如果拿走,宿主工程还理解它吗
这是我自己很常用的判断方式。
如果你把一个候选模块抽出去,问自己:
- 宿主工程对它的理解是不是仍然清楚
- 外部调用者是不是只需要知道接口,而不用知道内部大量细节
- 它离开宿主后,是否仍然是一个完整概念
如果答案是“是”,那它通常更适合做成 Package。
如果答案是“不是”,说明它现在可能仍然只是宿主内部的一团实现细节。
七、第一次拆分时,宁可少拆,也不要拆碎
第一次做模块化,很容易陷入“既然都开始拆了,那就多拆一点”的冲动。
但中型项目里最常见的失败之一,就是模块太碎。
模块一旦太碎,成本会快速上升:
- 依赖图更复杂
- 编译关系更难理解
- 版本和边界管理更费心
- review 时要跨很多模块来回跳
所以第一次拆分时,我更倾向于:
- 先拆出少数边界很清晰的模块
- 观察几个迭代
- 再决定是否继续细化
模块化最怕的不是慢,而是第一步就把系统拆成很多看似独立、实际上互相缠绕的小块。
八、结论:适合进 Package 的,不是“文件多的代码”,而是“边界清楚的能力”
如果只用一句话总结,我会说:
判断一段代码适不适合放进 Package,关键不在它有多少文件,而在它是否已经具备相对清晰的职责边界、依赖方向和独立语义。
所以真正重要的不是“拆不拆”,而是:
- 为什么拆
- 按什么边界拆
- 拆完后依赖方向是否更清楚
只有这三件事先成立,模块化才是真的在降复杂度,而不是把复杂度重新排版。