4、代码审查与合并:Pull Request流程、Code Review最佳实践、嵌入式代码静态检查集成、合并策略选择

代码审查和合并,说白了就是团队协作的「质检站」。嵌入式开发不像写个Web页面,改错了顶多页面崩了。嵌入式代码出问题,轻则设备死机,重则产线停摆。所以这个环节,我特别看重。

今天咱们就聊聊,怎么把Pull Request、Code Review、静态检查、合并策略这些事儿,在嵌入式CI/CD流水线里串起来。

4.1 Pull Request流程:从提交到合并的「高速公路」

Pull Request(简称PR)是GitHub/GitLab上的一个协作机制。但很多团队把它用成了「发邮件」——开发者提交PR,然后等管理员合并。这其实浪费了PR的真正价值。

我个人习惯的PR流程是这样的:

  1. 创建功能分支:从主分支(比如main或develop)拉出一个新分支,命名规则用 feature/xxx 或 fix/xxx。
  2. 提交代码:在本地开发,提交时写清楚commit message。我见过有人写「fix bug」,这等于没写。要写「fix: 修复UART接收缓冲区溢出导致丢包的问题」。
  3. 推送并创建PR:推送到远程仓库,然后创建PR。PR标题要概括改动内容,描述里写清楚「为什么改」和「怎么改」。
  4. 触发CI流水线:PR创建后,自动触发静态检查、编译、单元测试。这些没过,PR不能合并。
  5. Code Review:至少两个Reviewer审核通过。
  6. 合并:选择合适策略合并到目标分支。

关键点:PR的粒度要小。一个PR只做一件事。我曾经接手过一个PR,改了300个文件,修复了5个bug,还加了2个新功能。Reviewer看了三天,最后发现有个bug没修干净。嗯,从那以后我规定:一个PR不超过20个文件,改动不超过500行。

4.2 Code Review最佳实践:嵌入式代码的「体检」

Code Review不是走过场。嵌入式代码的Review,要关注几个特殊点:

4.2.1 关注资源管理

嵌入式系统资源有限。Review时我会特别看:

  • 有没有内存泄漏?malloc/free是否成对出现?
  • 中断服务函数(ISR)里有没有做耗时操作?比如printf、malloc?
  • 全局变量有没有被多个任务同时访问?有没有加锁?

我记得有一次Review,发现有人在ISR里调用了printf。当时我就问:「你知道printf是阻塞的吗?中断里调这个,系统响应延迟直接翻倍。」后来改成了用环形缓冲区记录日志。

4.2.2 检查边界条件

嵌入式代码最容易出问题的地方,就是边界。Review时我会问:

  • 数组访问有没有越界?
  • 定时器溢出后,计数逻辑还正确吗?
  • ADC采样值超过范围时,有没有做限幅处理?

小技巧:Review时,可以专门列一个「边界条件检查清单」。每次Review都过一遍。我团队里就有一份这样的清单,贴在墙上。新来的同事看了都说好。

4.2.3 代码风格一致性

嵌入式团队通常有编码规范。比如变量命名用匈牙利命名法还是下划线命名法?缩进用空格还是Tab?这些看似小事,但代码风格不统一,阅读成本会很高。

我建议用工具自动检查代码风格,比如clang-format。Review时就不需要人工纠结这些了,把精力放在逻辑上。

4.3 嵌入式代码静态检查集成

静态检查是Code Review的「前置过滤器」。很多低级错误,静态检查就能发现,不需要浪费Reviewer的时间。

4.3.1 常用静态检查工具

工具 适用语言 主要功能
cppcheck C/C++ 检测内存泄漏、空指针、未初始化变量等
clang-tidy C/C++ 代码风格检查、潜在bug检测
Coverity C/C++、Java等 商业工具,深度静态分析
PC-lint C/C++ 老牌工具,规则非常细致
MISRA Checker C/C++ 检查是否符合MISRA编码规范(汽车、工业常用)

我个人在项目中常用cppcheck + clang-tidy的组合。开源、免费、够用。如果是汽车电子项目,MISRA检查是必须的。

4.3.2 集成到CI流水线

静态检查要集成到CI流水线中,在PR阶段自动运行。具体做法:

  1. 在CI配置文件中,添加静态检查步骤。
  2. 设置检查规则。比如cppcheck的规则可以写在cppcheck-suppressions.txt里。
  3. 检查结果以注释形式反馈到PR上。GitHub Actions或GitLab CI都可以做到。
  4. 设置质量门禁:静态检查不通过,PR不能合并。

注意:静态检查的规则不要一开始就设得太严。我曾经在一个项目里,把MISRA的所有规则都开了,结果第一次跑出来2000多个警告。团队直接崩溃了。建议先开核心规则,比如内存安全、空指针检查。等团队适应了,再逐步增加。

4.3.3 嵌入式代码的特殊检查项

除了通用静态检查,嵌入式代码还要关注:

  • 中断嵌套:检查中断优先级设置是否合理。
  • volatile使用:访问硬件寄存器或共享变量时,有没有加volatile?
  • 位操作:位域操作有没有考虑字节序?
  • 堆栈使用:有没有工具检查堆栈溢出风险?

嗯,这里要注意:静态检查工具不是万能的。它只能发现「已知模式」的问题。有些逻辑错误,比如算法实现错了,静态检查是发现不了的。所以静态检查是辅助,不能替代人工Review。

4.4 合并策略选择:哪种方式适合嵌入式项目?

合并策略,说白了就是「怎么把分支的改动合到主分支」。Git支持多种合并策略,但嵌入式项目有它的特殊性。

4.4.1 三种常见合并策略

策略 命令 特点
Merge Commit git merge --no-ff 保留完整分支历史,有合并节点
Squash Merge git merge --squash 将分支所有commit压缩成一个,然后合并
Rebase Merge git rebase + git merge 将分支的commit重新放在主分支最新commit之后,然后快进合并

4.4.2 嵌入式项目推荐策略

我个人推荐嵌入式项目使用 Merge Commit(--no-ff)。为什么?

  • 嵌入式项目通常有严格的版本管理。每个版本对应一个commit或tag。Merge Commit保留了分支的完整历史,方便回溯。
  • 如果用了Squash Merge,分支上的所有commit被压缩成一个。万一某个中间commit有问题,你很难定位。
  • Rebase Merge虽然历史干净,但会改写commit历史。如果多人协作同一个分支,rebase会导致混乱。

你想想看,嵌入式项目经常需要「回滚到某个版本」。如果历史被压缩或改写了,回滚时可能找不到准确的点。所以,保留完整历史更安全。

我的建议

  • 主分支(main/release):用Merge Commit,保留完整历史。
  • 开发分支(develop):可以用Squash Merge,保持历史简洁。
  • 功能分支(feature/*):随便用,反正最后要合并到开发分支。

4.4.3 合并前的检查清单

合并之前,我习惯过一遍这个清单:

  1. PR是否通过了所有CI检查?包括编译、静态检查、单元测试。
  2. 是否有至少两个Reviewer批准?
  3. 代码改动是否覆盖了所有边界条件?
  4. 是否有新增的全局变量或静态变量?如果有,是否合理?
  5. 是否有修改了硬件相关的代码?比如GPIO配置、时钟设置。这些改动要特别小心。

我曾经有一次合并前没检查硬件配置,结果合进去之后,整个板子起不来了。后来发现是有人改了时钟分频系数,但没更新对应的延时函数。嗯,从那以后,硬件相关代码的改动,必须由硬件工程师Review。

4.5 总结

代码审查与合并,是嵌入式CI/CD流水线中「承上启下」的一环。它连接了开发阶段和发布阶段。做得好,团队协作顺畅,代码质量有保障。做得不好,流水线就成了摆设。

我个人觉得,最重要的不是工具或流程,而是「人」。Reviewer要认真看,开发者要虚心接受意见。工具只是辅助,真正把好质量关的,还是团队里的每一个人。

好了,这一章就到这里。下一章咱们聊聊「自动化测试与固件发布」,到时候会讲到嵌入式单元测试怎么写、固件怎么签名、怎么发布到OTA服务器。敬请期待。