第三章 函数命名规范:命名风格、动词开头、见名知意

好,咱们今天聊聊函数命名。说实话,我见过太多嵌入式项目,代码跑起来没问题,但一看函数名——嗯,那叫一个头疼。我个人的习惯是:写函数名花的时间,至少占整个函数编写时间的10%。别觉得夸张,你想想看,一个命名清晰的函数,三个月后你自己回来看,不用读代码就知道它干嘛的。这才是真正的效率。

3.1 命名风格:选一种,坚持到底

嵌入式C里常见的命名风格就那么几种。我见过最混乱的项目,一个文件里同时用了三种风格——有的用下划线,有的用驼峰,有的全大写。这种代码,说白了就是给自己挖坑。

风格 示例 适用场景
下划线风格(snake_case) uart_send_byte Linux内核、驱动层
小驼峰(camelCase) uartSendByte 部分RTOS、应用层
大驼峰(PascalCase) UartSendByte 类对象、模块初始化
全大写(UPPER_CASE) UART_SEND_BYTE 宏定义、常量函数(极少)
我的建议:嵌入式底层代码,优先用下划线风格。为什么?因为Linux内核、FreeRTOS、STM32 HAL库都这么用。你跟着行业主流走,新同事上手快,代码审查也省心。

我在项目中遇到过一件事:有个同事坚持用驼峰写驱动层,结果和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()的函数。当时觉得简单明了,结果三个月后回来一看,完全想不起来它更新了什么。是更新显示?更新数据?还是更新状态?最后只能翻代码。从那以后,我要求所有函数名必须包含操作对象操作内容

好的函数名应该包含三个要素:

  1. 动作(动词)—— 做什么
  2. 对象(名词)—— 对谁做
  3. 限定(可选)—— 怎么做或什么条件下做

举个例子:

// 动作 + 对象
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);      // 紧急停机

你看,每个函数名都包含模块前缀、动词、对象。不用看注释,光看名字就知道整个模块提供了哪些功能。这就是「见名知意」的威力。

嗯,关于函数命名,今天就聊这么多。记住一句话:好的命名,是写给三个月后的自己看的。你总不希望到时候对着自己的代码挠头吧?