4. 编码规范基础:命名规范、注释规范与排版缩进

好,咱们进入第四章。说实话,编码规范这东西,很多工程师觉得是「形式主义」。我早年也这么想——代码能跑不就行了?直到有一次,我接手一个离职同事的项目,打开文件一看,变量名全是 a1、b2、temp,函数名清一色 func1 到 func50。那一周,我基本是在骂娘中度过的。

从那以后,我彻底明白了:规范不是束缚,是保护。尤其在嵌入式安全领域,一个命名不清的变量,可能直接导致误读、误改,最终酿成线上事故。

这一章,咱们就聊聊命名、注释和排版。这些都是「看着简单,做起来全是坑」的东西。

4.1 命名规范:让代码自己会说话

命名是代码的第一张脸。好的命名,读起来像读英文句子;差的命名,读起来像猜谜语。

4.1.1 变量命名

我个人习惯用 匈牙利命名法 + 下划线分隔 的组合。MISRA C 没有强制规定具体风格,但要求 标识符不能歧义、不能与关键字冲突、长度要有意义。

类型 推荐格式 示例 说明
全局变量 g_ 前缀 + 模块名 + 含义 g_sysTickCount 一眼看出作用域
静态全局变量 s_ 前缀 + 含义 s_adcBuffer 文件内私有
局部变量 直接含义,小写 + 下划线 sensor_value 简短清晰
指针变量 p 前缀 + 类型 + 含义 pUartBuf 提醒这是指针
布尔变量 is_ / has_ 开头 is_initialized 语义化判断
避坑指南: 我曾经见过一个项目,全局变量不加任何前缀,结果在中断服务函数里误改了另一个模块的计数器,导致系统跑飞。排查了整整两天。从那以后,我要求团队所有全局变量必须加 g_ 前缀,没有例外。

4.1.2 函数命名

函数命名要遵循 「动词 + 名词」 结构。说白了,看到函数名就知道它「干了什么」。

// 好的命名
uint32_t get_sensor_value(uint8_t channel);
void set_led_state(LedState state);
bool is_timer_expired(void);

// 差的命名
void process(void);           // 处理什么?
uint32_t data(void);          // 返回什么数据?
void led(void);               // 设置?读取?切换?

MISRA C 规则 8.2 要求:函数必须有显式原型声明。命名清晰了,原型声明才真正有意义。

4.1.3 宏命名

宏的命名,行业惯例是 全大写 + 下划线。为什么?因为宏是预处理阶段替换的,没有类型检查,必须一眼就能和普通变量区分开。

// 好的宏命名
#define BUFFER_SIZE          (256)
#define GPIO_PIN_13          (13U)
#define MAX_RETRY_COUNT      (3U)

// 差的宏命名
#define buffer_size          (256)   // 和变量分不清
#define maxRetryCount        (3U)    // 风格不统一
我的经验: 宏定义一定要加括号包裹整个表达式。我见过有人写 #define MUL(a, b) a * b,结果调用 MUL(1+2, 3) 展开成 1+2*3,结果等于 7 而不是 9。这种坑,踩一次就记住了。

4.1.4 文件命名

文件命名要体现 模块 + 功能。我习惯用 模块名_功能.c 的格式。

模块 文件名示例 说明
UART 驱动 uart_driver.c 驱动层
UART 配置 uart_config.c 配置层
系统定时器 sys_timer.c 系统服务
错误处理 error_handler.c 公共模块

4.2 注释规范:写给人看的说明书

注释不是写给编译器看的,是写给 三个月后的自己 和 接手你代码的同事 看的。我见过太多「为了注释而注释」的代码,比如:

i = i + 1;  // 把 i 加 1

这种注释,删掉比留着好。

4.2.1 文件头注释

每个源文件和头文件,开头都要有一个标准文件头。我团队用的模板是这样的:

/**
 * @file    uart_driver.c
 * @brief   UART 驱动层实现
 * @author  张三
 * @date    2024-01-15
 * @version v1.2.0
 *
 * @note    本文件实现 UART 的初始化、发送、接收功能。
 *          依赖 uart_hw.c 提供的硬件抽象层。
 *
 * @copyright Copyright (c) 2024, Company Name. All rights reserved.
 */

这里面有几个关键信息:文件名、功能简述、作者、日期、版本、依赖关系。为什么要有依赖关系?因为半年后你改代码时,需要知道这个文件「碰了哪些地方」。

4.2.2 函数头注释

每个函数都要有函数头注释,说明 功能、参数、返回值、注意事项。

/**
 * @brief   获取指定通道的传感器原始值
 * @param[in]  channel  传感器通道号 (0-3)
 * @param[out] value    指向存储读取值的缓冲区
 * @return    true  读取成功
 *            false 读取失败(通道无效或硬件错误)
 * @note     本函数会阻塞等待转换完成,最大等待时间 10ms
 * @warning  调用前需确保传感器已上电且 ADC 已初始化
 */
bool get_sensor_raw_value(uint8_t channel, uint16_t *value);
关键点: @param[in] 和 @param[out] 一定要写清楚。我见过一个 bug,就是因为调用者以为参数是输出,实际上函数把它当输入用了,结果数据一直不对。

4.2.3 代码行注释

行注释要解释 「为什么这么做」,而不是 「做了什么」。

// 好的行注释
// 等待硬件 FIFO 非空,超时 100us
while (UART_IS_EMPTY(uart)) {
    if (get_tick_count() - start_tick > TIMEOUT_100US) {
        return false;  // 超时返回错误
    }
}

// 差的行注释
while (UART_IS_EMPTY(uart)) {  // 等待 UART 为空
    // 获取 tick
    if (get_tick_count() - start_tick > TIMEOUT_100US) {
        return false;  // 返回 false
    }
}

你想想看,第二种注释是不是等于没写?代码本身已经表达了「做了什么」,注释应该补充代码无法表达的信息。

4.3 排版与缩进:代码的呼吸感

排版缩进,说白了就是代码的「呼吸感」。一段呼吸顺畅的代码,读起来不累;一段挤在一起的代码,看着就头疼。

4.3.1 缩进风格

MISRA C 没有强制规定缩进空格数,但要求 整个项目统一。我个人推荐 4 空格缩进,不用 Tab。为什么?因为不同编辑器的 Tab 宽度不一样,换台机器可能就乱了。

void example_function(void)
{
    if (condition)
    {
        do_something();
        do_another_thing();
    }
    else
    {
        handle_error();
    }
}

注意大括号的位置:我习惯 大括号另起一行。这样每个代码块的开头和结尾一目了然,尤其适合嵌套较深的嵌入式代码。

4.3.2 空行与分组

逻辑相关的代码放在一起,不同逻辑之间用空行隔开。这就像写文章分段一样。

void system_init(void)
{
    // 硬件初始化
    clock_init();
    gpio_init();
    uart_init();

    // 软件模块初始化
    task_manager_init();
    error_handler_init();

    // 启动系统
    start_scheduler();
}

每个初始化步骤之间用空行隔开,读起来是不是舒服多了?

4.3.3 对齐与换行

函数参数太长时,要合理换行。我一般遵循 每行不超过 80 字符 的原则。

// 好的换行
uint32_t calculate_checksum(const uint8_t *data,
                            uint32_t length,
                            ChecksumType type);

// 差的换行
uint32_t calculate_checksum(const uint8_t *data, uint32_t length, ChecksumType type);

赋值操作符、比较操作符前后要加空格。这看起来是小事,但能大幅提升可读性。

// 好的风格
int result = a + b;
if (result >= MAX_VALUE) { ... }

// 差的风格
int result=a+b;
if (result>=MAX_VALUE) { ... }

4.4 本章知识体系

下面这张图,把本章的核心知识点串起来了。你可以把它当作一个「编码规范检查清单」来用。

编码规范基础 - 知识体系 编码规范基础 命名规范 注释规范 排版缩进 变量命名 函数命名 宏/文件命名 文件头注释 函数头注释 代码行注释 缩进风格 空行分组 对齐换行 核心原则:统一 > 美观 > 个人习惯 整个项目使用同一套规范,比「哪套规范最好」更重要 落地建议 1. 在项目启动时制定编码规范文档,全员评审通过后执行 2. 使用静态检查工具(如 PC-lint、Coverity)自动检查规范合规性

本章核心要点:

  • 命名要 自文档化——看到名字就知道用途
  • 注释要解释 「为什么」,而不是「是什么」
  • 排版要 统一——整个项目保持一致风格
  • 所有规范都要 写入项目文档,并用工具自动检查

嗯,这一章的内容就到这里。命名、注释、排版,看起来都是「小事情」,但正是这些小事情,决定了你的代码是「艺术品」还是「垃圾堆」。下次写代码前,不妨多花 30 秒想想命名,多写一行注释说明原因。相信我,三个月后的你会感谢现在的自己。