DAB 项目复盘 —— 三个被埋藏在流水账下的工程判断
一、为什么再写一篇
我去年写过一篇《回顾我的软件开发经历:开发 DAB》(cnblogs.com/Rong-/p/18695984),按时间顺序把 DAB 项目从协议学习到 C++ 重构、到七亮点两遗憾的全流程串了一遍。那是流水账,价值在"事实归档"。
后来回头复盘那段经历,发现真正决定项目走向的不是"我做了什么",是埋在流水账之下、当时没有完全说清的几个工程判断:
- 第一次读协议时为什么会看出 V1 不完整
- 为什么真的把 Netflix 的 C OOP 风格写进了接口,后来又自己撤回
- 跨部门团队里,真正起作用的不是"沟通能力",是什么
这三件事,流水账里都只占一两行,但它们才是这个项目对我个人能力曲线影响最大的那几个节点。这篇是把它们各自展开。
项目背景一句话:DAB(Device Automation Bus)是基于 MQTT 的轻量级设备自动化协议,用于智能电视等客厅消费电子的自动化测试认证。我作为主导兼核心开发,带 8 人跨部门团队,做了 Node.js 和 C++ 两代实现,落地 3 个型号认证。下面三件事都是这条主线上的具体节点。
二、判断一:带评审眼光读协议 —— V1 不完整,V2 给我背书
2.1 当时发生了什么
DAB 项目的起点是协议学习。把 V1 规范完整啃下来,整理成内部文档,从规范里提炼出 28 个核心接口,与团队对齐 —— 这是工作流程。
不属于"工作流程"的那一步,是在啃 V1 时同时在心里问:
这套协议放进我们的真实场景,会不会塌?
真实场景是多台设备并行认证 —— 一个 MQTT broker 下挂多台被测设备,自动化框架要能定向把命令发到某一台。V1 的 Topic 设计是这样的:
dab/applications/launch
没有任何字段携带设备身份。一个 broker 下挂两台设备,这条 Topic 推下去两台都会响应。多设备场景下 Topic 模型直接塌掉。
这个判断在 V1 学习阶段就提了出来 —— 没有等开发开始、没有等"撞墙"才发现。
后来 DAB V2.0 发布,Topic 改成了:
dab/<device-id>/applications/launch
显式插入 <device-id> 段。协议委员会自己把我提的方向写进了下一版规范。
2.2 这件事真正的工程含义
被外部背书很爽,但这件事的真正价值不在"我对了",在为什么我能在第一次读协议时就看见这个。
复盘下来,原因只有一条:我读协议时,把"它要被放进的最终落地场景"和"它现在的规格"放在同一张桌上看。
大多数工程师读新协议的默认姿势是"先理解它在说什么",这是一种字典式的读法 —— 读到 V1 的 Topic 设计时,大脑的反应是"哦,launch 应用走这个 Topic"。
带评审眼光的读法不一样。读到同一行时,大脑的反应是:"如果一个真实场景下挂着五台设备,这一行 Topic 推下去,broker 会做什么?"
这两种读法,在写代码阶段没区别,在协议学习阶段差异巨大:前者只能学会协议,后者能在不写一行代码的情况下识别协议的缺陷。
这条习惯不是 DAB 阶段才有 —— 早在做 VIDAA Netflix NRDP 时就在用,只是那时还没把它命名为一种方法论。DAB 是它第一次被外部权威背书,所以才在我的认知里固化下来。
2.3 一个泛化的提炼
读规格的姿势分两种:一种是"读懂它在说什么",一种是"读它放进我所在场景会不会塌"。
第一种产出理解,第二种产出判断。
工程师的价值差距,大头不在第一种,在第二种。
三、判断二:借鉴 ≠ 照搬 —— Netflix 的 C OOP,我用过,然后自己撤回
3.1 当时发生了什么
C++ 版的适配层设计阶段,我做了一件后来回看挺关键的事:真的把 Netflix 的 C 语言面向对象风格写进了接口定义。
Netflix 的 NRDP 用 C 写,但里面有一套很优雅的 OOP 表达 —— 用结构体把若干函数指针打包成"接口对象",再用一个全局函数 getXxxIO() 返回这个对象的引用。我在 VIDAA 阶段读过大量 NRDP 代码,这套手法当时让我印象很深。
到 DAB 设计接口时,本能地想把它复刻一遍。第一版接口长这样:
extern "C" {
struct DAB_API_IO {
DAB_Interface iface;
bool (*getKeyList)(std::vector<std::string>& list);
bool (*pressKey)(const char* key, int durationMs);
bool (*catpureImage2png)(const char* file);
};
const DAB_API_IO& getDABAPIIO();
}
这套接口拿去和团队评审。讲第一遍,有人问:"这个 struct DAB_API_IO 为什么要套这一层?"
我开始解释 —— 多个实例可以并存、运行时可以切换、便于 mock……讲完一轮,对方还是不太理解为什么 DAB 需要这一层。
我自己听出问题了。
不是别人指出来的。是作为提案人,在评审里听自己讲解的成本。如果讲两遍对方还在问"为什么要套这一层",这个抽象在这个场景里就是空成本 —— 它解决的问题在 DAB 这边不存在。
第二版接口我自己改回去了:
extern "C" {
bool dab_api_getKeyList(std::vector<std::string>& list);
bool dab_api_pressKey(const char* key, int durationMs);
bool dab_api_catpureImage2png(const char* file);
}
直接的 extern C 函数。没有结构体打包,没有全局对象,没有间接调用。
3.2 这件事真正的工程含义
事后看,"撤回过度设计"很容易被讲成一种审美判断 —— 简洁好,复杂坏。但真实路径不是审美。真实路径是四步:
- 借鉴 —— 从 Netflix NRDP 学到这种 C OOP 表达
- 落地 —— 真的写进了 DAB 第一版接口定义
- 评审 —— 拿去讲的时候,讲解成本不对劲
- 撤回 —— 主动改回直接函数
这四步缺任何一步都不算。常见的两种残缺版本:
- 没用过就嫌弃:还没写就拍脑袋"这太复杂",这是直觉,不是判断
- 用了不撤回:写完接口提交了、被指出问题再被动改,这是"被校准",不是"自校准"
我这次走完了完整四步,而真正在第三步里识别问题的信号,是一种我以前没意识到自己在用的东西:评审讲解成本。
3.3 把"评审讲解成本"升格为代码质量信号
抽象层值不值,自己一个人写代码时很难判断 —— 因为脑子里已经建好了模型,看自己代码"很顺"。但拿去和团队讲、特别是讲给那些没参与这层设计的人时,讲解成本是非常诚实的信号:
抽象的价值 = 团队对它的理解成本 ≤ 它带来的灵活性收益
讲一遍对方就懂,且这个抽象后续真的支撑了多种场景 —— 收益高,成本低,值。
讲两遍对方还在问"为什么要这一层",且新场景一时也想不出来 —— 成本高,收益空,不值。
Netflix 那一版抽象在 NRDP 里值,因为 NRDP 确实需要"多个适配实例并存 + 运行时切换"(不同平台、不同部署形态)。DAB 不需要 —— 一个设备一个适配,实例数永远是 1。同一个抽象,在 NRDP 上是合理设计,在 DAB 上是空成本。
这件事让我把"评审讲解成本"内化成一种新的代码质量信号。大多数工程师把评审理解为"检查 bug 的环节",我现在把评审也理解为"检测过度设计的环节" —— 而且更便宜、更早期、不需要等运行时数据。
3.4 一个泛化的提炼
借鉴外部优秀实践的最高水准,不是"我学到了就用",是用过之后能识别它在新场景的适用边界,且能在自己作为提案人的评审里识别并主动撤回。
吸收 ≠ 照搬,克制不是审美,是论证后的结论。
四、判断三:横向协作的真正杠杆,不是横向沟通技巧
4.1 当时发生了什么
DAB 项目要协调跨部门团队。其中最难协调的,不是平台/安全/测试那种"必须经过的关键路径"。那种合作天然顺畅,因为对方有交付义务。
最难的是有自己工作安排、不归我管的兄弟子系统团队 —— 他们有自己的 OKR、自己的排期,我提的横向请求在他们的排期表里没有位置。这种横向请求,纯靠"我去找你聊聊"是推不动的。
围绕这一类场景,DAB 项目里我用了一套五件套方法,后来在其他项目里反复用,所以单独拎出来讲。
4.2 五件套,分两组
A 组:组织协调(3 条)
| 方法 | 关键动作 |
|---|---|
| 1. 目标拉起 | 上来先把跨部门的共同目标对齐,不让各组只盯自己 KPI |
| 2. 提前沟通 | 不等任务下发再找人,在自己方案设计阶段就把对方拉进对话 —— 让他们的排期表里有这件事 |
| 3. 领导协调 | 识别哪些事自己沟通推不动,借领导的杠杆;把状态做到对方领导能看到的层面,用组织资源对齐组织资源 |
B 组:工程解耦(2 条)
| 方法 | 关键动作 |
|---|---|
| 4. 接口先行 | 跨部门最大卡点是"等对方实现",先把接口约定下来,双方按接口并行开发 |
| 5. 默认实现 ↔ 实际实现切换 | 接口先行的工程基础 —— 提供 mock/stub 让自己这边能跑通流程,等对方实际实现就绪后切换 |
A 组是组织层动作:让对方愿意参与。B 组是工程层动作:让自己不被对方阻塞。两组同时启动效率最高。
4.3 这件事真正的工程含义
五件套都不新鲜,任何一本项目管理书上都有。这件事真正值得提的,不是方法清单,是为什么这五件套有效。
最关键的认知是这一条:
横向协作的真正杠杆,不是横向沟通技巧,是把横向请求翻译成对方的纵向目标。
"目标拉起"和"提前沟通"为什么有效?不是因为我"沟通能力强",是因为在沟通之前我已经做好了一件事:找到这件事与对方部门 OKR 的重合点。一旦能把"帮我搞定 DAB 适配"翻译成"这是你今年部门要交付的认证能力的一部分",对方推动这件事就有了纵向授权 —— 他不再是"在帮我",他是"在做自己本来就要做的事"。
"领导协调"为什么有效?不是因为"找老大压人",是因为领导的视野往往天然就在"组织目标"这一层 —— 你把信息抬到那一层,事情自然就被翻译成纵向目标。
"接口先行"和"默认实现切换"为什么有效?它们解决的是另一面 —— 即便目标对齐,对方的排期也未必和你的排期吻合。这时候工程解耦让"对方什么时候交付"对你的进度无关紧要。
这条认知本质上是一种协议设计思维:找共同语言 + 解耦执行节奏。横向协作的成败,大头不在你说话的方式,在你能不能把请求重新表达成对方系统里早就存在的目标。
4.4 一个泛化的提炼
横向协作的杠杆点,不在沟通桌的两端,在两端的纵向目标之间能不能找到重合面。
把横向请求翻译成对方的纵向目标,事情自动顺;翻译不成,任何沟通技巧都是徒劳。
五、三件事的同源:都是"评审眼光"
写到这里回头看,这三件事看起来在讲不同的东西 —— 协议学习、过度设计、跨部门协调。但它们底下是同一种思维姿态。
判断一是用评审眼光读外部规格 —— 不是"它说了什么",是"它放进我的场景会不会塌"。
判断二是用评审眼光读自己的设计 —— 不是"它好不好看",是"它的讲解成本能不能换来灵活性收益"。
判断三是用评审眼光读跨部门关系 —— 不是"我怎么说服他",是"我能不能把请求翻译进他的目标体系"。
三种场景换的是对象 —— 别人的协议、自己的代码、别人的目标 —— 不变的是把目标当作判断坐标系:不被表面规格、自己的审美偏好、表面的沟通氛围牵着走,而是反复回到"我要解决的问题"和"它会被放进的场景"上做判断。
这种姿态我以前没有命名。DAB 项目结束时还没意识到这是同一件事。是后来在更多项目里反复用、反复看见同一个手法在不同场景里起作用,才回头把它认出来。
六、为什么这三件事值得专门讲
最后说一句这篇博客的元层次。
DAB 项目我有一篇按时间线的总结,流水账格式,事实清楚。但一年后回头读那篇,发现自己当时讲不清楚最值得讲的事。七亮点两遗憾列了一长串,但"为什么我能在 V1 阶段就提出 Topic 缺陷"、"为什么我能在评审里主动撤回 Netflix 风格"、"为什么跨部门协调推得动",这些都被埋在事实清单底下,没有被独立命名,所以下次也不知道怎么复用。
工程经历的价值,不在做过多少事,在每件事里有没有提炼出可命名的判断动作。这篇是补一次提炼。三件事提炼出三个判断 —— 评审眼光读协议、评审讲解成本作为代码质量信号、横向请求翻译成纵向目标 —— 被命名之后才是工具,没被命名就还是手感。
三件事换的是对象,不变的是把目标当作判断坐标系。命名之后,才是工具。
浙公网安备 33010602011771号