静态分析不仅仅是直接找出 Bug,还包括识别容易滋生 Bug 的隐患——这些隐患会损害代码的可读性和可维护性。静态分析可以处理代码的许多其他属性:
- 代码度量:例如,包含过多循环、if、else、switch 和 case 语句的方法会变得难以理解,从而难以维护。通过圈复杂度(Cyclomatic Complexity)这一代码度量来统计它们,是评估方法何时变得过于复杂的好方法。
- 依赖关系:如果程序的类相互纠缠,代码中任何修改的影响都会变得不可预测。静态分析可以帮助评估类和组件何时纠缠在一起。
- 不可变性:被多个线程并发使用的类型应该是不可变的;否则您将不得不用复杂的锁策略来保护状态的读写访问,而这些策略最终会变得无法维护。静态分析可以确保某些类保持不可变。
- 死代码:死代码是可以安全移除的代码,因为它在运行时不再被调用。它不仅可以被移除,而且必须被移除,因为这些多余的代码会给程序增加不必要的复杂性。静态分析可以找到程序中大部分死代码(但不是全部)。
- API 破坏性变更:如果您向客户提供 API,很容易在不经意间移除一个公有成员,从而破坏客户的代码。静态分析可以比较程序的两个状态,并对这种陷阱发出警告。
- API 使用:有些 API 需要谨慎使用。例如,持有可释放字段的类通常自身也必须是可释放的,除非该可释放字段的生命周期与类实例的生命周期不一致——而后者本身就散发着设计问题的味道。
有许多实用的工具可以检测 C++ 代码库中的 Bug,如 Cppcheck、Clang 和 Visual Studio 分析器。但识别容易滋生 Bug 的隐患呢?
静态分析工具的开发者可以决定哪些情况被视为 Bug,但容易滋生 Bug 的隐患却不是这样——它们取决于开发团队的选择。例如,一个团队可能认为超过 20 行的方法就算复杂,而另一个团队可能把上限设为 30。如果一个工具要检测易滋生 Bug 的隐患,它就必须提供自定义相关规则的方法。
"代码即数据"是检测易滋生 Bug 隐患的最佳方式
静态分析是指分析源代码的各种属性并报告这些属性,但从哲学上讲,它也意味着将代码视为数据。对于我们这些应用程序开发者来说,这非常奇怪,因为我们早已习惯将源代码看作指令、过程和算法。但它也异常强大。
分析源文件之后,我们可以提取其 AST,并生成一个包含大量有用代码信息的模型。然后,我们可以使用类似 SQL 的代码查询语言来查询这个模型。
CppDepend提供了一种名为 CQLinq 的强大代码查询语言,可以像查询数据库一样查询代码库。开发者、设计师和架构师可以定义自定义查询,轻松识别容易滋生 Bug 的隐患。
借助 CQLinq,我们可以将来自代码度量、依赖关系、API 使用情况和模型的其他信息组合起来,定义出匹配特定易滋生 Bug 隐患的高级查询。
下面是一个匹配最复杂方法的 CQLinq 查询示例:

总结
最好结合多种 C++ 工具来检测代码库中的问题:有些工具检测 Bug,另一些还能检测易滋生 Bug 的隐患。通过 CppDepend,我们旨在结合多种工具的优势:我们提供了一种定义自定义查询的简便方法,最近还新增了导入其他静态分析工具的结果并用 CQLinq 查询它们的功能。
