3、命名规范(上):变量命名(匈牙利命名法 vs 下划线命名法),宏定义命名,枚举常量命名
各位同学,咱们今天聊聊命名。嗯,别小看这件事。
我见过太多项目,代码跑得飞快,但一看变量名,头皮发麻。什么 a、b、tmp、data1…… 你想想看,三个月后你自己回来看,能认得几个?
命名规范,说白了就是给代码「上户口」。名字起好了,别人读你的代码就像读文章。起不好,那就是看天书。今天咱们先啃下变量命名、宏定义和枚举常量这三块硬骨头。
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_t、int16_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 —— 嗯,该拆结构体了。
i、cnt、tmp。但全局变量和模块间接口的变量,必须用完整的下划线命名。我曾经因为偷懒用了 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() 函数被覆盖了,编译报错找了半天。嗯,从那以后我定了个规矩:宏定义必须大写,函数和变量用小写,界限分明。
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 总结一下
命名这件事,没有绝对的对错,但有高下之分。我给你的建议是:
- 变量:用下划线命名法,全小写。别碰匈牙利法。
- 宏定义:全大写 + 下划线。没得商量。
- 枚举常量:全大写 + 下划线,显式赋值。
你想想看,如果整个团队都按这个规矩来,代码读起来是不是就像印刷体一样工整?
下一节咱们聊函数命名和文件命名。到时候我会讲讲我当年因为一个函数名没起好,被硬件工程师追着骂的故事……嗯,先卖个关子。