通过Python脚本支持OC代码重构实践(三):数据项使用模块接入数据通路的适配
作者 | 刘俊启
导读
在软件开发中,经常会遇到一些代码问题,例如逻辑结构复杂、依赖关系混乱、代码冗余、不易读懂的命名等。这些问题可能导致代码的可维护性下降,增加维护成本,同时也会影响到开发效率。这时通常通过重构的方式对已有代码结构进行改进和优化。在重构的工作中,大部分的工作是人工的方式完成,是一个耗时且容易出错的过程。对于研发人员来讲,在不改变软件的功能和行为的前提下,保证质量和效率完成对已有功能的重构,是一个极大的挑战。本系列以Python实现自动化的工具,支持代码重构过程的实践。
在第一篇《通过Python脚本支持OC代码重构实践(一):模块调用关系分析》的内容中,介绍了使用Python实现模块调用关系的分析,确定了调用数据项的代码块超过了600处,如图-1所示,这些调用点分布在不同的组件中,是直接调用的关系。
△图-1
在第二篇《通过Python脚本支持OC代码重构实践(二):数据项提供模块接入数据通路的代码生成》的内容中,重点介绍了使用Python实现了数据项提供模块接入数据通路时,公开数据项相关的代码生成(图-2中的红框部分),这时数据项读写由原来的直接读写方式改为通过数据通路的间接读写方式。
△图-2
当数据项提供模块接入到数据通路后,数据项使用模块需要进行重构,以符合数据通路的标准。重构涉及到600多处调用代码段的适配(图-3中的红框部分),手工重构方式成本高、出错概率高,并且在测试时需要逐项验证,成本也很高。为了解决这个问题,我们使用Python脚本实现了与数据通路的通讯代码的生成,可自动的为每个数据项封装读写函数,和自动将原有的代码调用替换为升级后的代码调用,支持不同数据项的升级。这样做实现了本次重构工作在测试及上线阶段零 Bug。
△图-3
本篇内容阐述如何利用Python编写的自动化工具,实现将原数据项使用模块中直接对数据项提供模块中数据项的读写方式,升级为通过数据通路间接读写。包括每个数据项读写类的封装和数据项使用模块的调用代码段适配。
01 数据项读写类封装
为了降低数据项的读写调用代码的重构成本,在数据项使用模块中创建一个封装类。每个数据项的读写创建一个静态函数来实现,可被数据项使用模块中的数据项读写类使用。由于需要使用Python脚本实现工具,因此需要有明确的生成规则,以便工具的实现。规则如下:
1、数据项的读取操作,函数返回类型,函数名,均与与数据项相同。
- 如:NSString *value1; 需要转为 +(NSString *)value1,包含函数定义及实现。
2、数据项的更新操作,set_ + 函数名:数据类型,均与数据项相同。
- 如:NSString *value1; 需要转为 +(void)set_value1:(NSString *)value,包含函数定义及实现。注意:参数名均为value
1.1 数据项读取能力封装
基于数据项读取操作生成规则,分别实现函数头、函数声明、函数体,分别输出至.m和.h文件。
- 函数头及函数声明实现示例
# 原代码行示例 NSString value1; matchObj = re.match(r"(.*)\s+(.*);", line, re.M|re.I) if matchObj: valuetype = matchObj.group(1) valuename = matchObj.group(2) # 因不同类型修饰的方式不同,在getReadFunReturnType进行类型映射;如NSString :NSString * funname = '+ (' + getReadFunReturnType(valuetype) + ')' + valuename # 函数声明 .h文件 + (NSString *)value1;\n\n hfunname = funname + ';\n\n' # 函数定义 .m文件 + (NSString *)value1 {\n mfunname = funname + ' {\n'
- 函数体示例,每个数据项跟据key与数据通路通信,读取数据项
# 定义返回类型的变量,并赋值,代码行为 # funbody为: NSString *res = [DataChannelReaderxxx funbody = ' ' + getReadFunReturnValueType(valuetype) + 'res = [DataChannelReaderxxx ' # 不同类型的数据,数据通路提供的读取的函数不同,由getReadFunName函数中映射,如:NSString :stringForKey # funbody 为 NSString *res = [DataChannelReaderxxx stringForKey:@" funbody += getReadFunName(valuetype) + ':@\"' # key,类名_数据项名 className_value1 key = className + '_' + valuename # funbody 为 NSString *res = [DataChannelReaderxxx stringForKey:@"className_value1"];\n funbody += key + '\"];\n' # 函数实现完成 funbody += ' return res;\n}\n\n'
- 分别存到.m文件和.h文件
# 函数数头 .m文件 file_data += mfunname # 函数体 .m文件 file_data += funbody # 函数定义 .h文件 hfile_data += hfunname
- 文件生成:默认以XXXSettingReader作为文件名及类名作为前辍,XXX为使用方模块名称,这样就比较清楚,是那个模块中的数据项读取能力封装。
1.2 数据项更新能力封装
基于数据项更新操作生成规则,分别实现函数头、函数体,及.m和.h文件
- 函数头及函数声明实现示例
# 原代码行示例 NSString value1; matchObj = re.match(r"(.*)\s+(.*);", line, re.M|re.I) if matchObj: valuename = matchObj.group(2) valuetype = matchObj.group(1) # funname为: + (void)set_value1 funname = '+ (void)set_' + valuename # 因不同类型修饰的方式不同,在getValueType进行类型映射;如NSString :NSString * # funname为:+ (void)set_value1:(NSString *)value funname += ':(' + getValueType(valuetype) + ')value' # 函数声明 .h文件 + (void)set_value1:(NSString *)value;\n\n hfunname = funname + ';\n\n' # 函数定义 .m文件 + (void)set_value1:(NSString *)value {\n mfunname = funname + ' {\n'
- 函数体示例,每个数据项跟据key与数据通路通信,更新数据项
# 不同类型的数据,数据通路提供的更新的函数不同,在getUpdateFunName函数中映射,如:NSString :updateString # funbody 为 [DataChannelWriterxxx updateString:value funbody = ' [DataChannelWriterxxx ' + getUpdateFunName(valuetype) + ':value ' # key,类名_数据项名 className_value1 key = className + '_' + valuename # funbody 为 [DataChannelWriterxxx updateString:value forKey:@"className_value1"];\n funbody += 'forKey:@\"' + key + '\"];\n' # 函数实现完成 funbody += ' }\n\n'
- 分别存到.m文件和.h文件
# 函数数头 .m文件 file_data += mfunname # 函数体 .m文件 file_data += funbody # 函数定义 .h文件 hfile_data += hfunname
- 文件生成:默认以XXXSettingWriter作为文件名及类名作为前辍,XXX为使用方模块名称,这样就比较清楚,是那个模块中的数据项更新能力封装。
02 数据项使用模块调用代码段适配
当数据提供模块通过数据通路支持数据项的读写,在数据项使用模块中也需要进行适配。原直接使用数据项,改为使用数据项读写类,这部分的代码使用自动化方式完成。分为两类,数据项更新调用代码段适配和数据项读取调用代码段适配,因数据项更新和数据项读取代码段前辍相似,先执行更新后执行读取。
2.1 数据项更新调用代码段适配
2.1.1 代码转换OC代码示例
数据项读取的更改的主要思路为字符串匹配,查找替换。依次的拼装每个数据项字串,再替换成每个数据项升级之后的写法,如:
[XXXSetting share].value1 = @"str" => [XXXSettingWriter set_value1:@"str"]
2.1.2 关键的代码实现
- 原始数据项调用字串使用数据通路的数据项绑定
# 定义个全局的字典 allwritepubvalue = {} # 原代码行示例 NSString value1; matchObj = re.match(r"(.*)\s+(.*);", line, re.M|re.I) if matchObj: # valuename = value1 valuename = matchObj.group(2) # key = [XXXSetting share].value1 key = '[XXXSetting share].' + valuename # value = SettingWriter set_value1:,不同的模块前面加上[XXX ,后面加运算符右侧 value = ' XXXSettingWriter set_' + valuename + ':' # 赋值 key = [XXXSetting share].value1,value = SettingWriter set_value1: allwritepubvalue[key] = value
- 查找原调用方式,升级为数据通路的读取方式
# 获取当前工程中,所有源码文件及对应的组件名 allfileandlib = {} # allpubvalue 全局变量,字典 for key, value in allwritepubvalue.items(): # filename为文件名,libname为组件名 for filename, libname in allfileandlib.items(): # 当libname为XXX replacevalue = [XXXSettingWriter set_value1: replacevalue = '[' + libname + value # 实现个函数 重写这个文件 将文件中 [XXXSetting share].value1 = YYY 替换为 [XXXSettingReader set_value1:YYY] reWriteFile(filename, key, replacevalue)
- 文件重写函数实现,需要实现全字的匹配,避免数据中存在相互为子串的情况。
# 定义一个输出的数据,初始为空字串 outfiledata = '' # 使用正则全字匹配,查找替换 regAbKey = fromstr.replace('[', '\[') regAbKey = regAbKey.replace(']', '\]') regAbKey = regAbKey.replace('.', '\.') # pattern 为 .*\[XXXSetting share\]\.value1\s*=\s*([a-zA-Z0-9_\[\]\s\.]+),为了匹配赋值字串,但没有考虑运算符右侧有运算符的情况 pattern = r'.*' + regAbKey + '\s*=\s*([a-zA-Z0-9_\[\]\s\.]+)' # 依次从文件中读,正则全字查找及规换 for line in f: matchObj = re.match(pattern, line, re.M|re.I) if matchObj: # 代码中真实的写法,去掉前面的一些代码,比如 [XXXSetting share].value1 = YYY,变为[XXXSetting share].value1 = YYY eqcode = re.sub(r'.*' + regAbKey, fromstr, matchObj.group()) # 如原代码为 [XXXSetting share].value1 = YYY ,则 matchObj.group(1) 为YYY # 把 [XXXSetting share].value1 = YYY 替换为 [XXXSettingWriter set_value1:YYY] newline = line.replace(eqcode, tosrt + matchObj.group(1) +']') outfiledata += newline
2.2 数据项读取调用代码段适配
2.2.1 代码转换OC代码示例
数据项读取的更改的主要思路为字符串匹配,查找替换。依次的拼装每个数据项字串,再替换成每个数据项升级之后的写法,如:
[XXXSetting share].value1 => [XXXSettingReader value1]
2.2.2 关键的代码实现
- 原始数据项调用字串使用数据通路的数据项绑定
# 定义个全局的字典 allreadpubvalue = {} # 原代码行 NSString value1; 4.1生成的类型及变量名 matchObj = re.match(r"(.*)\s+(.*);", line, re.M|re.I) if matchObj: # valuename = value1 valuename = matchObj.group(2) # key = [XXXSetting share].value1 key = '[XXXSetting share].' + valuename # value = SettingReader value1] ,不同的模块再加上[XXX value = 'SettingReader ' + valuename + ']' # 赋值 key = [XXXSetting share].value1,value = SettingReader value1] allreadpubvalue[key] = value
- 查找原调用方式,升级为数据通路的读取方式
# 获取当前工程中,所有源码文件及对应的组件名 allfileandlib = {} # allpubvalue 全局变量,字典 for key, value in allreadpubvalue.items(): # filename为文件名,libname为组件名 for filename, libname in allfileandlib.items(): # 当libname为XXX replacevalue = [XXXSettingReader value1] replacevalue = '[' + libname + value # 实现个函数 重写这个文件 将文件中 [XXXSetting share].value1 替换为 [XXXSettingReader value1] reWriteFile(filename, key, replacevalue)
- 文件重写函数实现,需要实现全字的匹配,避免数据中存在相互为子串的情况。
# 定义一个输出的数据,初始为空字串 outfiledata = '' # 使用正则全字匹配,查找替换 regAbKey = fromstr.replace('[', '\[') regAbKey = regAbKey.replace(']', '\]') regAbKey = regAbKey.replace('.', '\.') # \[XXXSetting share\]\.value1\b pattern = r'' + fromstr + r'\b' # 依次从文件中读,正则全字查找及替换 for line in f: newline = re.sub(pattern, tosrt , line) outfiledata += newline
03 小结
本篇是本系列的最后一篇,在本系列第一篇内容中介绍了通过Python脚本实现公开接口及调用关系的分析,用来支持重构工作量及影响面的评估。在第二扩篇内容中,介绍了通过Python脚本实现数据项提供模块接入数据通路的代码转换。
本篇内容介绍使用 Python 编写自动化工具,实现了将原数据项使用模块中直接对数据项提供模块中数据项的读写方式,升级为通过数据通路间接读写。包括每个数据项读写类的封装和数据项使用模块的调用代码段适配。通过封装每个数据项的读写类,并为每个数据项封装了独立的读写函数,和对原有调用代码的自动替换,这些工作是IDE提供的相关工具不可支持及定制的,基于Python编写的自动化工具,降低了重构成本,并在测试及上线阶段实现了零 Bug。
欢迎加入百度搜索大前端团队,持续招聘 iOS/Android/Web前端 研发工程师。简历欢迎投递至joinefe@baidu.com
——END——
推荐阅读

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
为React Ant-Design Table增加字段设置 | 京东云技术团队
最近做的几个项目经常遇到这样的需求,要在表格上增加一个自定义表格字段设置的功能。就是用户可以自己控制那些列需要展示。 在几个项目里都实现了一遍,每个项目的需求又都有点儿不一样,迭代了很多版,所以抽时间把这个功能封装了个组件:@silverage/table-custom,将这些差别都集成了进去,方便今后使用和维护。同时也方便需要这个功能的人来使用。 下面介绍下安装和使用 安装 npm i @silverage/table-custom --save yarn add @silverage/table-custom pnpm add @silverage/table-custom 组件在ant-design基础上开发,所以你也要安装antd。数据持久化使用的use-local-storage-state,也是要安装的。另外,react也是peer dependency。 使用 组件使用非常简单,只需要将你原先antd的<Table />换成<TableCustom />即可。兼容所有antd table的属性。 import { TableCustom } ...
- 下一篇
准「AI 时代」下,如何衡量程序员的工作效率和生产力?
近 20 家科技、金融和制药公司实施了新的研发效能管理方法,并取得了令人鼓舞的初步结果。 客户报告的产品缺陷减少 20%-30%; 员工体验分数提高 20%; 客户满意度评分提高 60 个百分点。 大模型和 AIGC 技术催生了软件研发的新范式,也让研发管理的复杂度急剧攀升。尽管有研究称,Copilot X 和 ChatGPT 等生成式 AI 工具有望将开发者完成任务的速度提高两倍,但在引入合适的 AI 应用和工具之前,研发管理者们可能首先要回答,「如何判断 AI 工具确实为组织提效产生了助力? 」 准「AI 时代」下,「AI + 研发效能」很可能成为企业构建核心竞争力的角逐高地。而如何科学、全面且准确地衡量开发者和研发团队的工作效率与生产力正是研发效能治理中的重要命题。 麦肯锡在最近的一次研究中,对现有的两组生产力指标模型进行拓展和补充,构建了端到端的开发者工作效率与生产力视图。报告称,该方法很容易通过调查问卷或沉淀在研发管理工具中的过程数据进行部署,无需引入大量的技术堆栈或工具设备。 麦肯锡:开发者工作效率与生产力视图 基于 DORA 指标和 SPACE 指标,麦肯锡拓展补充了 4...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS8编译安装MySQL8.0.19
- CentOS7,CentOS8安装Elasticsearch6.8.6