一、模块化编程概述

什么是模块化编程

模块化编程,说白了就是把一个大程序拆成一个个独立的小单元。每个单元负责一块明确的功能,彼此之间通过定义好的接口通信。

我刚开始做嵌入式开发时,写过一段"面条代码"——一个 main.c 文件里塞了两千多行,全局变量满天飞。后来要加个新功能,改了一处,崩了三处。那感觉,就像在雷区里跳舞。

模块化编程的核心思想就四个字:高内聚,低耦合

  • 高内聚:一个模块内部的功能要紧密相关。比如 LED 驱动模块,就只管 LED 的亮灭、闪烁、PWM 调光,别掺和按键扫描的事。
  • 低耦合:模块之间的依赖要尽量少。A 模块改了内部实现,B 模块不需要跟着改。

一句话总结:模块化编程 = 把大象装进冰箱,但冰箱、大象、装的动作,各是一个模块。

模块化编程的优势

为什么要搞模块化?我列几个实实在在的好处:

  1. 可维护性大幅提升——哪个模块出问题,就查哪个模块。不用从头读到尾。
  2. 代码复用——写好的 UART 驱动模块,换个项目直接拿过来用。我手头有个 I2C 驱动模块,从 2016 年用到现在,改了三次,越改越稳。
  3. 团队协作更顺畅——你写传感器模块,我写显示模块,他写通信模块。接口定好,各干各的,互不干扰。
  4. 测试更容易——可以单独测试每个模块。我习惯先写一个测试桩,把模块的输入输出都测一遍,再集成到主程序里。
  5. 可移植性强——硬件换了,只需要改底层驱动模块。上层业务逻辑基本不动。

我的经验:模块化不是越细越好。我见过有人把加法运算都单独搞一个模块,那就过度设计了。模块的粒度,以"一个人一周能写完"为参考比较合适。

C语言中的模块化思想

C 语言本身没有"模块"这个关键字。但我们可以用文件组织来实现模块化。标准做法是:

  • .h 文件:放接口声明、宏定义、类型定义。相当于模块的"说明书"。
  • .c 文件:放具体实现。相当于模块的"内部构造"。

举个例子,一个 LED 驱动模块:

// led_driver.h
#ifndef LED_DRIVER_H
#define LED_DRIVER_H

#include <stdint.h>

// 初始化 LED 引脚
void led_init(void);

// 设置 LED 状态:1 亮,0 灭
void led_set(uint8_t state);

// 切换 LED 状态
void led_toggle(void);

#endif
// led_driver.c
#include "led_driver.h"
#include "gpio.h"  // 底层 GPIO 驱动

static uint8_t led_state = 0;

void led_init(void) {
    gpio_pin_mode_set(LED_PIN, GPIO_MODE_OUTPUT);
    gpio_pin_write(LED_PIN, 0);
    led_state = 0;
}

void led_set(uint8_t state) {
    gpio_pin_write(LED_PIN, state ? 1 : 0);
    led_state = state;
}

void led_toggle(void) {
    led_state = !led_state;
    gpio_pin_write(LED_PIN, led_state);
}

注意:.h 文件里不要放变量定义!只放 extern 声明。否则多个 .c 文件包含同一个 .h 会导致重复定义错误。我曾经因为这个 bug 查了整整一个下午。

模块划分原则

模块怎么划分?我总结了几条实战原则:

原则 说明 反例
单一职责 一个模块只做一件事 一个模块既管温度采集,又管数据显示
接口最小化 暴露给外部的函数越少越好 把内部辅助函数也放到 .h 里
依赖单向 模块之间的依赖不能形成环 A 调用 B,B 又调用 A
变化隔离 容易变化的部分单独成模块 硬件寄存器操作混在业务逻辑里

嗯,这里要特别说一下"依赖单向"。我在一个项目中见过这样的代码:

// 错误示例:循环依赖
// sensor.c 调用 display.c 显示数据
// display.c 又调用 sensor.c 获取数据

// 正确做法:引入一个数据管理模块
// sensor.c 把数据写到 data_manager.c
// display.c 从 data_manager.c 读取数据

为什么要这样?因为循环依赖会让代码变得像一团乱麻。你改一处,不知道会影响多少地方。我曾经在一个循环依赖的项目里改 bug,改了三轮才改干净——第一轮修好了 A,第二轮发现 B 又坏了,第三轮才发现是循环依赖导致的时序问题。

核心原则:模块划分不是一次就能做对的。我通常先按功能粗分,写代码过程中再调整。重构不可怕,可怕的是不敢重构。

模块化编程的知识体系

下面这张图展示了模块化编程的核心逻辑:

模块化编程知识体系 模块化编程 高内聚 低耦合 接口清晰 .h / .c 文件分离 static 隐藏内部细节 extern 暴露接口 单一职责 接口最小化 依赖单向 + 变化隔离

这张图把模块化编程的核心逻辑串起来了。从核心概念出发,到三大支柱,再到具体的实现手段,最后落到划分原则上。你写代码的时候,可以拿这张图当 checklist 对照一下。

一个小建议:刚开始做模块化时,别追求一步到位。先把一个 .c 文件拆成两三个,感受一下变化。慢慢你就会发现,模块化不是束缚,而是解放。


公众号:蓝海资料掘金营,微信deep3321