巧用 Protobuf 反射来优化代码,拒做 PB Boy

作者:iversonluo,腾讯 WXG 应用开发工程师

有些后台同学将自己称为 SQL Boy,因为负责的业务主要是对数据库进行增删改查。经常和 Proto 打交道的同学,是不是也会叫自己 PB Boy?因为大部分工作也是对 Proto 进行 SET 和 GET。面对大量重复且丑陋的代码,除了宏是否有更好的解决方法?本文结合 PB 反射给出了我在运营系统开发工作中的一些代码优化实践。

一、背景

Protobuf(下文称为 PB)是一种常见的数据序列化方式,常常用于后台微服务之间传递数据。

笔者目前主要的工作都是和表单打交道,而表单一般涉及到大量的数据输入,表单调用方一般将数据格式化为 JSON 后传给 CGI,而 CGI 和后台服务、后台服务之前会用 PB 传递数据。

在写代码时,经常会遇到一些丑陋的、圈复杂度较高、较难维护的关于 PB 的使用代码:

  1. 对字段的必填校验硬编码在代码中:如果需要变更校验规则,则需要修改代码;

  2. 一个字段一个 if 校验,圈复杂度较高:对传进来的字段每个字段都进行多种规则校验,例如长度,XSS,正则校验等,一个校验一个 if 代码,代码圈复杂度很高;

  3. 想要获取 PB 中所有的非空字段,形成一个 map<string,string>,需要大量的 if 判断和重复代码;

  4. 在后台服务间传递数据,由于模块由不同的人开发,导致相同字段的命名不一样,从一个 PB 中挑选一部分内容到另外一个 PB 中,需要大量的 GET 和 SET 代码。

是否可以有方法解决上面的几个问题呢?

答案是使用PB 反射

二、PB 反射的使用

反射的一般定义如下:计算机程序在运行时可以访问、检测和修改它本身状态或行为。

protobuf 的类图如下:

从上图我们可以看出,Message 类继承于 MessageLite 类,业务一般自定义的 Person 类继承于 Message 类。

Descriptor 类和 Reflection 类都聚合于 Message,是弱依赖的关系。

类名 类描述
Descriptor 对 Message 进行描述,包括 message 的名字、所有字段的描述、原始 proto 文件内容等
FieldDescriptor 对 Message 中单个字段进行描述,包括字段名、字段属性、原始的 field 字段等
Reflection 提供了动态读和写 message 中单个字段能力

所以一般使用 PB 反射的步骤如下:

1. 通过Message获取单个字段的FieldDescriptor
2. 通过Message获取其Reflection
3. 通过Reflection来操作FieldDescriptor,从而动态获取或修改单个字段

获取 Descript、Reflection 的函数:

const google::protobuf::Reflection* pReflection = pMessage->GetReflection();
const google::protobuf::Descriptor* pDescriptor = pMessage->GetDescriptor();

获取 FieldDescriptor 的函数:

const google::protobuf::FieldDescriptor * pFieldDesc = pDescriptor->FindFieldByName(id);

下面分别介绍上面的三个类。

2.1 类 Descriptor 介绍

类 Descriptor 主要是对 Message 进行描述,包括 message 的名字、所有字段的描述、原始 proto 文件内容等,下面介绍该类中包含的函数。

首先是获取自身信息的函数:

const std::string & name() const; // 获取message自身名字
int field_count() const; // 获取该message中有多少字段
const FileDescriptor* file() const; // The .proto file in which this message type was defined. Never nullptr.

在类 Descriptor 中,可以通过如下方法获取类 FieldDescriptor:

const FieldDescriptor* field(int index) const; // 根据定义顺序索引获取,即从0开始到最大定义的条目
const FieldDescriptor* FindFieldByNumber(int number) const; // 根据定义的message里面的顺序值获取(option string name=3,3即为number)
const FieldDescriptor* FindFieldByName(const string& name) const; // 根据field name获取
const FieldDescriptor* Descriptor::FindFieldByLowercaseName(const std::string & lowercase_name)const; // 根据小写的field name获取
const FieldDescriptor* Descriptor:
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值