4. MISRA规则解读基础

编码风格这东西,说实话,很多团队一开始都不太当回事。我早年带过一个项目,代码写得飞起,结果到了集成测试阶段,几个人对着同一个文件改,光对齐缩进就花了两天。嗯,从那以后我就明白了——风格不是审美问题,是工程问题。

MISRA-C 对编码风格没有强制规定,但它要求代码必须「可审查」。什么叫可审查?就是别人拿到你的代码,不用猜,不用问,一眼就能看懂逻辑。下面我把我这些年积累的经验拆开来讲。

4.1 命名约定:让变量自己说话

我见过最离谱的命名是什么?int a1, a2, a3; 三个全局变量,分别控制三个电机。你想想看,三个月后你自己回来看,还记得 a1 是哪个电机吗?

我个人习惯用这套规则,在多个项目里验证过,效果不错:

元素 命名规则 示例 说明
全局变量 g_ + 模块名 + 含义 g_motor_speed 一眼看出作用域和归属
静态全局 s_ + 模块名 + 含义 s_adc_buffer 文件内可见,外部不可访问
局部变量 小驼峰或下划线 motorSpeed 或 motor_speed 团队统一即可
宏定义 全大写 + 下划线 BUFFER_SIZE_MAX 与变量区分,防止误用
函数名 模块名_动作_对象 motor_set_speed() 动宾结构,语义清晰
类型定义 模块名 + _t motor_config_t 符合 MISRA 类型规范
避坑指南:我曾经在一个项目里看到有人用 i、j、k 做嵌套循环的计数器,三层循环下来,代码审查时谁也分不清哪个 i 对应哪层。后来我们强制规定:嵌套超过两层时,计数器必须带含义,比如 ch_idx、byte_idx。

命名还有一个容易被忽略的点——不要用否定逻辑。比如 flag_not_ready,你写 if (!flag_not_ready) 的时候,脑子要转两个弯。改成 flag_ready,直接 if (flag_ready),多清爽。

4.2 缩进与排版:视觉上的「呼吸感」

缩进这件事,说白了就是让代码结构在视觉上「立起来」。我见过有人用 Tab,有人用空格,还有人混着用——在某个编辑器里看着对齐,换一个就全乱了。

我的建议很简单:统一用 4 个空格。为什么不是 2 个?2 个缩进在深层嵌套时层次感不够。为什么不是 8 个?8 个缩进会让代码很快跑到屏幕右边去。4 个是折中,也是工业界的主流选择。

来看一个对比:

/* 不推荐的排版:缩进混乱,括号位置随意 */
void motor_init(void)
{
int speed = 0;
if (speed > 0)
  {
motor_run();
}
else
{
motor_stop();
    }
}

/* 推荐的排版:缩进统一,括号对齐 */
void motor_init(void)
{
    int speed = 0;

    if (speed > 0)
    {
        motor_run();
    }
    else
    {
        motor_stop();
    }
}

你想想看,第二种是不是一眼就能看出 if-else 的结构?这就是「呼吸感」——代码块之间有明确的视觉边界,审查时不容易漏掉逻辑分支。

小技巧:我习惯在代码编辑器里开启「显示空白字符」功能。这样能一眼看出哪些地方混进了 Tab,哪些地方有多余的空格。提交代码前跑一遍自动化格式化工具,省心省力。

4.3 注释规范:写给人看的,不是写给编译器看的

注释这件事,我踩过不少坑。早期我总觉得注释越多越好,结果代码改了,注释没改,反而误导人。后来我总结了一条原则:注释应该解释「为什么」,而不是「是什么」。

来看一个反面教材:

/* 将 speed 加 1 */
speed = speed + 1;

这种注释就是废话。代码本身已经说清楚了「是什么」。真正需要注释的是这种:

/* 
 * 这里加 1 是因为电机在启动瞬间需要克服静摩擦力,
 * 实测发现不加这个补偿,启动成功率只有 73%。
 * 参考测试报告 v2.3 第 4 节。
 */
speed = speed + 1;

看到了吗?注释告诉后来人:为什么这么做。这才是注释的价值。

MISRA-C 对注释还有一条硬性要求:禁止使用 // 风格的注释(C90 标准不支持)。虽然 C99 之后支持了,但很多嵌入式编译器仍然只支持 C90。所以统一用 /* */ 是最安全的。

我个人的注释规范如下:

  • 文件头注释:版权、作者、创建日期、文件功能简述
  • 函数头注释:功能描述、参数说明、返回值、注意事项
  • 关键逻辑注释:解释「为什么」这么做,引用测试数据或标准条款
  • TODO 注释:统一格式 /* TODO: 原因 - 责任人 - 日期 */

核心原则:好的代码本身应该是自解释的。如果一段代码需要大段注释才能看懂,说明代码本身写得不够好。先优化代码,再补充注释。

4.4 文件组织结构:让项目像书架一样整齐

一个嵌入式项目,少则几十个文件,多则几百个。如果没有清晰的文件组织结构,找文件就像在垃圾堆里翻东西。

我习惯用这种结构:

project_root/
├── app/                  # 应用层代码
│   ├── motor/
│   │   ├── motor.c
│   │   └── motor.h
│   └── sensor/
│       ├── sensor.c
│       └── sensor.h
├── bsp/                  # 板级支持包
│   ├── bsp_uart.c
│   ├── bsp_uart.h
│   ├── bsp_gpio.c
│   └── bsp_gpio.h
├── lib/                  # 通用库
│   ├── ring_buffer.c
│   ├── ring_buffer.h
│   ├── crc.c
│   └── crc.h
├── config/               # 配置文件
│   ├── project_config.h
│   └── pin_mapping.h
├── test/                 # 测试代码
│   ├── test_motor.c
│   └── test_sensor.c
└── doc/                  # 文档
    └── architecture.md

这个结构有几个好处:

  • 按功能模块分目录:每个模块一个文件夹,内聚性强
  • 头文件与源文件同目录:查找方便,不用来回跳路径
  • 配置单独拎出来:换平台时只改 config 目录,不动业务代码
  • 测试代码独立:不污染生产代码,也方便自动化测试

这里有一个容易犯的错——头文件的包含路径。我见过有人把所有的 .h 文件扔到一个 inc/ 目录下,结果不同模块的同名头文件冲突了。后来我们规定:包含头文件时用相对路径,比如 #include "motor/motor.h",这样模块归属一目了然。

避坑指南:我曾经在一个项目里看到有人把全局变量定义在头文件里,结果多个 .c 文件包含同一个头文件,链接时报重复定义。记住:头文件只放声明,不放定义。全局变量用 extern 声明在头文件里,定义放在对应的 .c 文件中。

4.5 知识结构总览

下面这张图把本章的核心知识点串起来了,你可以对照着检查自己的项目:

编码风格规范知识结构图 编码风格规范 命名约定 缩进与排版 注释规范 文件组织结构 全局变量: g_前缀 静态变量: s_前缀 宏定义: 全大写 函数: 模块_动作_对象 统一4空格缩进 括号对齐 每行不超过80字符 禁止混用Tab/空格 解释「为什么」 文件头/函数头注释 禁止 // 风格 TODO 统一格式 按功能模块分目录 头文件与源文件同目录 配置单独目录 测试代码独立 核心目标:让代码可审查、可维护、可传承 风格不是审美问题,是工程问题

这张图把四个核心维度串在了一起。你可以用它来对照检查自己的项目——看看命名有没有统一前缀,缩进是不是混用了 Tab 和空格,注释是不是在解释「为什么」,文件目录是不是按功能分的。

编码风格这件事,前期花点时间定好规矩,后面能省下大把的沟通成本和调试时间。别等到代码写了几万行再回头改,那时候改起来就真的想哭了。