第三章 函数命名规范:命名风格、动词开头、见名知意
好,咱们今天聊聊函数命名。说实话,我见过太多嵌入式项目,代码跑起来没问题,但一看函数名——嗯,那叫一个头疼。我个人的习惯是:写函数名花的时间,至少占整个函数编写时间的10%。别觉得夸张,你想想看,一个命名清晰的函数,三个月后你自己回来看,不用读代码就知道它干嘛的。这才是真正的效率。
3.1 命名风格:选一种,坚持到底
嵌入式C里常见的命名风格就那么几种。我见过最混乱的项目,一个文件里同时用了三种风格——有的用下划线,有的用驼峰,有的全大写。这种代码,说白了就是给自己挖坑。
| 风格 | 示例 | 适用场景 |
|---|---|---|
| 下划线风格(snake_case) | uart_send_byte |
Linux内核、驱动层 |
| 小驼峰(camelCase) | uartSendByte |
部分RTOS、应用层 |
| 大驼峰(PascalCase) | UartSendByte |
类对象、模块初始化 |
| 全大写(UPPER_CASE) | UART_SEND_BYTE |
宏定义、常量函数(极少) |
我在项目中遇到过一件事:有个同事坚持用驼峰写驱动层,结果和HAL库的命名风格打架。每次调用HAL_XXX函数时,自己写的函数又是xxxYyy,看着就别扭。后来统一改成下划线,代码清爽多了。
3.2 动词开头:函数就是动作
函数是什么?是一段可执行的逻辑。所以函数名必须以动词开头。这是铁律。
你想想看,如果看到一个函数叫data_process,你第一反应是什么?它到底是处理数据,还是获取数据,还是初始化数据?嗯,猜去吧。
正确的写法应该是:
// 好的命名
void uart_send_byte(uint8_t data);
uint8_t uart_receive_byte(void);
int uart_init(uint32_t baudrate);
void uart_deinit(void);
// 糟糕的命名
void uart_byte(uint8_t data); // 动词呢?
void uart_data_send(uint8_t data); // 动词放后面了
int uart_baudrate_set(uint32_t baud); // 动词位置别扭
常用动词清单(我自己的积累):
- init / deinit — 初始化/反初始化
- open / close — 打开/关闭设备
- read / write — 读写数据
- send / receive — 发送/接收(通信类)
- get / set — 获取/设置参数
- enable / disable — 使能/禁止
- start / stop — 启动/停止
- is_ / has_ — 判断状态(返回布尔值)
我曾经在一个项目中看到有人写check_temperature(),结果这个函数不仅检查温度,还顺便把风扇打开了。嗯,这就是典型的「名不副实」。函数名说检查,实际干了控制的事。后来我重构时把它拆成read_temperature()和control_fan(),代码逻辑一下子就清晰了。
3.3 见名知意:名字就是最好的注释
好的函数名,不需要注释就能看懂。我经常跟团队说:如果你需要写注释来解释这个函数是干嘛的,那函数名大概率没起好。
来看个对比:
// 糟糕:看了名字不知道干嘛
void func1(void);
int process(int a, int b);
void handle_event(void);
// 好:一看就懂
void led_toggle(void);
int calculate_crc16(uint8_t *data, uint32_t len);
void timer_irq_handler(void);
update()的函数。当时觉得简单明了,结果三个月后回来一看,完全想不起来它更新了什么。是更新显示?更新数据?还是更新状态?最后只能翻代码。从那以后,我要求所有函数名必须包含操作对象和操作内容。
好的函数名应该包含三个要素:
- 动作(动词)—— 做什么
- 对象(名词)—— 对谁做
- 限定(可选)—— 怎么做或什么条件下做
举个例子:
// 动作 + 对象
adc_read_channel();
// 动作 + 对象 + 限定
adc_read_channel_dma(); // 用DMA方式读
adc_read_channel_blocking(); // 阻塞方式读
3.4 命名中的常见坑(我踩过的)
这些年我踩过的坑不少,挑几个典型的说说:
- 缩写过度:
uart_tx_buf_flush()写成u_tx_b_f()—— 省了几个字符,浪费了别人十分钟理解时间。 - 拼写错误:
recieve(正确是receive)、enabel(正确是enable)。这种错误在代码审查时特别显眼,显得不专业。 - 否定命名:
is_not_ready()不如is_busy()直观。因为双重否定在逻辑判断时容易把人绕晕。 - 类型信息冗余:
get_status_flag()里的flag是多余的,get_status()就够了。
一个小技巧:写完函数名后,自己读一遍。如果读起来像一句完整的话,那就是好名字。比如:
uart_send_byte→ 「UART发送一个字节」 ✅byte_uart_send→ 「字节UART发送」 ❌
3.5 实际项目中的命名规范示例
最后,我拿一个实际项目中的模块来举例。这是一个温控系统的部分函数:
// 模块前缀:temp_ctrl_
void temp_ctrl_init(void); // 初始化温控模块
void temp_ctrl_set_target(float temp); // 设置目标温度
float temp_ctrl_get_current(void); // 获取当前温度
int temp_ctrl_start_heating(void); // 启动加热(返回状态码)
void temp_ctrl_stop_heating(void); // 停止加热
bool temp_ctrl_is_target_reached(void); // 是否达到目标温度
void temp_ctrl_emergency_shutdown(void); // 紧急停机
你看,每个函数名都包含模块前缀、动词、对象。不用看注释,光看名字就知道整个模块提供了哪些功能。这就是「见名知意」的威力。
嗯,关于函数命名,今天就聊这么多。记住一句话:好的命名,是写给三个月后的自己看的。你总不希望到时候对着自己的代码挠头吧?