3、命名规范(上):变量命名(匈牙利命名法 vs 下划线命名法),宏定义命名,枚举常量命名

各位同学,咱们今天聊聊命名。嗯,别小看这件事。

我见过太多项目,代码跑得飞快,但一看变量名,头皮发麻。什么 abtmpdata1…… 你想想看,三个月后你自己回来看,能认得几个?

命名规范,说白了就是给代码「上户口」。名字起好了,别人读你的代码就像读文章。起不好,那就是看天书。今天咱们先啃下变量命名、宏定义和枚举常量这三块硬骨头。

3.1 变量命名:匈牙利命名法 vs 下划线命名法

变量命名这块,江湖上一直有两大门派。一派是匈牙利命名法,另一派是下划线命名法。我个人习惯用下划线命名法,但匈牙利法在某些场景下也有它的价值。咱们不搞门派之争,只讲实用。

3.1.1 匈牙利命名法

匈牙利命名法,核心思想是「变量名里带类型信息」。比如 iCount 表示整型计数器,szName 表示以零结尾的字符串。我早期做 Windows 驱动开发时,微软的示例代码里全是这套。

举个例子:

int iLedStatus;        // i 表示 int
char *pszDeviceName;   // psz 表示 pointer to zero-terminated string
uint32_t u32TimerCnt;  // u32 表示 unsigned 32-bit

好处很明显——看到变量名就知道它是什么类型。但坏处也致命:

  • 类型变了,名字得跟着改。我在项目中遇到过,重构时把 int 改成 short,结果忘了改前缀,代码里全是 iXXX 实际却是 short,坑了后面接手的人。
  • 现代 IDE 太强了。鼠标悬停就能看到类型,谁还靠前缀猜?
  • 嵌入式里类型别名多。什么 uint8_tint16_t,前缀怎么定?每个人写的都不一样。
注意: 匈牙利命名法在嵌入式驱动开发中,我个人不推荐。除非你维护的是遗留代码,否则尽量别用。它带来的维护成本,远大于那点「一眼看出类型」的便利。

3.1.2 下划线命名法(推荐)

这是我现在的主力命名方式。全小写,单词之间用下划线分隔。简洁、清晰、无歧义。

int led_status;
char *device_name;
uint32_t timer_counter;

为什么推荐它?

  • 和 Linux 内核风格一致。你去看 Linux 驱动源码,全是这套。咱们搞嵌入式驱动开发,跟着内核走准没错。
  • 没有类型耦合。变量类型变了,名字不用动。重构成本低。
  • 可读性强。说白了,下划线就是天然的「分词符」。 led_status 一眼就知道是「LED 状态」,比 ledStatus 或者 iLedSta 舒服多了。

这里有个小技巧:变量名长度控制在 3 到 4 个单词以内。太长了就考虑是不是抽象层次有问题。比如 spi_flash_write_protect_status_register_value —— 嗯,该拆结构体了。

我的习惯: 局部变量可以短一点,比如 icnttmp。但全局变量和模块间接口的变量,必须用完整的下划线命名。我曾经因为偷懒用了 tmp 做全局变量,结果三个模块都在改它,查 bug 查了两天……从那以后,全局变量我必加模块前缀。

3.2 宏定义命名

宏定义这块,规矩很简单,但很多人就是记不住。我直接给结论:宏定义全部大写,单词间用下划线分隔

#define LED_ON              1
#define LED_OFF             0
#define GPIO_PIN_NUMBER     5
#define MAX_BUFFER_SIZE     1024

为什么必须大写?

  • 视觉区分。大写字母在代码里特别显眼,一看就知道是宏,不是变量。这能避免很多误用。
  • 防止命名冲突。宏没有作用域,一旦定义,整个文件甚至包含它的文件都能看到。大写命名相当于在喊:「我是宏!小心我!」

这里有个坑,我踩过:

// 错误示例
#define max(a, b) ((a) > (b) ? (a) : (b))

// 正确示例
#define MAX(a, b) ((a) > (b) ? (a) : (b))

你看,小写的 max 很容易和函数混淆。我曾经在一个项目里,有人定义了小写的 min 宏,结果标准库的 min() 函数被覆盖了,编译报错找了半天。嗯,从那以后我定了个规矩:宏定义必须大写,函数和变量用小写,界限分明。

核心原则: 宏定义命名 = 全大写 + 下划线。不要用驼峰,不要用小写。这是嵌入式 C 开发的铁律。

3.3 枚举常量命名

枚举常量的命名,说实话,业界没有完全统一。但我个人有一套经过实战检验的规则。

3.3.1 枚举类型命名

枚举类型名,我建议用 大写字母开头 + 下划线,或者全小写下划线。看团队习惯。我个人倾向用 typedef 定义枚举类型,名字带 _e 后缀。

typedef enum {
    LED_STATE_OFF = 0,
    LED_STATE_ON,
    LED_STATE_BLINK
} led_state_e;

3.3.2 枚举常量命名

枚举常量的命名,有两种主流风格:

风格 示例 适用场景
全大写 + 下划线 LED_STATE_OFF 和宏定义风格一致,适合公开接口
小写 + 下划线 led_state_off 和变量风格一致,适合模块内部

我个人推荐 全大写 + 下划线。为什么?

  • 枚举常量本质上就是「有名字的整数常量」,和宏定义性质类似。用大写,一眼就能看出它是常量。
  • 在 switch-case 里,大写枚举名特别醒目,代码结构一目了然。
typedef enum {
    ERROR_NONE = 0,
    ERROR_TIMEOUT,
    ERROR_CRC_MISMATCH,
    ERROR_INVALID_PARAM
} error_code_e;

// 使用示例
error_code_e err = read_sensor_data(&buf);
switch (err) {
    case ERROR_NONE:
        // 处理成功
        break;
    case ERROR_TIMEOUT:
        // 处理超时
        break;
    // ...
}
避坑指南: 枚举常量的值,尽量显式指定。尤其是第一个成员,我习惯写成 = 0。为什么?因为 C 标准规定第一个枚举值默认为 0,但如果你后来在中间插入了新成员,默认值会变,容易引发隐蔽的 bug。我曾经在一个通信协议项目里,因为枚举值隐式递增,导致协议版本不兼容,两台设备怎么都握不上手……嗯,从那以后我每个枚举都写显式值。

3.4 总结一下

命名这件事,没有绝对的对错,但有高下之分。我给你的建议是:

  • 变量:用下划线命名法,全小写。别碰匈牙利法。
  • 宏定义:全大写 + 下划线。没得商量。
  • 枚举常量:全大写 + 下划线,显式赋值。

你想想看,如果整个团队都按这个规矩来,代码读起来是不是就像印刷体一样工整?

下一节咱们聊函数命名和文件命名。到时候我会讲讲我当年因为一个函数名没起好,被硬件工程师追着骂的故事……嗯,先卖个关子。