代码风格与规范:命名规范、注释规范、代码布局、模块化设计

说实话,我见过太多「能跑就行」的代码了。

刚入行那几年,我也觉得代码风格是小事。反正编译器又不管变量名长不长,注释写不写都能运行。直到有一次,我接手一个同事离职留下的项目——整整两万行代码,全部用 a1a2temptmp2 这种名字。函数名清一色 func1func2……我花了整整两周才理清楚逻辑。嗯,从那以后,我再也不敢轻视代码风格了。

说白了,代码风格不是写给编译器看的,是写给人看的。而人,才是维护代码的主力。

一、命名规范:让变量自己说话

我个人习惯,命名要「见名知义」。你想想看,如果看到一个变量叫 cnt,你大概能猜到是计数器。但如果叫 n,你就得去翻上下文了。

核心原则:命名要准确反映用途,不要怕名字长。

1.1 变量命名

  • 局部变量:简短但清晰。比如 ij 做循环索引没问题,但 temp 最好改成 temp_valueswap_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 模块间通信

尽量用函数调用和回调,少用全局变量。全局变量多了,模块之间就「耦合」死了。

我的经验:如果一个模块需要访问另一个模块的内部变量,说明接口设计有问题。重新审视一下,把该暴露的暴露出来,不该暴露的藏好。

五、知识体系总览

下面这张图,是我梳理的代码风格与规范的核心逻辑。你可以把它当作一个检查清单。

代码风格与规范 命名规范 变量、函数、宏 见名知义 注释规范 文件头、函数头 关键逻辑说明 代码布局 缩进、空行、对齐 行长度限制 模块化设计 高内聚、低耦合 接口与实现分离 核心目标 可读性 · 可维护性 · 可复用性 实践建议 1. 团队统一风格,用 clang-format 自动化格式化 2. 代码审查时把风格问题作为必查项 3. 定期重构,消除「坏味道」

六、总结

代码风格这件事,短期看是「麻烦」,长期看是「省钱」。省的是你未来排查 bug 的时间,省的是新同事上手的时间,省的是整个团队沟通的成本。

我个人习惯,每次提交代码前都会问自己三个问题:

  • 别人能不能一眼看懂这个变量是干什么的?
  • 如果半年后我回来看这段代码,需要多久才能理解?
  • 这个模块能不能独立拿出来给另一个项目用?

如果答案都是肯定的,那这份代码就合格了。

最后说一句:规范是死的,人是活的。不要为了规范而规范,关键是让代码「好读、好改、好复用」。你想想看,是不是这个理?

公众号:蓝海资料掘金营,微信deep3321