返回文章列表

Swift Package Manager 系列 04|如何拆分模块,代码才适合放进 Package

真正难的不是“敢不敢拆”,而是怎么避免把耦合关系原样搬进多个新模块

很多团队一开始做模块化时,最容易犯的一个错误就是:

先决定要拆模块,再想模块为什么存在。

于是最后的结果通常不是模块化,而是“把原来的耦合复制到更多目录里”。
表面上模块变多了,实际上依赖关系没变,甚至更乱。

所以这篇文章我更想讲的是:什么样的代码才适合放进 Package,以及拆分时最常见的结构性误判是什么。

一、判断该不该拆,先别看文件数量,先看边界是否天然存在

一个模块值不值得拆出来,首先要看它有没有天然边界。

所谓天然边界,通常体现在:

  • 它承担的是相对单一的一组职责
  • 它的依赖方向比较清楚
  • 它不需要知道太多宿主工程上下文
  • 它未来很可能被多个场景复用

如果这些条件都不满足,只是因为“这个文件夹现在有点大”,那拆出来很可能只是换个地方继续耦合。

模块化的第一原则不是“切开”,而是“边界本来就存在,我只是把它显式化”。

二、最不适合第一批拆出去的,往往是和页面强耦合的大业务块

很多人刚开始做模块化时,最有冲动拆的反而是:

  • 首页
  • 账号中心
  • 支付流程
  • 内容详情

这些模块当然重要,但它们也往往和这些东西深度耦合:

  • 路由
  • 宿主 App 生命周期
  • 页面状态
  • 埋点
  • UI 资源

如果第一次拆分就从这些地方下手,边界还没想清楚,很容易出现:

  • 模块自己还要反向依赖宿主
  • 为了共用一点东西,多个包互相引用
  • 公共层被业务层污染

所以第一次更适合拆的,通常不是“最大块”,而是“边界最清晰的块”。

三、适合放进 Package 的代码,通常有三种典型形态

1. 基础能力模块

例如:

  • 日志
  • 网络封装
  • 本地缓存
  • 配置读取

这些能力共同特点是职责清楚、页面无关、复用空间大。

2. 领域模型或领域服务模块

例如:

  • 用户模型与用户资料查询
  • 内容领域对象与内容仓库
  • 订单领域逻辑

这类模块更偏业务,但只要边界清晰,也很适合 Package 化。

3. 稳定的 UI 基础组件模块

例如:

  • 设计系统基础组件
  • 通用样式系统
  • 若干无业务耦合的交互组件

前提是它们真的稳定,而且没有偷带页面专属语义。

四、一个非常常见的误区:按目录拆,而不是按依赖方向拆

很多团队模块化失败,不是因为拆太少,而是因为拆法错了。

最典型的问题就是:
原来文件夹怎么分,现在模块就怎么分。

比如:

  • Home/
  • User/
  • Common/
  • Utils/

表面看像是模块了,实际上你很快会遇到:

  • Home 依赖 User
  • User 又依赖 Common
  • Common 里其实混着一堆业务逻辑
  • Utils 最后成了什么都往里塞的垃圾桶

这说明你拆的是目录,不是边界。

真正更稳的拆法,应该从依赖方向出发去看:

  • 哪些模块只能向下依赖
  • 哪些能力是公共底层
  • 哪些是业务领域层
  • 哪些是宿主层专属

只有依赖方向先清楚,模块拆分才会长期稳定。

五、不要为了模块化把依赖关系藏起来

有些项目在拆模块时,会为了避免循环依赖,开始用各种折中手段:

  • 超大的公共模块
  • 到处抽协议
  • 中间塞一层“桥接层”

这些技巧不是不能用,但如果只是为了“让模块图好看”,很容易把真正的耦合藏起来,而不是解决它。

所以我更在意的是:

  • 当前依赖关系是不是业务上合理
  • 模块是不是因为真正职责不同而分开
  • 还是只是为了规避工具上的限制

真正健康的模块化,不是没有耦合,而是耦合被清楚地放在正确方向上

六、一个实用判断:这个模块如果拿走,宿主工程还理解它吗

这是我自己很常用的判断方式。

如果你把一个候选模块抽出去,问自己:

  • 宿主工程对它的理解是不是仍然清楚
  • 外部调用者是不是只需要知道接口,而不用知道内部大量细节
  • 它离开宿主后,是否仍然是一个完整概念

如果答案是“是”,那它通常更适合做成 Package。
如果答案是“不是”,说明它现在可能仍然只是宿主内部的一团实现细节。

七、第一次拆分时,宁可少拆,也不要拆碎

第一次做模块化,很容易陷入“既然都开始拆了,那就多拆一点”的冲动。
但中型项目里最常见的失败之一,就是模块太碎。

模块一旦太碎,成本会快速上升:

  • 依赖图更复杂
  • 编译关系更难理解
  • 版本和边界管理更费心
  • review 时要跨很多模块来回跳

所以第一次拆分时,我更倾向于:

  • 先拆出少数边界很清晰的模块
  • 观察几个迭代
  • 再决定是否继续细化

模块化最怕的不是慢,而是第一步就把系统拆成很多看似独立、实际上互相缠绕的小块。

八、结论:适合进 Package 的,不是“文件多的代码”,而是“边界清楚的能力”

如果只用一句话总结,我会说:

判断一段代码适不适合放进 Package,关键不在它有多少文件,而在它是否已经具备相对清晰的职责边界、依赖方向和独立语义。

所以真正重要的不是“拆不拆”,而是:

  • 为什么拆
  • 按什么边界拆
  • 拆完后依赖方向是否更清楚

只有这三件事先成立,模块化才是真的在降复杂度,而不是把复杂度重新排版。