作者 | 刘俊启
导读
在软件开发中,经常会遇到一些代码问题,例如逻辑结构复杂、依赖关系混乱、代码冗余、不易读懂的命名等。这些问题可能导致代码的可维护性下降,增加维护成本,同时也会影响到开发效率。这时通常通过重构的方式对已有代码结构进行改进和优化。在重构的工作中,大部分的工作是人工的方式完成,是一个耗时且容易出错的过程。对于研发人员来讲,在不改变软件的功能和行为的前提下,保证质量和效率完成对已有功能的重构,是一个极大的挑战。本系列以Python实现自动化的工具,支持代码重构过程的实践。
全文5529字,预计阅读时间14分钟。
在上一篇《通过Python脚本支持OC代码重构实践(一):模块调用关系分析》的内容中,重点介绍了使用Python实现模块调用关系的分析,并以.csv格式文件输出,导入到excel中评估重构影响面及每个数据项重构方式,在重构编码工作启动前明确了具体的工作及重构之后的收益,这对于立项的人力投入的决策会起到很关键的作用,特别是当团队资源稀缺时,把要做的事情讲清楚是一件很重要的事情。
同时也在上篇内容中提到,技术层面先实现一个模块间数据项通讯的机制(本系列文章中以数据通路代指),支持数据项不需要公开,也可以被其它的组件中的模块读写。基于数据通路的实现,将XXXSetting模块接入数据通路,就可以解决因为XXXSetting模块中的数据项变更而带来的接口不兼容变更的问题,也会降低上层的依赖方组件二次的发布次数,间接的提升XXXSetting模块数据项相关研发需求的研发效率。
在XXXSetting模块数据项接入到数据通路过程,以百为量级的数据项需要逐项的按照数据通路的标准进行重构,手工的重构方式成本高,出错概率高,测试时需要逐项验证成本高,我们使用的Python脚本实现接入数据通路的这部分代码的生成,可精准的生成每一个数据项接入数据通路的代码段,实现了本次重构工作在测试及上线阶段零Bug。
本篇的内容先简单介绍数据通路的基础功能,随后再阐述如何利用Python编写的自动化工具,XXXSetting模块作为数据项提供方集成到数据通路中,代码自动的生成的实现思路。
01 数据通路技术实现与接入
基于本次配置数据项重构工作的目标和数据通路的复用,数据通路的实现目标为可支持不同模块接入,如图-1所示,与数据通路相关的模块共为两类。
△图-1
1.1 数据项交互模块定义及简介
数据项按照供需关系,主要分为两类,数据项提供模块和数据项使用模块。
1、数据项提供模块:数据的提供方(如本文中提到的XXXSetting),遵循系统中约定的数据读写协议,为系统提供互通的数据项支持。数据项与数据项提供模块是n:1的关系。数据通路支持多个数据项提供模块的接入,是 1:n 的关系。
2、数据项使用模块:数据的使用方(如上篇文章中的XXXLib中的模块),基于数据通路提供的能力,进行数据的读写调用,获取及更新所依赖的数据项的值。数据通路与数据项使用模块是 1:n 的关系。
1.2 数据通路实现及模块简介
数据通路的主体实现思路为,提供统一的接口,支持不同的数据项提供模块接入,在数据通路中管理接入数据项提供模块,当数据项使用模块需要读写数据时,根据数据通路提供的接口,进行数据项的数据同步。主要分为数据项提供模块接口层、数据项提供模块管理和数据项读写服务模块。
1、数据项提供模块接口层:约定数据项提供方要实现的数据读写能力,只有按照该标准实现的模块,才可作为数据的提供模块接入。
2、数据项提供模块管理:管理系统中所有数据项提供模块,提供注册的接口,数据项提供模块可调用注册接入需要在数据通路中管理的数据项。同时在收到数据项读写请求时,对模块中所关联的数据项的读写进行分发。
3、数据项读写服务模块:提供稳定的数据读写的能力,全局可访问,根据key查找数据项提供模块,并调用数据项提供模块的接口实现数据项的读写。
02 数据提供方接入数据通路的实现
2.1 数据项接入数据通路的主要工作
1、数据提供模块接入数据通路:按照数据项提供模块接口层约定,实现数据项的读写,主要分两步:
-
向数据通路注册可读写的数据项的信息,是一个数组,数组中存放的是每个数据项的Key,Key的命名规则为数据项提供模块类名_数据项名,这部分代码使用Python脚本自动生成。
-
数据项的读写,由数据项提供模块实现读写数的接口,根据key匹配数据项,之后再对该数据项进行读写 ,这部分代码也使用Python脚本自动生成。
2、数据项使用模块接入数据通路:由原直接调用方式,改为通过数据通路间接调用的方式,详细实现在下一篇内容介绍,敬请关注。
2.2 需要重构的数据项整理
在上一篇《通过Python脚本支持OC代码重构实践(一):模块调用关系分析》的内容中,3.1.2 提取的是变量类型和变量的名称小节中,经过预处理后,可提取所有数据项的类型及数据项名称。
同时结合上篇3.3.2 数据项的预分析统计输出小节中,取数据项被多个组件使用的数据项,确定为本次需要重构的数据项。
将这两部分数据进行交集的计算,得出来需要重构的数据项类型及数据项名称全集,为数据项读写代码生成时使用。下面为数据集的示例。
// 数据项类型 数据项名称;
NSString value1;
NSString value2;
BOOL value3;
...
2.3 数据项提供模块的数据项列表生成
数据通路本身不产生数据,只作数据读写的桥接。数据项提供模块接入到数据通路时,需要知知数据通路支持那些数据项的读写。
具体的实现为,通过数据项提供模块接口层的约定告知数据通路,由数据通路调用,返回数据项提供模块支持的数据项列表,数据项列表的数据结构为数组,数组中为每个数据项的key,key的生成格式为数据项提供模块类名_数据项名。Python实现的转换代码如下:
# 原代码行示例 NSString value1; 参考2.2小节中的代码
matchObj = re.match(r"(.*)\s+(.*);", line, re.M|re.I)
if matchObj:
# value = matchObj.group(2) -- value1
key = ' @\"' + className + '_' + matchObj.group(2) + '\",\n'
# key = ' @"className_value1",\n'
# key 按OC的写法,每一行一个key,按NSArray的方式初始化多个key
2.4 数据项读取代码生成
注册了可通过数据通路读写的数据项,当数据通路需要读写该数据项时,数据项提供方按照标准实现数据项的读写。
2.4.1 数据项读取代码示例
数据通路支持基本的数据类型的读取,每一种数据类型对应的不同的读取接口,数据提供方根据数据项的类型,实现不同类型数据读取,同一种数据类型中,数据提供方根据key返回的对应的数据项值,目标生成的OC代码如下:
// 数据项是 NSString类型
- (NSString *)stringForKey:(NSString *)key {
if ([key isEqual:@"className_value1"]) {
return self.value1;
}
// 如有多个数据项,自动也合到同一个函数
if ([key isEqual:@"className_value2"]) {
return self.value2;
}
return nil;
}
// 数据项是 BOOL类型
- (BOOL)boolForKey:(NSString *)key {
if ([key isEqual:@"className_value3"]) {
return self.value3;
}
return NO;
}
// 其它...
2.4.2 数据项读取实现生成
因数据项的类型不同,需要使用不同的接口读取,故在代码转换时,会根据数据项的类型,将转换后的代码行,分别的存储在不同的数据变量中,每个数据变量会在初始化时,增加函数头,在转换结束后增加函数尾。
- 函数头示例,以数据项为NSString类型为例
# NSString 类型的数据读接口,函数头字串由变量保存
funName = '- (NSString *)stringForKey:(NSString *)key {'
- 函数体示例,每个数据项均生成对应的代码,依次的存储每个数据项的读取
# 原代码行示例 NSString value1; 参考2.2小节中的代码
matchObj = re.match(r"(.*)\s+(.*);", line, re.M|re.I)
if matchObj:
funbody = ' if ([key isEqual:@\"'
funbody += 'className_' + matchObj.group(2) + '\"]) {\n'
funbody += ' return self.' + matchObj.group(2) + ';\n'
funbody += ' }\n\n'
# funbody 为转换之后的读取某个数据项的部分代码,匹配key,之后再返回对应的值,增加一些空格及换行,代码按规范对齐
# if ([key isEqual:@"className_value1"]) {
# return self.value1;
# }
- 函数尾示例,以NSString类型为例,所有数据项转换完成之后,再增加
funEnd = ' return nil;\n'
funEnd += '}\n\n'
不同的数据类型依次转换,所有数据项转换完成之后,依次的组合成为一个文件,文件的内容可以直接copy到项目工程中,可直接的使用。
2.5 数据项更新
2.5.1 数据项更新代码示例
数据通路支持基本的数据类型的更新,每一种数据类型对应的不同的更新接口,数据提供方使用根据数据项的类型,实现不同类型数据更新,同一种数据类型中,数据提供方根据key更新的对应的数据项值,目标生成的OC代码如下:
// 数据项是 NSString类型
- (void)updateString:(NSString *)value forKey:(NSString *)key {
if ([key isEqual:@"className_value1"]) {
self.value1 = value;
return;
}
// 如有多个数据项,自动也合到同一个函数
if ([key isEqual:@"className_value2"]) {
self.value2 = value;
return;
}
}
// 数据项是 BOOL类型
- (void)updateBool:(BOOL)value forKey:(NSString *)key {
if ([key isEqual:@"className_value3"]) {
self.value3 = value;
return;
}
}
// 其它...
2.5.2 数据项更新实现生成
因数据项的类型不同,需要使用不同的接口更新数据项,故在代码转换时,会根据数据项的类型,将转换后的代码行,分别的存储在不同的数据变量中,每个数据变量会在初始化时,增加函数头,在转换结束后增加函数尾。
- 函数头示例
# NSString 类型的数据读接口,函数头字串由变量保存
funName = '- (void)updateString:(NSString *)value forKey:(NSString *)key {'
- 函数体示例,每个数据项均生成对应的代码,依次的存储每个数据项的读取
# 原代码行示例 NSString value1; 参考2.2小节中的代码
matchObj = re.match(r"(.*)\s+(.*);", line, re.M|re.I)
if matchObj:
funbody = ' if ([key isEqual:@\"'
funbody += 'className_' + matchObj.group(2) + '\"]) {\n'
funbody += ' self.' + matchObj.group(2) + ' = value;\n'
funbody += ' return;\n'
funbody += ' }\n\n'
# funbody 为转换之后的更新某个数据项的部分代码,匹配key,之后再返回对应的值,增加一些空格及换行,代码按规范对齐
# if ([key isEqual:@"className_value1"]) {
# self.value1 = value;
# return;
# }
- 函数尾示例,所有数据项转换完成之后,再增加
funEnd += '}\n\n'
03 小结
本篇的内容,基于上一篇内容的分析结论,将被多个组件使用的数据项接入到数据通路的代码,使用Python脚本自动生成的实践。
因涉及到的数据项较多,需要在所有的数据项中选出需要重构的数据项,生成数据项key列表,并跟据数据项的类型,接入到不同类型的读写接口中。使用人工书写代码的方式很难保证数据项接入到数据通路过程的质量,同时也很难验证数据项迁移的完整性。
而使用Python脚本实现工具支持数据项接入数据通路的代码生成,可以自动的、精准的生成每一个数据项接入数据通路的代码,可减少研发及测试人力的投入,间接的提升了研发效率。
下一篇我们将介绍如何通过Python脚本支持数据项使用模块接入数据通路时的适配,感兴趣的同学,可以持续关注。
欢迎加入百度搜索大前端团队,持续招聘iOS/Android/Web前端研发工程师
简历欢迎投递至joinefe@baidu.com
——END——
推荐阅读
对话InfoQ,聊聊百度开源高性能检索引擎 Puck
浅谈搜索展现层场景化技术-tanGo实践
初识搜索:百度搜索产品经理的第一课
智能问答技术在百度搜索中的应用
通过Python脚本支持OC代码重构实践(一):模块调用关系分析