1、编码规范总纲:为什么需要编码规范?规范的核心原则
说实话,我刚开始做嵌入式那几年,也觉得编码规范是「形式主义」。
那时候我一个人写一个模块,变量名爱怎么起怎么起,缩进全看心情。代码跑得通就行,管它好不好看?
直到有一次,我接手了一个离职同事的项目。打开代码那一刻,我整个人都傻了——一个函数写了 2000 行,变量名全是 a1、a2、tmp,注释全是「这里改了」「这里没改」……
嗯,从那以后,我再也不敢不写规范了。
1.1 为什么需要编码规范?
你想想看,嵌入式开发有个特点:代码和硬件深度绑定。你今天写的驱动,可能三年后还要维护;你写的这个模块,可能要被移植到另一颗芯片上。
如果没有规范,会发生什么?
- 自己看不懂自己的代码——我亲身体会过,三个月前写的驱动,回头一看,愣是没搞懂当时为什么那么写。
- 别人不敢改你的代码——团队里最怕什么?怕「这个函数我不敢动,动了怕炸」。不规范的结构,让维护成本翻倍。
- 移植时处处是坑——比如你把 GPIO 的地址硬编码在某个 .c 文件里,换一颗芯片就得满世界找。我见过一个项目,就因为这种硬编码,移植花了整整两周。
说白了,编码规范不是束缚你的枷锁,而是保护你的铠甲。
核心观点:规范不是为了好看,是为了降低「认知负载」。你不需要在阅读代码时猜测「这个变量是干什么的」,一眼就能看懂,这才是规范的价值。
1.2 规范的核心原则
我个人习惯把规范的核心原则归纳为三个词:可读性、可维护性、可移植性。这三个词,几乎能覆盖你写驱动时遇到的所有问题。
1.2.1 可读性——代码是写给人看的
我记得有一次评审代码,一个同事写了个表达式:
if ((a & 0x0F) >> 2) { ... }
我问他在干什么,他想了半天说:「呃……判断 bit2 和 bit3 是不是至少有一个为 1。」
你看,他自己都要想半天,别人怎么可能一眼看懂?
可读性的核心要求:
- 命名要自解释:
gpio_set_pin_high()比set_pin()好,set_pin()比func1()好。 - 一个函数只做一件事:我见过一个初始化函数,里面混了 GPIO 配置、定时器启动、还有一段 LED 闪烁……这种代码,拆成三个函数不好吗?
- 注释要写「为什么」,而不是「是什么」:
// 坏注释:将 GPIOA 的 bit5 置 1
GPIOA->BSRR = (1 << 5);
// 好注释:拉高 CS 引脚,通知从设备开始接收数据
GPIOA->BSRR = (1 << 5);
我的小技巧:写完一段代码,过两天再读一遍。如果自己都觉得别扭,那别人肯定更难受。改,别犹豫。
1.2.2 可维护性——别让未来的你骂现在的你
可维护性,说白了就是「改代码的时候不心慌」。
我曾经维护过一个老项目,里面有个全局变量 int flag,被 12 个函数读写。我想改一个功能,结果改了 A 函数,B 函数炸了;改了 B 函数,C 函数又出问题了……
那两周,我每天都在「改 bug — 引入新 bug — 再改」的循环里打转。
怎么提升可维护性?
- 减少全局变量:能用局部变量就别用全局的。实在要用,用
static限制作用域。 - 使用模块化设计:每个 .c 文件只负责一个外设或一个功能。比如
uart.c只做串口相关的事,别把 I2C 的代码也塞进去。 - 接口要稳定:对外暴露的函数,参数和返回值一旦定下来,不要轻易改。我习惯在头文件里写清楚「这个函数是干什么的,参数是什么,返回值是什么」。
| 原则 | 坏例子 | 好例子 |
|---|---|---|
| 全局变量 | int g_flag;(到处都能改) |
static int flag;(只在当前文件可见) |
| 函数长度 | 一个函数 500 行 | 一个函数不超过 50 行 |
| 模块划分 | 所有代码写在 main.c 里 | gpio.c / uart.c / timer.c 分开 |
1.2.3 可移植性——换芯片不换代码
嵌入式开发最头疼的事之一,就是换芯片。
项目做了一半,客户说「成本太高,换一颗便宜点的 MCU」。如果你代码里到处都是寄存器地址、中断号、时钟频率的硬编码……那恭喜你,重写吧。
可移植性的核心做法:
- 用宏定义封装硬件相关参数:
// 不好的做法:直接写死地址
#define UART_BASE_ADDR 0x40013800
// 好的做法:通过芯片头文件或配置文件定义
#include "chip_config.h"
// chip_config.h 里定义:
// #define UART1_BASE_ADDR 0x40013800
- 抽象硬件操作层(HAL):比如写一个
gpio_write_pin(),内部实现可能依赖具体芯片,但上层代码只调用这个函数。换芯片时,只需要重写这个函数体,上层逻辑完全不用动。 - 避免依赖编译器特性:比如
__attribute__((packed))在 GCC 里能用,换到 IAR 或 Keil 可能就不一样了。我建议用条件编译或标准写法来兼容。
注意:可移植性不是「一次写好,永远不改」。而是「改的时候,改动范围最小」。我曾经为了追求「绝对可移植」,写了一大堆抽象层,结果代码膨胀了 30%,性能还下降了。适度就好,别过度设计。
1.3 我的个人经验总结
做了十几年嵌入式,我越来越觉得:编码规范不是教条,是经验。
每一个规范背后,都对应着一个真实的坑。比如「不要用魔数」,是因为我见过有人把 0x55 写错成 0x5A,排查了三天才发现。比如「函数要短」,是因为我见过一个 800 行的函数,里面嵌套了 7 层 if-else,改一个条件就要通读全函数。
所以,我建议你:
- 先遵守,再理解——刚开始可能觉得麻烦,但坚持三个月,你会发现代码质量明显提升。
- 团队统一,比个人最优更重要——哪怕你觉得某个规范不合理,只要团队统一了,沟通成本就降低了。
- 规范要持续迭代——没有一套规范能覆盖所有场景。遇到新问题,就补充一条新规范。
好了,这一章就聊到这里。下一章,我会详细讲讲命名规范——变量、函数、宏、文件,到底该怎么起名字才不踩坑。
记住一句话:规范的代码,是对未来自己最大的善意。