第4章 排版与缩进:缩进风格、大括号位置与空格空行规则

说到代码排版,我见过太多团队因为缩进风格吵得不可开交。说实话,这玩意儿没有绝对的对错,但一旦定下来,就得所有人遵守。我自己做了十几年嵌入式开发,从51单片机到ARM Cortex-M系列,代码风格换了好几轮,最后发现——一致性比风格本身更重要

4.1 三大缩进风格,你站哪一派?

嵌入式圈子里,缩进风格主要有三派。我最早接触的是K&R风格,因为读《C程序设计语言》那本书时,里面的例子全是这么写的。后来进了大厂,发现他们用Allman风格。再后来做开源项目,又遇到了Whitesmiths风格。嗯,咱们一个一个说。

4.1.1 K&R风格(又称“内核风格”)

K&R风格的特点是:左大括号放在上一行的末尾,右大括号单独一行。Linux内核用的就是这种风格。我个人习惯用K&R,因为代码紧凑,一屏能看更多内容。

// K&R风格示例
void uart_init(uint32_t baudrate) {
    uint32_t divisor = 0;
    
    if (baudrate > 115200) {
        divisor = 1;
    } else {
        divisor = 16;
    }
    
    UBRR0H = (uint8_t)(divisor >> 8);
    UBRR0L = (uint8_t)divisor;
}

我在项目中遇到过一件事:有个同事写K&R风格时,把if后面的左大括号忘了,结果else挂到了上一级if上。排查了整整一下午。所以用K&R风格,大括号绝对不能省,哪怕只有一行代码。

4.1.2 Allman风格(又称“BSD风格”)

Allman风格的特点是:左大括号单独一行,和右大括号对齐。这种风格可读性最好,因为大括号的层次结构一目了然。

// Allman风格示例
void uart_init(uint32_t baudrate)
{
    uint32_t divisor = 0;
    
    if (baudrate > 115200)
    {
        divisor = 1;
    }
    else
    {
        divisor = 16;
    }
    
    UBRR0H = (uint8_t)(divisor >> 8);
    UBRR0L = (uint8_t)divisor;
}

你想想看,Allman风格是不是特别适合代码审查?每个代码块的开头和结尾都清清楚楚。我建议团队新人先用Allman风格,等熟练了再换别的。

4.1.3 Whitesmiths风格

Whitesmiths风格比较小众,它的特点是:左大括号和代码块缩进在同一层级。说白了,大括号的缩进和里面的代码一样。

// Whitesmiths风格示例
void uart_init(uint32_t baudrate)
    {
    uint32_t divisor = 0;
    
    if (baudrate > 115200)
        {
        divisor = 1;
        }
    else
        {
        divisor = 16;
        }
    
    UBRR0H = (uint8_t)(divisor >> 8);
    UBRR0L = (uint8_t)divisor;
    }

说实话,Whitesmiths风格我用的不多。有一次接手一个老项目,代码全是这种风格,我花了三天才适应。不过它的好处是:大括号和代码块的关系非常直观,不容易漏掉括号。

4.2 大括号位置:细节决定成败

大括号的位置,看似小事,其实坑不少。我总结了几条铁律,你记一下。

核心原则:大括号必须成对出现,且位置在整个项目中保持一致。

4.2.1 函数体的大括号

不管用哪种风格,函数体的大括号必须独占一行。为什么?因为函数是代码的基本单元,它的开始和结束需要一目了然。

// 正确做法
void timer_init(void)
{
    // 初始化代码
}

// 错误做法(函数体大括号不能放在行尾)
void timer_init(void) {
    // 初始化代码
}

4.2.2 控制语句的大括号

if、for、while、do-while后面的大括号,必须写,哪怕只有一行代码。我曾经因为这个问题吃过亏:

避坑指南:我曾经在维护一个电机控制程序时,看到有人写了这样的代码:

if (motor_speed > MAX_SPEED)
    motor_stop();
    emergency_brake();  // 这行不在if里面!

结果电机超速后只停了电机,没触发紧急刹车。从那以后,我要求团队所有控制语句都必须加大括号。

// 正确做法
if (motor_speed > MAX_SPEED) {
    motor_stop();
    emergency_brake();
}

// 或者Allman风格
if (motor_speed > MAX_SPEED)
{
    motor_stop();
    emergency_brake();
}

4.3 空格规则:让代码呼吸起来

代码就像人一样,需要呼吸。空格就是代码的呼吸。我见过有人写代码像挤牙膏,所有字符挤在一起,看着就头疼。

4.3.1 运算符两侧加空格

二元运算符(+、-、*、/、=、==、!=、<、>、&&、||等)两侧必须加空格。一元运算符(++、--、!、~等)不加空格。

// 正确做法
int sum = a + b;
int result = (x * y) / z;
if (flag == TRUE && count > 0) {
    counter++;
}

// 错误做法
int sum=a+b;
int result=(x*y)/z;
if(flag==TRUE&&count>0){
    counter++;
}

4.3.2 逗号和分号后面加空格

逗号后面加一个空格,分号后面如果还有内容也要加空格。

// 正确做法
void func(int a, int b, int c);
for (int i = 0; i < 10; i++) {
    printf("value: %d\n", array[i]);
}

// 错误做法
void func(int a,int b,int c);
for(int i=0;i<10;i++){
    printf("value:%d\n",array[i]);
}

4.3.3 关键字和括号之间加空格

if、for、while、switch等关键字后面加一个空格,再跟左括号。但函数名后面不加空格。

// 正确做法
if (condition) { ... }
while (running) { ... }
func(param1, param2);  // 函数名后面不加空格

// 错误做法
if(condition) { ... }
while(running) { ... }
func (param1, param2);  // 函数名后面不能加空格

4.4 空行规则:段落感很重要

代码没有空行,就像文章没有段落。我读代码时,先看空行,空行划分出了代码的逻辑块。

4.4.1 函数之间空一行

每个函数定义之间空一行。如果函数有注释,注释和函数之间不空行。

/**
 * @brief 初始化UART
 */
void uart_init(void)
{
    // ...
}

// 这里空一行

/**
 * @brief 发送一个字节
 */
void uart_send_byte(uint8_t data)
{
    // ...
}

4.4.2 逻辑块之间空一行

在一个函数内部,不同逻辑块之间空一行。比如变量声明和业务逻辑之间,或者不同的处理步骤之间。

void process_sensor_data(void)
{
    // 变量声明区
    uint16_t raw_value;
    float calibrated_value;
    uint8_t status;
    
    // 读取原始数据
    raw_value = adc_read(ADC_CHANNEL_0);
    
    // 校准处理
    calibrated_value = (float)raw_value * CALIB_FACTOR;
    
    // 状态判断
    if (calibrated_value > THRESHOLD_HIGH) {
        status = STATUS_ALARM;
    } else {
        status = STATUS_NORMAL;
    }
    
    // 输出结果
    set_output(status);
}

4.4.3 不要连续空多行

代码中最多空一行。连续空两行以上,说明你的代码结构有问题,需要重构。

小技巧:我习惯在代码写完后再过一遍空行。把连续的空行删掉,把该有的空行补上。就像写文章后的润色一样。

4.5 缩进宽度:Tab还是空格?

这个问题堪称编程界的“甜咸之争”。我的建议是:用空格,缩进4个字符

缩进方式 优点 缺点
Tab(制表符) 文件体积小,可自定义显示宽度 不同编辑器显示不一致,容易错位
空格(2字符) 显示一致,节省水平空间 嵌套深时层次不明显
空格(4字符) 显示一致,层次清晰 嵌套深时占用较多水平空间
空格(8字符) 层次非常清晰 嵌套3层以上就写不下代码了

我个人推荐4空格缩进。为什么?因为嵌入式代码通常嵌套不深(超过4层就该重构了),4空格既能保证清晰度,又不会浪费太多空间。

重要提醒:不管你选哪种,千万不要混用Tab和空格。我见过最惨的一次,一个文件里Tab和空格混用,在VSCode里看着对齐的代码,拿到Keil里全乱了。最后用了一个工具统一转换才搞定。

4.6 实战建议:建立团队规范

说了这么多,最后给你几个实操建议:

  1. 用工具强制统一:在项目中配置.editorconfig文件,或者用clang-format自动格式化。别指望人肉检查,人总会犯错。
  2. 代码审查时检查风格:我每次做Code Review,第一遍先看风格,第二遍才看逻辑。风格不对的直接打回。
  3. 新人入职先学规范:新同事来了,先给一份代码规范文档,再给一个格式化工具配置。别让他自己摸索。
  4. 定期做风格审计:每季度用工具扫描一次代码库,看看有没有风格违规的地方。及时纠正。

记住一句话:好的代码风格,不是为了让机器看懂,而是让人看懂。机器只关心语法对不对,人还要关心逻辑清不清晰。你想想看,半年后你自己回头看代码,如果风格乱七八糟,是不是也得骂娘?

嗯,这一章就到这里。下一章咱们聊聊命名规范——变量名、函数名、宏定义,这些看似简单的东西,其实坑也不少。