支付结果里 oneof 包含 success 和 fail 两个分支,代码先 setSuccess 再 setFail,接收端只看到 fail,成功的金额和流水号全没了,对账差了十几笔。第一次遇到会往复杂处想,其实漏掉的多是个小前提。

PayResult.Builder b = PayResult.newBuilder();
if (b.getResultCase() != PayResult.ResultCase.RESULT_NOT_SET) {
    throw new IllegalStateException("result already set: " + b.getResultCase());
}

oneof 在报文里只有一个字段位,后写的会清掉先写的,这是协议规定的行为,不报错也不告警。业务代码把两个分支当成可以共存的字段来用,就丢了数据。这一点先确认清楚,后面就不用反复试。

「Protobuf oneof 语义」相关的验证我通常做两遍,改完一次、隔天再看一次,确认不是临时生效。临时改动用注释写上日期和原因,过两周自己都记不清为什么这么写。

具体做法是,赋值前先判断当前分支 用 WhichOneof 或 has 方法确认;把两个分支的公共字段提到 oneof 外面 别放进互斥结构;在构造处加断言 两个都设置时直接抛错;回归时造一条同时命中的用例 看最终保留的是哪个。

改完照着核一遍,同时赋值的用例在构造阶段就报错 线上不再出现金额丢失的对账差异。

作者 9823189