前一段时间咱们盘点了C语言中那些常见的坑,相信大家伙在日常的工作中都是:项目进度催催催,Bug改改改,头发掉掉掉…,好不容易写完的代码,质量却总是不尽如人意,各种隐患,各种“坑”,简直让人心力交瘁!你是不是也经常感觉,想要提升软件质量,却总是“心有余而力不足”?。今天,我就要给大家带来5个“锦囊妙计”!让你如何防患于未然,让你的代码质量“芝麻开花节节高”。

1. 编译器 Warning,一个都不能放过!

不知你是否有类似经历?某位同事嘴硬说"这些警告不影响功能,忽略就好"?其实编译器Warning绝对不是“狼来了”的故事!它是在认真地告诉你:“老铁,你的代码有点问题,虽然我能编译过,但可能会有坑哦!”现在没问题,指不定啥时候就爆发成大 Bug 了!所以,把它们当成 Bug 一样,认真对待,逐一消灭!

怎么做呢?很简单!

开启所用编译器的警告选项(例如 GCC 的 -Wall -Wextra -Werror,Clang 的 -Weverything)。现代IDE(Keil MDK, IAR Embedded Workbench, Eclipse + GCC 等)都提供了丰富的警告选项,可以根据你的需求进行配置,而且都可以方便地查看和管理 Warning 信息。善用IDE的提示功能,效率倍增!

温馨提示: 有些历史遗留的Warning可能比较难改,可以先放一放,集中精力搞定新增代码的Warning。但最终目标是零 Warning,零 Bug!(理想状态)

2.“静态分析” 黑科技加持!让 Bug 无处遁形!

搞定了编译器警告,代码质量就“万事大吉”了吗?Naive!编译器毕竟只能做一些“表面功夫”,有些隐藏得更深的bug,它就“鞭长莫及”了。这时候,就该 静态分析工具 上场了。那么静态分析工具都能干啥呢?

  • 更深入的Bug检测: 能发现编译器发现不了的Bug,例如空指针解引用、内存泄漏、越界访问、除零错误、未使用变量等等。防患于未然,避免运行时踩坑!
  • 代码风格检查: 可以强制执行统一的代码风格,避免团队成员代码风格迥异,提高代码可读性。强迫症福音!
  • 安全漏洞扫描: 对于有安全要求的项目,静态分析工具可以扫描代码中潜在的安全漏洞,例如缓冲区溢出等等。而且,很多静态分析工具还能帮你检查代码是否符合MISRA C、AUTOSAR C++14 等编码规范,提升代码的规范性和安全性。

你是不是觉得静态分析工具都是“高大上”的商业软件,价格昂贵?其实现在有很多免费又好用的静态分析工具,下面给大家整理了国内外主流工具及特点对比(评论区分享你觉得好用的静态分析工具

工具名称 官网 适用场景 优势 劣势 价格
Clang Static Analyzer https://clang-analyzer.llvm.org/ C/C++ 项目,特别是嵌入式开发 基于 LLVM,集成度高,误报率低,功能强大 配置略复杂 免费开源
clang-tidy http://clang.llvm.org/extra/clang-tidy/ C/C++ 项目,代码风格和现代 C++ 特性检查 代码风格检查,现代 C++ 特性检查,与 Clang 集成 可能需要根据项目具体情况进行配置 免费开源
Cppcheck http://cppcheck.sourceforge.net/ C/C++ 项目,快速代码检查,资源受限的嵌入式系统 轻量级,速度快,误报率较低,易于使用 功能相对简单 免费开源
PVS-Studio https://pvs-studio.com/en/pvs-studio/ C/C++/C# 项目,Bug 检测 商业级,功能强大,报告详细,易于使用,有免费版(个人和开源) 商业版较贵 免费版/商业版
Coverity https://scan.coverity.com/ 企业级大型项目,高精度代码分析 业界标杆,功能全面,精度高,误报率极低,支持 CI 价格昂贵 订阅制,较贵
Parasoft C/C++test https://github.com/Tencent/TscanCode 汽车/航空电子等,支持 MISRA/AUTOSAR 等规范 功能全面,支持多种编码规范检查, 自动化测试 部署复杂,价格高 高价
CodeSonar https://www.grammatech.com/products/codesonar 安全性要求高的项目,如汽车电子、航空航天 专注于安全性分析, 深入的静态分析和污点分析 较复杂 商业版
SonarQube (社区版) https://www.sonarqube.org/ 通用代码质量管理,支持多种语言,包括 C/C++ 代码质量的全面分析和管理,社区版免费, 插件丰富, 支持CI/CD 对于单一静态分析需求,可能过于庞大 社区版免费
PRQA QA-C/++ https://www.perforce.com/products/qac 安全关键领域,如汽车电子、医疗器械 符合 MISRA、ISO 26262 等标准, 历史悠久, 行业认可度高 相对较老, 界面可能不够现代化 商业版
PC-Lint/FlexeLint https://www.vector.com/cn/zh/ 传统嵌入式C/C++ 规则全面,检查严格,历史悠久 配置较繁琐, 界面过时 商业版
TscanCode https://github.com/Tencent/TscanCode C/C++、Lua等项目 中文支持好,上手快, 腾讯出品, 支持多种语言和编码规范 规则不如国际工具全面 开源免费
Klocwork https://www.perforce.com/products/klocwork 企业级嵌入式系统 支持安全标准检查, 支持增量分析,适合大型项目 资源消耗大, 价格较高 订阅制
  • IDE 集成插件: 很多 IDE,例如 VS Code, Eclipse, Keil MDK, IAR EWARM 等,都有静态分析插件,可以直接在 IDE 中进行静态分析,非常方便!例如:
    • VS Code: 可以安装 C/C++ Extension,自带 Clang-Tidy 支持,还可以安装 SonarLint 等插件。
    • Eclipse: 可以安装 CDT 插件,集成 Clang Static Analyzer 和 Cppcheck。
    • Keil MDK, IAR EWARM: 可以安装相应的静态分析插件,例如 ARM Compiler toolchain 自带的 Arm Compiler Static Analysis。

使用建议:

  • 循序渐进,逐步引入: 一下子用太多规则可能会让人懵逼,可以先从默认规则开始,逐步添加更严格的规则。
  • 集成到CI/CD流程: 将静态分析集成到持续集成/持续交付流程中,每次代码提交都自动进行静态分析,尽早发现问题。自动化才是王道!
  • 关注高优先级告警: 静态分析工具可能会报出很多告警,优先关注高优先级的告警,例如错误和潜在Bug。逐步解决,积少成多!

3. 代码复杂度,化繁为简是王道!

代码写久了,难免会遇到一些“巨无霸函数”——它们动辄几百行上千行,逻辑复杂,嵌套N层,可读性差到极点,自己看着都头疼,更别说别人维护了!“巨无霸函数”是代码质量的“毒瘤”,必须尽早“铲除”!如何找到这些“毒瘤”呢?秘密武器就是 “代码复杂度度量尺”(Code Complexity Metrics)

如何度量代码复杂度?常用指标了解一下:

  • 代码行数 (Lines of Code, LOC):最简单粗暴的指标,代码行数越多,复杂度往往越高。
  • McCabe 循环复杂度 (McCabe Cyclomatic Complexity, MCC) :基于代码的 控制流图 计算复杂度, 分支越多,复杂度越高
  • 严格循环复杂度 (Strict Cyclomatic Complexity, SCC) :在MCC的基础上,考虑了 条件表达式的复杂度 ,更精细地衡量复杂度。

对于MCU项目,建议将复杂度阈值设定为:

  • 函数行数<100行
  • 圈复杂度<15
  • 嵌套深度<4层

国内外常用复杂度分析工具推荐:

工具名称 官网 适用场景 优势 劣势 价格
Lizard https://github.com/terryyin/lizard 多语言项目 (C/C++, Java, Python, C# 等),快速复杂度分析 轻量级,支持多种语言,安装使用简单 (pip install lizard),输出结果清晰,可计算 LOC、圈复杂度等指标 功能相对基础 免费开源
cccc (C and C++ Code Counter) http://sarnold.github.io/cccc/ C/C++ 项目,详细复杂度报告 专门针对 C/C++,功能强大,可生成各种详细的报告,包括函数级别的复杂度信息 界面较为陈旧, 可能不支持最新的 C++ 标准 免费开源
SourceMeter https://www.sourcemeter.com/ 商业级项目,全面的代码复杂度分析和趋势分析 商业级,功能非常全面,可生成各种图表和趋势分析报告,指标丰富 收费 商业版
SonarQube (社区版) https://www.sonarqube.org/ 通用代码质量管理,也包括复杂度分析 代码质量的全面分析和管理平台 (包括复杂度、风格、Bug 等),社区版免费,插件丰富,支持 CI/CD 对于单一复杂度分析需求,可能过于庞大 社区版免费
Source Insight https://www.sourceinsight.com/ 代码阅读和理解,辅助进行复杂度分析 强大的代码阅读和导航功能,也提供了一些简单的复杂度分析功能 主要功能是代码阅读,复杂度分析功能较弱 商业版
Source Monitor https://www.campwoodsw.com/ Windows 平台,C/C++, C#, Java, VB.NET 等语言项目 Windows 平台友好,图形界面直观,操作简单 仅限 Windows 平台 免费
Understand https://www.scitools.com/ 大型项目,全面的代码分析 功能全面的代码分析工具,支持多种语言,提供丰富的分析指标和可视化 价格较高 商业版
Visual Studio 代码度量功能 (内置于 Visual Studio) Visual Studio 用户,C/C++, C# 等项目 VS 用户即插即用,方便快捷 功能相对 Visual Studio 的专业代码分析工具较弱 包含在 VS 中
Eclipse Metrics 插件 (Eclipse Marketplace 中搜索 “Metrics”) Eclipse IDE 用户 适合 Eclipse IDE 用户,方便集成 功能依赖于具体插件 通常免费
IDEA Metrics 工具 (内置于 IntelliJ IDEA) IntelliJ IDEA 用户 IDEA 自带,方便使用 功能依赖于 IDEA 版本 包含在 IDEA 中
VS Code CodeMetrics 插件 (VS Code Marketplace 中搜索 “CodeMetrics”) VS Code 用户 方便在 VS Code 中实时查看代码复杂度指标 功能依赖于具体插件 通常免费
SLOCCount https://dwheeler.com/sloccount/ 多种语言,统计代码行 可估计软件项目大小和所需工作量 仅关注代码行数,结果可能不精确 免费开源

*如何降低代码复杂度?

  • 函数职责单一化: 一个函数只做一件事!避免“万能函数”,把复杂函数拆分成多个小函数,提高代码可读性和可维护性。模块化设计YYDS!
  • 减少代码分支: 合理使用设计模式、查表法等技巧,减少if/else,switch-case等分支语句的嵌套层数。代码逻辑更清晰!
  • 提取公共代码: 将重复的代码提取成公共函数或模块,避免代码冗余,降低代码复杂度。DRY(Don’t Repeat Yourself)原则要牢记!
  • 代码重构: 定期进行代码重构,优化代码结构,消除代码坏味道,降低代码复杂度。代码也需要定期“体检”!
  • 代码审查: 代码审查是发现和降低代码复杂度最有效的方法之一!让同事帮你“把把脉”,看看你的代码是不是“太复杂”了!
  • 设计模式 合理运用设计模式,可以提高代码的可读性、可维护性,降低代码复杂度。例如,策略模式、工厂模式、观察者模式等等。

4. 代码格式化,强迫症的福音!

代码风格不统一,简直是代码洁癖患者的噩梦!团队协作开发,你用Tab,他用空格;你用驼峰命名,他用下划线;你{另起一行,他{紧跟在后面;代码review的时候,光是代码风格就吵个不停,简直浪费生命!那么,如何统一代码风格呢?答案就是:“自动化代码格式化”!配置好规则,一键格式化,代码瞬间变得整整齐齐,赏心悦目!

针对不同嵌入式开发环境的格式化工具

  • clang-format: LLVM 官方出品,功能强大,支持C/C++/Objective-C/Java/JavaScript/TypeScript等多种语言。配置灵活,格式化效果出色。嵌入式 C/C++ 项目首选!
  • Uncrustify: 开源的代码格式化工具,支持 C, C++, C#, Java, Objective-C, Pawn, Vala 等语言。配置选项非常丰富,可以满足各种奇葩的代码风格需求。
  • Astyle (Artistic Style): 开源的 C/C++/C#/Java 代码格式化工具,历史悠久,使用广泛。特点是格式化风格比较“艺术化”,代码看起来更美观(但可能有人觉得过度格式化)。
  • EditorConfig: 如果你觉得代码格式化工具配置太复杂,可以使用 EditorConfig 来统一团队的代码风格。 EditorConfig 是一个简单的配置文件,可以定义缩进风格、行尾符、字符集等基本代码风格,支持多种编辑器和IDE。

IDE 集成: 很多 IDE (例如 VS Code, CLion, Source Insight 等)都内置了代码格式化功能,或者可以通过插件集成 clang-format, Uncrustify 等工具。配置好 IDE 的自动格式化功能,写代码的时候就不用操心格式问题了!

如何用好代码格式化工具?

  1. 团队统一代码风格规范: 在开始项目之前,团队成员一起讨论,制定统一的代码风格规范。可以参考一些流行的代码风格指南,例如 Google C++ Style Guide, LLVM Coding Standards 等。
  2. 选择合适的代码格式化工具: 根据项目语言、团队习惯等因素,选择合适的代码格式化工具。ClangFormat, Uncrustify, AStyle 都是不错的选择。
  3. 配置代码格式化工具: 根据团队的代码风格规范,配置代码格式化工具的参数。确保格式化后的代码符合团队规范。
  4. 集成到开发流程中: 将代码格式化工具集成到IDE 中、代码提交 Hook 中、CI/CD 流程中让代码格式化自动化! 例如:
  5. 代码提交 Hook: 使用 Git Hooks,在代码提交前自动运行代码格式化工具,确保提交的代码都是格式化过的。
  6. CI/CD 流程: 在 CI/CD 流程中,加入代码格式化检查步骤,如果代码风格不符合规范, 就拒绝构建。

第五招: Code Review,集思广益!三人行,必有我师!

“代码审查?感觉好麻烦,浪费时间,还不如自己多测几遍!” 如果你是这么想的,容易陷入“当局者迷”的困境。因为再牛逼的程序员,也难免有疏忽的时候。

如何进行代码审查?

  1. 小步快跑: 代码审查要小批量、频繁进行 !每次代码审查的代码量不要太大,例如每次审查几十行到一两百行 代码即可。这样可以提高审查效率,减少审查负担。
  2. Code Review Tool! 使用代码审查工具,例如GitLab Merge Request, GitHub Pull Request, Gerrit, Review Board 等。这些工具可以简化代码审查流程 ,提高审查效率。
  3. Review Checklist: 制定一份简单的代码审查 Checklist ,例如:
    • 代码逻辑是否正确?
    • 是否有潜在的 Bug?
    • 代码是否易读易懂?
    • 代码风格是否符合规范?
    • 是否有重复代码?
    • 代码注释是否清晰完整?
    • … 等等。
      审查者可以对照 Checklist 进行审查,避免遗漏重要问题。
  4. 建设性反馈: 代码审查的重点是“改进代码质量” ,而不是“挑刺”或者“批斗大会”! Reviewer 要给出建设性的反馈意见 ,指出代码中存在的问题,并给出改进建议 。作者要虚心接受 Reviewer 的意见,及时修改代码。互相尊重,共同进步 才是代码审查的“正确姿势”!记住,目标是提升代码质量,而不是证明谁比谁更牛!
  5. 选择合适的 Reviewer: 代码审查要选择合适的 Reviewer 。 Reviewer 最好是对代码模块比较熟悉,或者在相关领域有经验的同事 。如果团队规模较小,可以进行交叉审查 ,让不同模块的同事互相审查代码,这样可以促进知识共享,也能发现一些意想不到的问题。
  6. 定期回顾和改进: 代码审查流程不是一成不变的,要定期回顾代码审查的效果收集团队成员的反馈意见不断改进代码审查流程 ,使其更高效、更实用。就像“升级打怪”一样,代码审查流程也要不断“升级”!
  7. 自动化辅助: 结合静态分析工具和代码格式化工具,在 code review 之前先用工具进行初步检查,减少 review 的工作量,提高 review 效率。事半功倍,何乐而不为?

对于嵌入式团队,特别要关注:

  • 中断处理函数
  • 内存分配与释放
  • 硬件访问逻辑
  • 时序关键代码

这里推荐谷歌代码审查标准

总结:告别996,从提升代码质量开始!

今天的分享就到这里!希望对大家有所帮助。记住,“罗马不是一天建成的” 循序渐进,建议先从消除警告开始。然后拥抱自动化,可以把静态分析、代码格式化等工具集成到 CI/CD 流水线中,在代码提交的时候自动运行,有问题及时报警,防患于未然!

FAQ:常见问题

上期回答

Q1:为什么未初始化变量在有些情况下像有“默认值”?
A: 这纯属巧合!C语言并不保证未初始化的局部变量有任何默认值。之所以有时看起来像有默认值,是因为该内存位置恰好保留了之前的数据。这完全依赖于具体实现,绝对不可靠。我曾经因为依赖这种"巧合",在产品上线后遇到了灾难性的随机崩溃问题,调试了整整一周。教训深刻!

Q2:“未定义行为”会导致程序崩溃吗?
A: 可能会,也可能不会——这就是"未定义"的本质!最危险的未定义行为是那些看起来工作正常的,它们可能在特定条件下突然失效,造成安全漏洞、数据损坏或系统不稳定。在我的嵌入式项目中,有一个未定义行为导致的bug在测试环境完全没问题,但在客户现场却导致了随机重启,最终追查到是一个未初始化指针在特定条件下被解引用。

Q3:嵌入式系统内存那么小,“栈”是不是很容易爆掉?
A: 极其重要!嵌入式系统的栈通常只有几KB甚至更少。不理解栈的工作原理,很容易导致栈溢出,这是嵌入式系统崩溃的常见原因之一。我始终记得一个教训:在一个8位MCU项目中,一个递归函数没有适当的终止条件,导致栈溢出并覆盖了关键的系统数据,结果整个设备变成了"砖头"。深入理解栈,对每一个嵌入式开发者都是必修课!

Q4:编译器优化越高越好吗?嵌入式开发应该怎么选优化等级?
A: 绝对不是!优化是把双刃剑。高级优化可以显著提升性能和减小代码体积,但也可能使调试变得困难,甚至改变含有未定义行为的代码的执行结果。在嵌入式开发中,我的经验是:开发阶段使用低优化级别(如-Og、-O0或-O1)以便调试,然后在最终产品中谨慎地尝试更高级的优化(建议使用-Os优化代码大小或-O2平衡性能与大小,同时使用-fno-strict-aliasing等选项避免激进优化导致的异常),同时进行全面测试。记住,在资源受限的嵌入式系统中,代码正确性和可靠性通常比极致性能更重要。

Q5:有没有什么“葵花宝典”,可以避免代码中出现“未定义行为”?
A: 实践经验:

  • 始终初始化所有变量,即使是临时变量
  • 使用静态代码分析工具(如cppcheck或clang-tidy)
  • 开启所有相关的编译器警告(-Wall -Wextra -Werror)并确保零警告编译
  • 进行代码审核,特别关注指针操作和内存管理
  • 遵循安全编码标准(如MISRA C)
  • 对代码进行单元测试和边界测试
  • 持续学习C语言的陷阱和未定义行为

本期问题

1.对于资源受限的小型嵌入式项目,静态分析会不会带来太大开销?
2.国产MCU(如GD32、CH32等)开发环境如何配置这些工具?
3.团队成员不愿意改变既有编码习惯怎么办?
4.如何在紧张的项目进度中挤出时间来实施这些流程?
5.RTOS项目与裸机项目在实施这些流程时有什么不同?


温馨提示: 本文提到的工具和方法,仅供参考。实际应用中,还需要根据项目和团队的具体情况进行调整和选择。如有任何疑问,欢迎在评论区留言讨论。

【订阅公众号获取更多】
公众号名称:初探嵌入式

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐