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 知识体系总览

下面这张图总结了代码风格与规范的核心内容。你可以把它当作一个检查清单,每次写代码时对照着看。

代码风格与规范 命名规范 变量:g_/s_/is/has 函数:动词+名词 宏:全大写+下划线 文件:模块_功能 缩进与排版 4空格缩进 大括号位置统一 空行分隔逻辑块 行宽不超过80 注释规范 Doxygen格式 文件头/函数头 解释为什么 及时更新注释 代码审查清单 基础检查:初始化/边界 风格检查:命名/缩进 安全检查:溢出/中断 逐项确认

代码风格这件事,说白了就是「约定大于配置」。团队里统一了规范,沟通成本就降下来了。我见过太多项目因为风格不统一,代码审查变成了吵架现场。

嗯,今天就聊到这里。记住一句话:好的代码风格,是对下一个阅读者的尊重。