代码风格与规范:命名规范、注释规范、代码布局、模块化设计
说实话,我见过太多「能跑就行」的代码了。
刚入行那几年,我也觉得代码风格是小事。反正编译器又不管变量名长不长,注释写不写都能运行。直到有一次,我接手一个同事离职留下的项目——整整两万行代码,全部用 a1、a2、temp、tmp2 这种名字。函数名清一色 func1、func2……我花了整整两周才理清楚逻辑。嗯,从那以后,我再也不敢轻视代码风格了。
说白了,代码风格不是写给编译器看的,是写给人看的。而人,才是维护代码的主力。
一、命名规范:让变量自己说话
我个人习惯,命名要「见名知义」。你想想看,如果看到一个变量叫 cnt,你大概能猜到是计数器。但如果叫 n,你就得去翻上下文了。
1.1 变量命名
- 局部变量:简短但清晰。比如
i、j做循环索引没问题,但temp最好改成temp_value或swap_buf。 - 全局变量:加前缀区分。我习惯用
g_开头,比如g_system_tick。 - 静态变量:用
s_前缀,比如s_last_error_code。
// 不推荐
int n;
char *p;
void *d;
// 推荐
int packet_count;
char *rx_buffer;
void *device_context;
1.2 函数命名
函数名应该是一个「动词+名词」的组合。我在项目中遇到过有人写 deal() 这种函数,你根本不知道它在处理什么。
// 不推荐
void deal(void);
int process(int a, int b);
// 推荐
void uart_send_byte(uint8_t data);
int calculate_checksum(const uint8_t *buf, size_t len);
1.3 宏与常量
全大写,下划线分隔。这是 C 语言的老传统了。
#define BUFFER_SIZE 256
#define MAX_RETRY_COUNT 3
#define GPIO_PIN_LED 13
#define TIMEOUT_MS 1000 // 单位:毫秒,这样别人一看就明白。
二、注释规范:写给人看的说明书
注释不是越多越好。我曾经见过一个项目,每行代码后面都跟着注释,结果代码改了注释没改,反而误导人。
注释要回答「为什么」,而不是「是什么」。代码本身已经说明了「是什么」。
2.1 文件头注释
每个源文件和头文件开头,我都会加一段注释,说明作者、日期、功能描述。
/*
* 文件名: uart_driver.c
* 描述: STM32 UART 驱动,支持中断接收
* 作者: 张三
* 日期: 2024-01-15
* 版本: v1.2
*/
2.2 函数注释
说明函数的功能、参数、返回值、注意事项。
/**
* @brief 发送一帧数据到 UART
* @param data 数据缓冲区指针
* @param len 数据长度(字节)
* @return 0 成功,-1 失败(缓冲区满)
* @note 该函数为阻塞模式,超时时间由 g_uart_timeout_ms 决定
*/
int uart_send_frame(const uint8_t *data, size_t len);
2.3 关键逻辑注释
遇到复杂的算法、状态机、或者容易踩坑的地方,一定要加注释。
// 注意:此处必须先读取状态寄存器,再读取数据寄存器
// 否则硬件会自动清除状态位,导致数据丢失
status = UART->SR;
data = UART->DR;
三、代码布局:让结构一目了然
代码布局就像房间的装修。东西摆放整齐,找起来就快。
3.1 缩进与对齐
我统一用 4 个空格缩进。不用 Tab,因为不同编辑器 Tab 宽度不一样。
if (condition) {
do_something();
if (sub_condition) {
do_nested();
}
}
3.2 空行分隔逻辑块
函数内部用空行分隔不同的逻辑段落。比如:变量声明、参数检查、核心处理、错误处理。
int process_data(uint8_t *buf, size_t len) {
int ret = 0;
uint32_t checksum = 0;
if (buf == NULL || len == 0) {
return -1;
}
checksum = calculate_checksum(buf, len);
if (checksum != EXPECTED_CHECKSUM) {
ret = -2;
goto error_exit;
}
save_to_buffer(buf, len);
return 0;
error_exit:
clear_buffer();
return ret;
}
3.3 行长度限制
我习惯每行不超过 80 个字符。超过就换行,对齐。
// 不推荐
int result = some_function_with_long_name(param1, param2, param3, param4, param5);
// 推荐
int result = some_function_with_long_name(param1, param2,
param3, param4, param5);
四、模块化设计:拆开才能复用
模块化设计,说白了就是「高内聚、低耦合」。每个模块只做一件事,做好一件事。
4.1 模块划分原则
- 按功能划分:驱动层、协议层、应用层分开。
- 按硬件划分:UART、I2C、SPI 各一个模块。
- 按业务划分:数据采集、日志记录、状态管理各一个模块。
4.2 接口设计
模块之间通过头文件暴露接口,内部实现对外隐藏。我习惯用 _priv.h 后缀表示内部头文件。
// uart_driver.h —— 公开接口
#ifndef UART_DRIVER_H
#define UART_DRIVER_H
#include <stdint.h>
#include <stddef.h>
int uart_init(uint32_t baudrate);
int uart_send(const uint8_t *data, size_t len);
int uart_recv(uint8_t *buf, size_t max_len);
#endif
// uart_driver_priv.h —— 内部接口
#ifndef UART_DRIVER_PRIV_H
#define UART_DRIVER_PRIV_H
void uart_isr_handler(void);
void uart_dma_callback(void);
#endif
4.3 模块间通信
尽量用函数调用和回调,少用全局变量。全局变量多了,模块之间就「耦合」死了。
五、知识体系总览
下面这张图,是我梳理的代码风格与规范的核心逻辑。你可以把它当作一个检查清单。
六、总结
代码风格这件事,短期看是「麻烦」,长期看是「省钱」。省的是你未来排查 bug 的时间,省的是新同事上手的时间,省的是整个团队沟通的成本。
我个人习惯,每次提交代码前都会问自己三个问题:
- 别人能不能一眼看懂这个变量是干什么的?
- 如果半年后我回来看这段代码,需要多久才能理解?
- 这个模块能不能独立拿出来给另一个项目用?
如果答案都是肯定的,那这份代码就合格了。
公众号:蓝海资料掘金营,微信deep3321