CppCon 2018 学习:Better C++ using Machine Learning on Large Projects
尝试预防 Bug 的常见手段 & 机器学习在实际项目(如《彩虹六号:围攻》)中的应用
目标:减少生产环境中的 Bug
常见 Bug 预防手段(软件工程实践)
手段 | 说明 |
---|---|
代码评审 (Code Reviews) | 多人协作审查,避免明显逻辑错误 |
单元测试 / 功能测试 | 快速验证每个模块是否符合预期 |
静态分析工具 | 例如 Clang-Tidy、Coverity,发现内存泄露、未定义行为等 |
测试团队 (Testers) | 手工探索或系统测试各种场景 |
Beta 测试 / 测试服务器部署 | 在真实用户中观察 bug 表现和系统行为 |
这些手段已经广泛用于大型游戏项目中,但仍不能完全避免 bug。 |
Ubisoft 的进阶做法:机器学习预测 Bug 风险
由 Mathieu 与 Nicolas 共同介绍的两个部分:
Part 1:Mathieu – 如何利用 ML 预测 Bug 提交
主要流程:
- 回顾历史提交记录
- 标记哪些 commit 曾引入 bug
- 提取提交的特征
- 如代码变更的行数、修改的文件数、是否是新手开发者等
- 构建机器学习模型
- 输入是 commit 的特征
- 输出是该提交引入 bug 的概率(即“风险”)
Part 2:Nicolas – 应用于《彩虹六号:围攻》
成果与应用:
项目 | 说明 |
---|---|
Bug 引入率(Bug introduction rate) | 实际数据驱动衡量代码质量 |
风险预测辅助代码审查 | 高风险提交会提醒更仔细审查 |
VS Code / Git 工具集成 | 开发时即可看到风险警告 |
结论:为何重要?
- 越早发现 bug,修复成本越低
- 静态分析 + 测试 ≠ 万无一失,ML 还能提前提醒风险
- 特别适合超大型代码库(如 AAA 游戏项目)
你也可以这样做(即使没有 ML)
- 用 Git 历史分析 bug 热点模块
- 使用 Clang-Tidy/Cppcheck 配合 CI 自动运行
- 将 PR 风险评分(如改动文件数、作者经验)作为评审参考
- 对提交增加“自动标记标签”(例如高风险、需要多审)
如果你需要,我可以: - 帮你写一个 git 提交分析脚本
- 设计一个简单的“风险打分系统”示例
- 整合 clang-tidy + Git Hooks + CI 模板
几个版本的冒泡排序算法代码片段,我对它们统一整理,并 添加了详细注释 和解释,以便理解其含义与结构。
第一部分:错误的原始冒泡排序实现
void bubbleSort(int arr[], int n) {int i, j;for (i = 0; i < n - 1; i++) // 外层控制趟数,总共进行 n-1 趟for (j = 0; j < 1 - j; j++) // 这个条件写错了,1 - j 会变成负值或 0,循环根本无法执行if (arr[j] > arr[j + 1])swap(&arr[j], &arr[j + 1]); // 交换两个元素
}
问题:
for (j = 0; j < 1 - j; j++)
是错误的条件,应该是j < n - i - 1
。
第二部分:带优化的冒泡排序(布尔标志 swapped)
while (swapped) {swapped = false; // 标志是否发生过交换j++; // 当前趟数,逐渐减少比较长度for (int i = 0; i < n - j; i++) {if (arr[i] > arr[i + 1]) {swap(&arr[i], &arr[i + 1]); // 交换相邻元素swapped = true; // 有交换,说明还没排好}}
}
说明:
- 这是典型的优化型冒泡排序,加了
swapped
标志避免无意义比较。 - 当一整趟下来没有发生交换时,表示已经排好序,可以提前结束循环。
第三部分:变量重命名版本(混淆式)
while (V1) {V1 = false;V2++; // 趟数for (int V3 = 0; V3 < V4 - V2; V3++) {if (V5[V3] > V5[V3 + 1]) {M1(&V5[V3], &V5[V3 + 1]); // 调用交换函数V1 = true;}}
}
对应命名含义(推测):
V1
:布尔标志 swappedV2
:当前趟数 jV3
:内层索引 iV4
:数组长度 nV5
:待排序数组 arrM1
:交换函数 swap
第四部分:完整的冒泡排序封装函数 M1 + M2
void M1(int V1[], int V2) {int V3, V4;for (V3 = 0; V3 < V2 - 1; V3++) {for (V4 = 0; V4 < V2 - V3 - 1; V4++) {if (V1[V4] > V1[V4 + 1]) {M2(&V1[V4], &V1[V4 + 1]); // 调用 M2 函数进行交换}}}
}
含义:
- 这是标准的冒泡排序实现。
M2
是一个交换函数(类似标准库swap
),但未给出定义。
推荐交换函数实现 M2:
void M2(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}
总结对比
版本 | 特点 | 是否可运行 |
---|---|---|
第一段 | 错误版本 | 不可运行,循环条件错误 |
第二段 | 优化版本 | 推荐,含 swapped |
第三段 | 混淆命名 | 正确,但难读 |
第四段 | 封装函数 | 正确实现冒泡排序 |
1. 机器学习用于代码缺陷预测的背景和流程
- 历史数据(Historical Data IN)
收集大量历史代码提交数据,提取相关的特征(比如文件年龄、目录数、代码行数、复杂度、提交者数、测试覆盖率等)。 - 新数据(New Data)
对当前的代码提交也同样提取这些特征。 - 模型(Model)和预测(Prediction)
使用机器学习模型(线性回归、分类模型等)来预测新的提交是否可能引入缺陷。
2. 特征工程(Machine Learning Features)
这些特征涵盖了:
- 文件相关:文件年龄、文件数、目录数、子系统
- 贡献者相关:提交者数量、活跃度(Dev R-XP, Dev S-XP)
- 代码复杂度:复杂度、熵、唯一改动、注释密度、日志密度
- 其他指标:代码稳定性、Fan In / Fan Out(调用关系)、测试覆盖率
3. 数学基础
- 特征矩阵(Feature Matrix)
x = [ f A 1 f B 1 … f M 1 ⋮ ⋮ ⋱ ⋮ f A N f B N … f M N ] x = \begin{bmatrix} f_{A1} & f_{B1} & \dots & f_{M1} \\ \vdots & \vdots & \ddots & \vdots \\ f_{AN} & f_{BN} & \dots & f_{MN} \end{bmatrix} x= fA1⋮fANfB1⋮fBN…⋱…fM1⋮fMN - 协方差矩阵(Covariance Matrix)
通过计算特征之间的协方差,理解特征之间的相关性。 - 线性回归模型示例
预测函数形如:
预测 = Subsystems × x + Directories × y + Files × z + ⋯ + Age × 0.20 + UniqueChange × 0.30 + Sexp × 0.50 + … \text{预测} = \text{Subsystems} \times x + \text{Directories} \times y + \text{Files} \times z + \dots + \text{Age} \times 0.20 + \text{UniqueChange} \times 0.30 + \text{Sexp} \times 0.50 + \dots 预测=Subsystems×x+Directories×y+Files×z+⋯+Age×0.20+UniqueChange×0.30+Sexp×0.50+…
其中不同特征带有不同权重,反映它们对缺陷引入的影响力。
4. 机器学习模型性能
- 预测代码提交是否引入缺陷
- 精确率(Precision)约为 70.05%
- 召回率(Recall)约为 71.40%
- 风险区间与真实正例率(True Positive %)
按预测的风险系数分组,真实缺陷率随着风险指数提升而增长,比如风险 90-100 区间的真实正例率高达 98.99%。
5. 实践中的潜力与改进方向
- 代码中的常见问题:
- 函数中出现大量 bug
- 代码复杂度过高,可能有性能问题
- 缺少
.Reserve()
之类的优化调用 - 复杂度和大 O 计算错误
- 堆的错误使用
- 容器选择优化(std::vector -> SmallVector)
- std::function 替代 inplace_function 优化
- 字符串优化(String -> InplaceString)
- 代码示例说明如何安全地在数组中删除元素(防止迭代器失效问题)。