3. CommonJS 规范与 Node.js 模块系统
说实话,我见过太多嵌入式项目死在代码可读性上。功能跑通了,但三个月后连原作者都看不懂自己写的代码。这真不是笑话,我亲身经历过。
代码风格不是花架子。它是团队协作的基石,是项目长期维护的保障。今天我们就聊聊怎么写出「会说话」的代码。
3.1 命名规范:名字就是最好的注释
我有个习惯:如果花超过30秒想一个变量名,说明我对这个变量的职责还没想清楚。命名这件事,其实反映了你对代码的理解深度。
3.1.1 变量命名
嵌入式开发中,变量命名要遵循几个原则:
- 全局变量:加前缀 g_,比如 g_systemTick
- 静态变量:加前缀 s_,比如 s_bufferIndex
- 局部变量:简短但清晰,比如 tempValue 而不是 tv
- 布尔变量:用 is/has/can 开头,比如 isReady、hasError
核心原则:名字要能自解释。看到名字就知道它是什么、干什么用。
// 不好的命名
int a;
int cnt;
uint8_t flag;
// 好的命名
int g_adcRawValue;
static uint8_t s_txBufferIndex;
uint8_t isDataReady;
我的经验:变量名长度控制在8-20个字符之间。太短含义不清,太长影响阅读流畅度。
3.1.2 函数命名
函数命名我推荐用「动词+名词」结构。这样读起来就像在描述一个动作。
// 不好的命名
void process(void);
int get(void);
// 好的命名
void uart_sendData(uint8_t *data, uint16_t len);
int timer_getElapsedMs(void);
我曾经在一个项目里看到有人用 f1、f2、f3 命名函数。嗯,那代码后来被重写了。
3.1.3 宏定义命名
宏定义全部大写,用下划线分隔。这是C语言的传统,也是共识。
#define BUFFER_SIZE 256
#define GPIO_PIN_13 13
#define ERROR_TIMEOUT (-1)
注意:宏定义不要加尾部分号。加了分号在某些上下文中会引发奇怪的编译错误。我踩过这个坑。
3.1.4 文件命名
文件名要体现模块功能。我习惯用「模块名_功能」的格式。
| 模块 | 文件名示例 |
|---|---|
| UART驱动 | uart_driver.c / uart_driver.h |
| 定时器管理 | timer_manager.c / timer_manager.h |
| 错误处理 | error_handler.c / error_handler.h |
3.2 缩进与排版:视觉上的整洁
代码排版就像房间的整洁度。乱糟糟的代码,逻辑大概率也是乱的。
3.2.1 缩进风格
我个人推荐使用4个空格缩进。Tab键在不同编辑器里显示宽度不同,容易乱。
// 好的缩进风格
void uart_init(uint32_t baudrate)
{
uint32_t divider;
divider = SYSTEM_CLOCK / baudrate;
UART_BRR = divider;
if (divider > 0) {
UART_CR1 |= UART_CR1_UE;
}
}
3.2.2 大括号位置
函数定义的大括号另起一行,控制语句的大括号跟在后面。这是Linux内核的风格,也是我用了十几年的习惯。
// 函数定义
void timer_start(void)
{
// 函数体
}
// 控制语句
if (condition) {
// 执行体
} else {
// 其他执行体
}
3.2.3 空行使用
空行是代码的呼吸。函数之间空两行,逻辑块之间空一行。
void func_a(void)
{
// 初始化部分
init_a();
init_b();
// 处理部分
process_data();
// 清理部分
cleanup();
}
void func_b(void)
{
// 另一个函数
}
3.3 注释规范:Doxygen风格
注释不是写给机器看的,是写给下一个人(包括三个月后的自己)看的。
3.3.1 文件头注释
/**
* @file uart_driver.c
* @brief UART驱动实现
* @author 张三
* @date 2024-01-15
* @version 1.0.0
*
* @details 实现UART的初始化、发送、接收功能。
* 支持波特率9600-115200,数据位8位。
*/
3.3.2 函数注释
/**
* @brief 发送数据到UART
* @param data 待发送的数据指针
* @param len 数据长度(字节)
* @return int 成功返回0,失败返回-1
* @note 调用前需确保UART已初始化
* @warning 数据指针不能为NULL
*/
int uart_sendData(uint8_t *data, uint16_t len)
{
// 实现代码
}
小技巧:Doxygen注释可以自动生成文档。我习惯在写完函数后立刻写注释,拖久了就忘了。
3.3.3 代码内注释
代码内注释要解释「为什么」,而不是「是什么」。
// 不好的注释
i++; // i加1
// 好的注释
i++; // 跳过校验和字节,因为校验和不需要参与计算
3.4 代码审查清单
代码审查是质量保证的最后一道防线。我整理了一份清单,每次提交代码前都会过一遍。
3.4.1 基础检查项
- ☐ 所有变量都已初始化
- ☐ 没有未使用的变量
- ☐ 函数参数检查(特别是指针参数)
- ☐ 边界条件处理(数组越界、除零等)
- ☐ 错误返回值处理
3.4.2 风格检查项
- ☐ 命名是否符合规范
- ☐ 缩进是否统一
- ☐ 注释是否完整且准确
- ☐ 代码行长度是否超过80字符
- ☐ 是否有魔法数字(应该用宏定义)
3.4.3 安全检查项
- ☐ 缓冲区是否有溢出风险
- ☐ 中断服务程序是否足够短
- ☐ 临界区是否有保护
- ☐ 是否有死锁风险
- ☐ 全局变量是否被意外修改
我曾经犯过的错:有一次审查时漏掉了中断中的全局变量访问,结果导致数据竞争。从那以后,我把「全局变量访问保护」列入了必查项。
3.5 知识体系总览
下面这张图总结了代码风格与规范的核心内容。你可以把它当作一个检查清单,每次写代码时对照着看。
代码风格这件事,说白了就是「约定大于配置」。团队里统一了规范,沟通成本就降下来了。我见过太多项目因为风格不统一,代码审查变成了吵架现场。
嗯,今天就聊到这里。记住一句话:好的代码风格,是对下一个阅读者的尊重。