返回专辑
·Johan·3 分钟阅读

接口设计:单位、演进成本与可机器判断的错误

msg/srv/action 按演进成本定价;单位、坐标与错误码写进契约。

接口设计:单位、演进成本与可机器判断的错误

1. 一个 Float64 话题,十种语义

早期图方便,/target_distancestd_msgs/Float64 广播目标距离。半年后:A 节点发 mm,B 当 m,C 在回调里除以 1000「因为上次那个驱动是这样」。接口没有单位与 frame,破坏性变更被摊进每个订阅者的 if.msg/.srv/.action 改一次,全语言绑定与 bag schema 一起震——接口是架构外骨骼,应按演进成本定价。Code review 看到新裸 Float64 topic 应直接拒,要求结构化 msg。

2. 消息:字段即文档

msg
# TargetPose.msg — 宁可啰嗦,不要口头传统
geometry_msgs/PoseStamped pose
float64 approach_distance_m
uint8 schema_version

原则:单位进字段名_m_rad_s);坐标进 frame_id 或嵌 PoseStamped;状态用 uint8 常量而非自由字符串(字符串留给日志)。大数组问一句:是否应拆 topic 或走零拷贝/loan——把 4K 图像塞进 srv request 通常是错的。

msg
uint8 STATUS_IDLE=0
uint8 STATUS_RUNNING=1
uint8 STATUS_FAULT=2
uint8 status

3. Service 与 Action:错误要可机器读

srv
# Calibrate.srv
string sensor_id
---
bool success
uint16 error_code    # 0=OK, 0x1001=timeout, 0x1002=bad_sample
string message       # 给人看

message 给工程师,error_code 给状态机与重试策略。只有 string 时,客户端只能 substring 匹配「timeout」,国际化后再崩一次。

Action 的 result 同理;cancel 与 feedback 频率要在接口层约定,避免每个实现各发各的。

4. 演进:添字段 < 改语义 < 改类型名

  • 添 optional 字段(ROS 2 IDL 新字段有默认):最便宜,旧 bag 可播
  • 改字段语义(同一 distance_m 从欧氏变沿路径):等价破坏性,应升 schema_version 或新类型名
  • 改类型名 / 包名:最贵,但最干净
bash
ros2 interface show my_robot_msgs/msg/TargetPose
ros2 interface package my_robot_msgs

发布说明写清:哪些旧 bag 还能播、哪些字段被弃用。下游若缓存了反序列化 struct,添字段仍可能要求 redeploy——接口变更通知应走 fleet 通道,不能只在接口仓库 merge 完就算完。

嵌套 geometry_msgs 时 frame 冲突是常见 bug:外层 PoseStamped 与内层 Point 各带 frame,文档写清以哪个为准,避免 AMCL 与 costmap 各读各的。

5. 版本与弃用窗口

预计会变的消息留 schema_version 或独立 v2 包。弃用流程:双发(旧+新 topic)→ 日志 warning → 两 release 后删旧。接口仓库的破坏性 PR 应比算法仓库更难合并:必须附迁移指南与下游 grep 证明。

6. 契约测试

CI 对关键接口跑:

  • 下游最小订阅/客户端能否编译链接
  • 序列化 round-trip(Python ↔ C++)
  • 字段默认与文档一致

新节点不得再 pub 裸 Float64 承载结构化状态——存量可以迁,增量要拦。

7. 与录包、多语言团队

bag 存的是 schema;接口漂移后旧袋「能播但语义变了」比不能播更危险。多语言(C++/Python/Go 绑定)共享同一 .msg 源,口头传统进不了 Go 的 struct。接口评审问三句:单位?frame?失败时 code 是什么?

跨仓库依赖时,接口包应独立 semver:下游 pin 主版本,避免算法仓库顺手改 msg 字段导致 fleet OTA 半升级。

8. 验收

  • 随机抽 5 个 srv:result 含 machine-readable code
  • 新增字段不破坏旧 subscriber(默认值可验证)
  • ros2 interface list 与架构图 topic 类型一致,无平行 Float64 语义
  • 破坏性变更有 migration 文档与双发窗口记录

9. 案例:改语义未升版本

BatteryState.percentage 从 0–100 改成 0.0–1.0,类型仍是 float32,下游电量门限全错,现场低电未告警。此后 percentage 改名 state_of_charge_fraction,旧名 deprecated 两 release。改语义即新类型或新字段,不偷偷改约定。

10. 默认策略

先想一年后的破坏性变更谁买单;单位、frame、error_code 今天写进契约。算法可以周更,接口按季度思考——方向反了,「快速迭代」会碎在十个平行语义里。

11. Action 与长时间任务

导航、充电、标定等长任务用 Action,feedback 频率与字段含义要写进 .action 注释或独立文档——例如 progress 是路径百分比还是时间百分比。Cancel 语义(软停 vs 硬停)必须在 result code 区分,否则客户端无法安全重试。Service 适合毫秒级原子操作;把 30 s 标定塞进 srv 会占满 executor 并阻塞同节点其他服务。

相关

也可以看看

← 全部文章

johan's blog