14-9.【模块化与包管理】如何判断一个模块应该拆分成独立 target / package,还是保持在现有模块内部?

0 阅读3分钟

判断一个功能是应该留在原地,还是独立为 Target(包内模块)或 Package(独立包),本质上是在开发便利性架构严谨性之间做权衡。

你可以通过以下四个维度来进行决策:


1. 变动频率与稳定性 (Volatility vs. Stability)

这是判断是否拆分的最直接标准。

  • 留在原地:如果该功能与现有代码紧密耦合,且每次修改业务时都需要同步修改该功能,拆分会导致你在两个模块间频繁切换,增加认知负担。

  • 拆分为 Target/Package:如果该功能非常稳定(如:自定义的加密工具、基础 UI 组件),一旦写好很少变动。

    • 收益:稳定模块编译后会被缓存。当你修改业务代码时,编译器无需重新扫描这些稳定的 Target,从而大幅缩短增量编译时间

2. 依赖对称性 (Dependency Symmetry)

观察该功能的依赖链条。

  • 留在原地:如果该功能依赖了大量的业务逻辑、具体的 ViewModel 或数据库模型,它就不具备独立性。

  • 拆分为 Target:如果该功能是**“纯净”**的(例如只依赖 Foundation),或者它被多个互不相关的业务模块共同依赖。

    • 原则:如果模块 A 和模块 B 都需要代码 C,那么 C 必须独立出来。否则,A 依赖 B 或 B 依赖 A 都会造成不必要的代码“污染”。

3. 复用边界 (Reusability Boundary)

  • 独立为 Target (包内模块) :当代码仅在当前项目的多个 Feature 之间复用时。例如:项目中通用的 LoadingView

  • 独立为 Package (独立包) :当代码具备跨项目复用的价值时。

    • 例子:你开发了一个高性能的图片裁切工具,公司里的其他 App 也能用。
    • 收益:Package 拥有独立的 Git 版本管理,可以跨工程分发,且能通过二进制化(XCFramework)进一步保护源码或加速构建。

4. 物理隔离的需求 (Safety & Encapsulation)

  • 保持在内部:如果你希望利用 Swift 的 internal 访问权限,让该功能对包内其他类可见,但不希望增加复杂的 public 接口声明。

  • 拆分为独立模块:当你需要强制解耦时。

    • 架构强制力:一旦拆分为独立 Target,除非显式在 Package.swift 中声明依赖,否则无法访问其他模块。这能防止开发者因为“贪图方便”而写出跨模块的越权调用(例如在底层的网络库里直接调用上层的登录 UI)。

决策决策矩阵 (Decision Matrix)

观察维度留在原地拆为 Target (模块)拆为 Package (独立包)
复用范围单一功能点本项目内多处复用多个项目间复用
编译耗时极小,不影响较大,需通过隔离加速极大,或需二进制分发
维护归属本业务组跨组公用公司架构组/开源
依赖关系包含业务逻辑仅依赖基础库几乎零外部依赖
访问控制利用 internal 方便访问强制 public 接口隔离严格的版本号管理 (SemVer)

总结建议

  1. 先 Target,后 Package:不要一开始就创建几十个 Git 仓库。先在同一个 SPM Package 下拆分不同的 target
  2. 关注“功能孤岛” :如果你发现某个文件夹下的代码从来不 import 宿主项目的任何东西,那么它就是一个完美的拆分候选者。
  3. 编译性能驱动:如果你的项目编译超过 3 分钟,优先把那些不常变动的底层工具类拆分成独立 Target。