
1. 评审找什么
评审机器人 C++ 时我优先找「会在 field 炸但 lab 偶发」的类:异步回调里的生命周期、数值 silently wrong、并发默认「应该没事」。下面十条是真实 PR 里反复出现的坑——不是万能清单,是高频拦截项。
PR 模板加「并发/所有权/数值」三问即可,不必长篇 checkbox。代码 review 不是挑风格——是在合并前把 field 概率从 1% 压到 0.01%。blocking issue 用 sanitizer 或 stress 复现链接,不靠 reviewer 肉眼猜。上次 Nav2 costmap 与 planner 死锁,lab 里跑了两个月没复现,TSan 十分钟 bag replay 就抓到——这类 bug 靠 review 问「能不能 copy snapshot 出锁」拦截。
2. 生命周期与所有权
create_subscription lambda 捕获 this 或 [&],节点 destroy 后仍可能被 executor 调用→ UAF。用 weak_ptr 或 LifecycleNode 状态门控。析构里禁止阻塞:~LidarDriver() { join_thread(); } 若 join 阻塞 2s,谁保证 shutdown 顺序?
更稳的是 stop() + 超时,~T() 只 assert 已停。Nav2 lifecycle deactivate 路径同理。传感器 callback 里 new Frame 交给 queue 而不 unique_ptr,是 CR 直接拒的 pattern。允许 T* 仅当 non-owning view,且生命周期文档写清。裸指针 ownership 问三个问题:谁 new?谁 delete?异常路径删了吗?
3. 数值与浮点
if (dist == 0) 改 epsilon 或 squared norm 比较;角度用 std::abs(normalize(q1)-normalize(q2))。Eigen::Vector3d a = b + c; 若 a 与 b 共享存储,结果错。review 看 .noalias() 与 in-place 更新。
EKF 更新后 if (pose.hasNaN()) 必须在 debug build 断言、release 打结构化日志并 safe-stop。review checklist 固定一项:「状态量有没有 isnan/isfinite 闸口」。int ms = angle * 1000 若 angle 超 int 范围——用 int64_t 或 clamp。浮点相等比较是 field 上 silently wrong 的高频来源,lab 里测不出因为测试数据太干净。
4. 并发与锁
两 mutex 不同顺序加锁→死锁。规定全局 lock order:sensor_buf < state_estimator < logger。mutex 顺序与文档 hierarchy 不一致——要求改或画注释。
rclcpp callback 里 lock_guard 持有 map_mu_ 再 call publish,容易与另一个 subscription 回调逆序抢锁。review 问:能不能 copy snapshot 出锁再 publish?Nav2 costmap update 与 planner 查询的经典 deadlock 就是这类 pattern 变体。
热路径 std::vector push 每帧扩容——改 ring buffer 或 reserve 一次。热路径新增 shared_ptr 按值——问能否 move。RCLCPP_INFO 在 30Hz callback——改 Throttle。每帧 alloc 在 30Hz 控制环里会触发 GC 式延迟尖峰,review 时必问。
5. 协议、错误码与 API 面
read() 返回值未查,partial packet 当完整帧 decode——协议层要有长度校验。默认构造函数漏 init,release 下随机值——-Wuninitialized 或 value-init {}。
公开头出现 std::map 成员→插件 ABI 风险,改 Pimpl。公开头新 include 第三方——问 Pimpl。review 时搜 reinterpret_cast 与 const_cast:前者偶有必要,后者几乎总是 red flag。再搜 detach() 与裸 new,机器人 repo 里应接近零。协议层长度校验缺失是 sensor driver 里 field 崩溃的高频来源。
// red flag
void cb(const sensor_msgs::msg::LaserScan msg); // copy
// prefer
void cb(const sensor_msgs::msg::LaserScan& msg);6. 测试与 CI
mock 时间/随机未固定,CI flake——seed 写死,steady_clock inject。sanitizer job 未跑就标 ready——等 CI。PR 勾 sanitizer 附件。参数 declare_parameter 默认值与 YAML 不一致——要求单源。TODO 不带 ticket 号——打回。
7. 案例:costmap 与 planner 死锁
Nav2 costmap update 回调持有 map_mu_ 再 call planner 查询,planner 回调又持有 plan_mu_ 再读 costmap——逆序加锁死锁。lab 里偶发(时序依赖),field 上高负载必现。改成 copy snapshot 出锁再 publish/query 后 TSan 绿。这类 bug review 时问「能不能 copy snapshot 出锁」就能拦截。死锁是机器人 repo 里 review 最高优先级拦截项之一。review 时搜 detach/裸 new/const_cast,NaN 闸口必查,热路径 alloc 必问。
8. 验收
新 thread 必须命名 pthread_setname_np 方便 perf/top。driver 改 DMA buffer size 要跑 overnight leak check。review 见 Eigen::Matrix4d 按值进 callback 直接 comment:改 const ref 或 shared_ptr<const>。Throttled 宏没写 clock 源的一律改。CR 搜 detach/裸 new/const_cast,NaN 闸口必查。
9. 评审节奏:blocking 与 non-blocking
blocking issue:UAF 风险、数据竞争、NaN 无闸口、公开头泄漏 STL——必须修完再 merge。non-blocking:命名风格、注释措辞——可以 follow-up。PR 模板三问(并发/所有权/数值)足以拦截 80% 的 field bug,不必搞二十项 checkbox。sanitizer 或 stress 复现链接比 reviewer 口头「我觉得没事」可靠——CI 绿了再标 ready。代码 review 的目标是把 field 概率从 1% 压到 0.01%,不是挑风格。blocking issue 必须修完再 merge,sanitizer 或 stress 复现链接比 reviewer 口头判断可靠。PR 勾 sanitizer 附件,未跑就标 ready 一律打回。新 thread 必须命名方便 perf/top,driver 改 buffer size 要跑 overnight leak check。公开头新 OpenCV include 直接 request changes,参数默认值与 YAML 不一致要求单源。TODO 不带 ticket 号一律打回。
相关
也可以看看
johan's blog