高内聚、低耦合:代码世界的“乐高哲学”

高内聚、低耦合:代码世界的“乐高哲学”

摘要:从朱卫军老师的 CLI 编程课出发,用“造遥控车”和"Type-C 接口”的比喻,拆解软件工程黄金法则。拒绝“万能杂物间”,拥抱“插拔式架构”,让代码从“盘丝洞”进化为“乐高积木”。


引言:为什么你的代码越写越像“盘丝洞”?

很多新手在学完基础语法后,会陷入一个怪圈:代码能跑,但越写越乱。
加个功能要改三个文件,修个 Bug 引出两个新 Bug。最后看着满屏的 import 和纠缠不清的逻辑,感叹一句:“这代码只有上帝和我能看懂,现在只有上帝知道了。”

朱卫军老师在讲 CLI(命令行界面)编程时,反复强调模块化思维。这并非什么高深莫测的架构玄学,其底层逻辑,归根结底就是软件工程的六个字:
高内聚,低耦合。

今天,我们不谈枯燥的 UML 图,我们用造一辆遥控车的逻辑,把这六个字掰开揉碎。


一、 高内聚:拒绝“万能杂物间”

1. 什么是内聚?

内聚(Cohesion),衡量的是一个模块内部元素之间的关联紧密程度

反面教材:低内聚的“万能杂物间”
你一定见过这样的文件,通常被命名为 utils.pycommon.py 或者 helpers.py。它就像一个塞满杂物的抽屉:

# utils.py —— 一个糟糕的杂物间  
def send_email(): ...  
def calculate_tax(): ...  
def draw_chart(): ...  
def connect_database(): ...  
def format_date(): ...  

后果

  • 认知过载:要修“发邮件”的 Bug,你得在一堆税率计算和画图代码里翻找。
  • 冲突频发:张三在改数据库连接,李四在改日期格式,两人同时提交 utils.py,Merge Conflict 在所难免。

2. 正面示范:高内聚的“专用工具箱”

高内聚要求:一个模块只做一件事,并把这件事做好。

# email_service.py —— 只干和邮件相关的事  
class EmailSender:  
    def __init__(self, smtp_server):  
        self.server = smtp_server  
      
    def send_welcome(self, user_email):  
        # 构造邮件、连接服务器、发送、记录日志  
        ...  
      
    def send_report(self, report_data):  
        ...  

3. 进阶理解:内聚的层级

内聚是有等级的,从低到高:

  • 巧合内聚(最低):毫无关系的代码凑在一起(如 utils.py)。
  • 逻辑内聚:逻辑上相似,但功能不同(如一个函数处理所有输入:handle_input(type))。
  • 功能内聚(最高):所有元素共同完成一个单一、明确的功能(如 EmailSender)。

通俗比喻
高内聚就像瑞士军刀里的剪刀。它只为“剪”而生,弹簧、刀片、铆钉全包在一起。你用的时候掏出来就能剪,不用去别的地方找零件。


二、 低耦合:换零件别拆整辆车

1. 什么是耦合?

耦合(Coupling),衡量的是模块与模块之间的依赖程度

反面教材:高耦合的“焊死电路”

class Car:  
    def __init__(self):  
        # 糟糕:Car 内部直接实例化具体的 V8 引擎  
        self.engine = V8Engine()   
      
    def start(self):  
        self.engine.ignite() # 强依赖 V8 引擎特有的点火方法  

后果

  • 牵一发而动全身:老板明天说把 V8 引擎换成电动机。你不仅要写 ElectricMotor 类,还得回头修改 Car 类的内部代码。
  • 无法测试:你想测试 Car 的启动逻辑,却必须启动一个真实的 V8Engine,测试成本高得吓人。

2. 正面示范:低耦合的“插拔式接口”

低耦合的核心是:依赖抽象,而非具体实现。

class Car:  
    def __init__(self, engine):  # 依赖注入:我只认接口,不认牌子  
        self.engine = engine  
      
    def start(self):  
        self.engine.start()      # 我只管叫你“启动”,具体怎么启我不管  
  
# 定义接口规范(Python 中可用 Protocol 或 ABC)  
class Engine:  
    def start(self):  
        raise NotImplementedError  
  
# 具体实现  
class V8Engine(Engine):  
    def start(self): print("轰隆隆...")  
  
class ElectricMotor(Engine):  
    def start(self): print("嗡嗡嗡...")  
  
# 使用  
car = Car(ElectricMotor())  # 换上电动机,Car 类一行代码都不用改!  

通俗比喻
低耦合就是手机充电口是 Type-C。不管另一头插的是充电宝、电脑还是插座,只要符合 Type-C 标准,手机就能充电。如果手机和充电器是焊死的,那就是高耦合。


三、 为什么必须“双剑合璧”?

你可能会问:“我把所有代码都写在一个 main.py 里,那它内部极其‘高内聚’啊(都在一个文件里),这算好吗?”

这是最大的误区。高内聚和低耦合是硬币的两面,缺一不可。

场景 只有高内聚 只有低耦合 高内聚 + 低耦合
形态 铁板一块 意大利面条 乐高积木
表现 一个模块包揽所有事,但模块间死死咬合 模块分得很细,但互相乱调用,跳转 50 次才能看懂逻辑 模块内部紧密,模块间通过清晰接口交互
修改 改一行代码要重启整个程序 改一个模块,发现十个模块报错 换一块积木,整车升级
测试 无法单独测试,必须运行全量环境 很难 Mock 依赖,测试环境搭建困难 单元测试极易编写

平衡点

模块内部像一块磁铁,紧紧吸住相关的东西;
模块之间像乐高积木,轻轻一扣就能换,拔下来也不留痕迹。


四、 CLI 编程中的实战落地

回到朱卫军老师的 CLI 编程场景。假设我们要写一个**“批量图片下载器”**。

❌ 错误做法(低内聚、高耦合)

downloader.py 文件里塞了 500 行代码:

  • argparse 解析命令行参数
  • requests 发起网络请求
  • os 创建文件夹
  • sqlite3 记录下载日志
  • 甚至直接打印了彩色的进度条

结果:如果你想把这个下载逻辑用到 GUI 程序里,你得把 argparseprint 的代码全删了重写。

✅ 正确做法(高内聚、低耦合)

我们将代码拆分为三个高内聚模块,通过低耦合接口连接:

  1. cli.py (接口层)
    • 只负责解析参数,调用核心逻辑。
    • 不关心图片怎么下载,不关心日志存哪。
  2. core.py (业务层)
    • 只负责下载逻辑。
    • 接收 URL 和保存路径,返回下载结果。
    • 不关心参数是怎么来的(是 CLI 给的还是 GUI 给的)。
  3. storage.py (数据层)
    • 只负责读写数据库。
    • 提供 save_log() 接口。

架构图

[CLI] --(参数)--> [Core] --(日志数据)--> [Storage]  
  ^                  |  
  |                  v  
(User)           [Network]  

当你需要开发 GUI 版本时,复用 core.pystorage.py,只写一个新的 gui.py 即可。这就是架构的威力。


五、 灵魂两问:代码写完后的自检清单

下次写代码犹豫结构对不对时,停下来,问自己两个问题:

1. 问内聚:“它跟这个文件里的其他兄弟是一家人吗?”

  • 如果 calculate_taxdraw_chart 放在一个文件里,它们是一家人吗?显然不是。
  • 行动:分家。按业务领域功能职责拆分文件。

2. 问耦合:“我要是删了这个类,隔壁老王家的代码会报错吗?”

  • 如果删了 Database 类,User 类直接报错,说明 User 强依赖了 Database 的具体实现。
  • 行动:引入接口。让 User 依赖 StorageInterface,而不是具体的 Database

结语:代码是写给人看的

Martin Fowler 曾说:

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand."

高内聚,是为了让阅读代码的人,打开一个文件就能看懂一个完整的故事。
低耦合,是为了让修改代码的人,换掉一个零件时,不用把整辆车拆散。

编程不仅是与机器对话,更是与未来的自己、与队友的协作。
当你开始用“乐高思维”审视代码时,你就已经从“码农”进化为“工程师”了。


雨轩于听雨轩 🌧️🏠