第三章节:模块化设计 —— 拆得好才能过得舒服

说到模块化,很多人脑子里蹦出来的可能只是「把代码分开放」。嗯,这倒也没错,但怎么分、分到什么粒度、分完之后怎么保证它们不互相拖累——这才是真正考验架构功底的地方。我早年接手过一个项目,当时团队为了图省事,所有功能几乎写在一个模块里。结果呢?每次改一个日志级别就得重新编译一整个工程。那段时间我最大的愿望,就是能把它拆得利利索索。

模块拆分原则

模块拆分不是拍脑袋的事。我个人习惯,先看几个关键维度:

  • 功能内聚性:一个模块只属于一个功能域。比如管理用户登录的,就别把图片压缩逻辑也塞进去。
  • 复用潜力:这部分的逻辑以后会不会被其他地方用到?如果会,就抽成独立模块。
  • 变更频率:经常改动的部分和基本不动的部分,放在一起就是自找麻烦。
  • 团队边界:有时候模块划分也得考虑人的因素。两个团队各管一摊,模块边界最好和团队职责对齐。
我个人倾向于先画出系统的「功能用例图」,然后在每个用例上标记「复用可能性」和「变更预期」。你不信试试看,拆完后会发现代码清晰很多。

我记得有一次做音视频SDK,音频处理模块和网络适配模块被我硬生生拆成两个独立的动态库。后来音频算法更新时,只需要替换其中一个库,网络侧完全不用动。这就叫拆对了。

高内聚低耦合

这两个词几乎成了模块化设计的代名词。但到底什么才算「高内聚」?什么才算「低耦合」?

高内聚,说白了就是一个模块里的代码,它们之间的关联度足够高。比如一个「日志模块」,里面只负责日志的写入、格式化、日志级别的控制。不应该有网络请求、不应该有页面渲染——那些是别人的事。

低耦合也很好理解。两个模块之间,最好只通过几个明确定义的接口来通信。千万别让模块A能直接操作模块B的内部变量。

我建议你把模块想象成一个黑盒子。外面的人只知道「这个盒子能提供什么能力」,但盒子里怎么运转,他们不需要知道。这就叫接口隔离。

内聚与耦合的典型对比
维度 高内聚 / 低耦合 低内聚 / 高耦合
修改影响范围 只影响本模块 蝴蝶效应,改一行炸一片
测试难度 可以单独测一个模块 必须启动整个系统
复用性 拿来就能用 连带一堆依赖,根本拆不走
团队协作 各管各的,互不干扰 动不动就冲突合并

我在项目中遇到过一种极端情况:用户模块依赖了订单模块的一个内部工具函数,订单模块又依赖了支付模块的某个配置类。最后形成了一个危险的循环依赖。你想想看,这还怎么独立测试?那时候我只能硬着头皮重构,把那个工具函数抽出来放到一个公共组件里。为什么一开始没做好?说到底就是耦合度没控制住。

千万不要觉得「只是临时用一下别人的内部函数」没问题。这种临时,往往就是架构腐化的开始。

职责划分

职责划分,最核心的一句话就是:一个模块只做一件事,并且做好这一件事。

这其实就是单一职责原则在模块层面的应用。每次有人问我「这个功能该放哪个模块」,我的回答都很简单:「假设你明天要把它拆出去独立维护,你是不是还得额外做一堆适配工作?如果是,那就说明放错地方了。」

我举一个真实的例子。我负责过一个支付SDK,里面包含了订单创建、支付渠道管理、退款处理、对账逻辑。你看,这四个职责就完全可以拆成四个模块:

  • 订单模块:只管生成和管理订单状态。
  • 支付渠道模块:封装微信、支付宝、银联等渠道的接口。
  • 退款模块:负责退款流程和退款的幂等处理。
  • 对账模块:拉取平台对账单,与本地的订单流水比对。

这么拆完之后,每个模块的代码量都控制在了500行以内。后来我们要增加一个新的支付渠道,只需要在「支付渠道模块」里加一个类。订单模块、退款模块、对账模块完全不需要改。你说爽不爽?

职责划分还有一个容易踩的坑:功能重叠。比如,两个模块各自实现了一个类似的日期格式化函数。我建议你把这些「跨模块共用的基础能力」统一放到一个基础组件(base library)里。别觉得一个小函数无所谓,多了以后就是噩梦。

// 一个职责清晰的类示例
// 这是 iOS SDK 中一个仅有单一职责的支付渠道模块
public class WeChatPayChannel: PaymentChannel {
    public func process(chargeInfo: ChargeInfo, completion: @escaping (Result<String, Error>) -> Void) {
        // 只做一件事:发起微信支付
        let request = WeChatPayRequest(
            partnerId: chargeInfo.partnerId,
            prepayId: chargeInfo.prepayId,
            nonceStr: chargeInfo.nonceStr
        )
        PayService.shared.send(request, completion: completion)
    }
}

这段代码很干净。它是一个模块里的一个类,只处理「微信支付」这一件事。如果以后要加支付宝,就再写一个 AlipayChannel。绝不会把微信和支付宝的逻辑混在一起。

知识体系总览

下面这张图概括了本章的核心脉络,你可以先扫一眼,再回头细读:

模块化设计核心逻辑 模块拆分原则 高内聚低耦合 职责划分 最终目标 独立开发 · 独立测试 · 独立维护 · 独立替换 避坑经验 模块之间只通过接口通信,绝不暴露内部变量或私有方法

图里其实已经说得很明白了:拆分原则告诉你「从哪些角度切」,高内聚低耦合是「切完后模块之间怎么相处」,职责划分是「每个模块里面到底装什么」。三者缺一不可。

嗯,这一节讲到这里。你只要在项目里真正动手拆一次、重构一次,就会明白我说的这些原则到底有多救命。