作者:iversonluo,腾讯 WXG 应用开发工程师
有些后台同学将自己称为 SQL Boy,因为负责的业务主要是对数据库进行增删改查 。经常和 Proto 打交道的同学,是不是也会叫自己 PB Boy?因为大部分工作也是对 Proto 进行 SET 和 GET 。面对大量重复且丑陋的代码,除了宏是否有更好的解决方法?本文结合 PB 反射给出了我在运营系统开发工作中的一些代码优化实践 。一、背景Protobuf(下文称为 PB)是一种常见的数据序列化方式,常常用于后台微服务之间传递数据 。
笔者目前主要的工作都是和表单打交道,而表单一般涉及到大量的数据输入,表单调用方一般将数据格式化为 JSON 后传给 CGI,而 CGI 和后台服务、后台服务之前会用 PB 传递数据 。
在写代码时,经常会遇到一些丑陋的、圈复杂度较高、较难维护的关于 PB 的使用代码:
- 对字段的必填校验硬编码在代码中:如果需要变更校验规则,则需要修改代码;
- 一个字段一个 if 校验,圈复杂度较高:对传进来的字段每个字段都进行多种规则校验,例如长度,XSS,正则校验等,一个校验一个 if 代码,代码圈复杂度很高;
- 想要获取 PB 中所有的非空字段,形成一个 map<string,string>,需要大量的 if 判断和重复代码;
- 在后台服务间传递数据,由于模块由不同的人开发,导致相同字段的命名不一样,从一个 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获取单个字段的FieldDescriptor2. 通过Message获取其Reflection3. 通过Reflection来操作FieldDescriptor,从而动态获取或修改单个字段
获取 Descript、Reflection 的函数:【拒做PB Boy!教你巧用 Protobuf 反射来优化代码】
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::FindFieldByCamelcaseName(const std::string & camelcase_name) const; // 根据驼峰的field name获取
其中FieldDescriptor* field(int index)和FieldDescriptor* FindFieldByNumber(int number)这个函数中index和number的含义是不一样的,如下所示:
推荐阅读
- 绿杨春茶的冲泡方法,教你鉴别春茶的两方法
- 冬季雾霾来袭!中医教你清理肺部污染物
- 春季脖子易出问题 中医专家教你颈椎病防治之道
- 初春老犯困当心是脑梗 教你几招有效预防
- PHP微服务实践——手把手教你搭建PHP微服务
- 牛肉的做法 教你吃牛肉抗癌
- 咽喉炎吃什么好 教你食疗治咽喉炎
- 教你太极拳推手的5个姿势
- 教你读懂太极拳的八字决
- 路由器怎样重置密码?教你方法,简单实用很有效