4、代码风格与规范:ArduPilot编码规范、命名约定、注释规范、代码审查流程

说实话,我刚接触ArduPilot那会儿,第一反应是——这代码怎么这么规整?

后来我参与了几次社区PR审查,才真正明白:代码风格不是面子工程,是生存法则。飞控代码一旦出bug,轻则炸机,重则伤人。没有统一的规范,团队协作就是灾难。

这一章,我把ArduPilot的编码规矩掰开揉碎了讲。你照着做,PR被合并的概率至少翻一倍。

核心原则:ArduPilot的代码风格遵循 AP_GNU 标准,本质上是GNU风格的变体。缩进2空格,行宽不超过120字符。别问为什么不用4空格——问就是历史原因,但你必须遵守。

4.1 命名约定:一眼看懂变量用途

我见过最头疼的代码,是一个变量叫 temp。是温度?临时值?临时温度?你猜。

ArduPilot的命名规则很明确,说白了就是:看名字就知道类型和作用域。

类别 命名规则 示例 说明
全局变量 g. 前缀 g.altitude_max 通过参数树访问
类成员变量 _ 后缀 _throttle_out 私有成员,一目了然
局部变量 小写+下划线 loop_count 不要用匈牙利命名法
常量/宏 全大写+下划线 AP_ARMING_CHECK_ALL 与变量区分开
函数名 小写+下划线 update_airspeed() 动词开头,描述行为
类名 帕斯卡命名 AP_InertialSensor 首字母大写,模块前缀

我的习惯:写新代码前,先翻翻同目录下已有的文件。ArduPilot的命名风格非常一致,你照着模仿就行。我曾经因为把 _rate 写成了 rate_,被reviewer打回来三次——嗯,从那以后我再也不敢乱放后缀了。

4.2 注释规范:别写废话,要写为什么

你想想看,最没用的注释是什么?

// 设置油门
throttle = 1000;  // 这注释跟没写一样

ArduPilot的注释哲学是:注释应该解释「为什么这么做」,而不是「做了什么」。代码本身已经说明了「做了什么」。

我总结了几条硬规矩:

  • 文件头注释:每个文件必须有GPL许可证声明和简要说明。这是法律要求,不是形式主义。
  • 函数注释:用Doxygen风格,说明参数、返回值、注意事项。特别是那些有副作用的函数。
  • 复杂逻辑注释:如果你花了10分钟才看懂一段代码,那一定要写注释。下一个看代码的人可能就是你(半年后的你)。
  • TODO/FIXME:允许使用,但必须附带你的名字和日期。别留一个「TODO: fix this」就跑路。

避坑指南:我曾经在一个PR里写了这样的注释:// 这里先这样处理,后续再优化。结果reviewer直接回复:「后续是什么时候?请现在优化,或者提issue跟踪。」——从那以后,我学会了要么现在改好,要么提issue并关联到代码里。

4.3 代码审查流程:你的PR是怎么被合并的

很多人觉得代码审查是找茬。其实不是。它是质量门禁,也是知识传递。

ArduPilot的审查流程很成熟。我画了张图,你一看就明白:

ArduPilot PR审查流程 ArduPilot PR 审查流程 提交PR CI自动检查(编译/测试) 通过? 修复后重新提交 人工代码审查 修改/讨论(可能多轮) 合并到主分支 说明:CI失败时PR会被自动标记,修复前无法进入人工审查阶段

流程看着简单,但有几个关键点我要强调:

  1. CI必须全绿:编译警告都不行。ArduPilot的CI会跑硬件在环测试,不是随便跑跑单元测试就完事。
  2. 至少2个Approval:核心模块的改动需要领域维护者批准。别想着找朋友刷通过。
  3. 审查关注点:不只是逻辑正确性,还有代码风格、内存安全、实时性影响。我见过一个PR因为多分配了4字节的堆内存被驳回——在飞控上,每一字节都珍贵。
  4. 响应时间:reviewer给了评论,你最好在48小时内回复。拖太久会被自动标记为「停滞PR」。

我的经验:提交PR前,自己先做一遍「预审查」。把代码打印出来(或者用diff工具),逐行读一遍。我至少有3次在自审时发现了自己都没意识到的bug——比如把 > 写成了 <。这种低级错误,让reviewer看到真的很尴尬。

4.4 编码规范细节:那些容易踩的坑

除了命名和注释,ArduPilot还有一些不成文的规矩。我挑几个重点说:

  • 禁止使用动态内存分配:飞控代码里没有 malloc、new、std::vector(除非在特定非实时路径)。所有内存都在编译期确定。为什么?因为动态分配可能导致碎片化,而飞控不能容忍「某次分配失败」。
  • 使用 AP_Int32 而非 int:参数系统要求所有可调参数使用AP封装类型,保证跨平台大小一致。你在STM32上是4字节,在Pixhawk上也是4字节。
  • 头文件保护用 #pragma once:ArduPilot已经统一用这个了,别再用传统的 #ifndef 方式。简洁,而且不会出错。
  • 禁止使用 using namespace std;:哪怕在cpp文件里也不行。ArduPilot有自己的命名空间 AP,保持隔离。

血的教训:我曾经在一个传感器驱动里用了 std::vector 来缓存数据,想着「就几个元素,没事」。结果在连续运行72小时后,飞控触发了内存分配失败,直接进入紧急降落。从那以后,我所有代码都老老实实用静态数组 + 环形缓冲区。

4.5 工具链:让规范自动执行

人总会犯错。所以ArduPilot用工具来强制执行规范:

工具 作用 使用方式
astyle 自动格式化代码(缩进、括号位置等) make format
cppcheck 静态分析,检测潜在bug CI中自动运行
clang-tidy 更深入的代码质量检查 可选,但推荐本地运行
git pre-commit hook 提交前自动检查格式 社区提供的脚本

我个人习惯是:每次提交前跑一遍 make format。这样reviewer看到的diff里只有逻辑改动,没有格式噪音。你想想看,如果reviewer看到100行改动里有80行是重新格式化,他得多烦躁?

一句话总结:代码规范不是束缚,是保护。它保护你不在凌晨三点调试一个因为命名混淆导致的bug,也保护整个社区能持续维护这套几十万行的代码库。