透過Python指令碼支援OC程式碼重構實踐(三):資料項使用模組接入資料通路的適配
在軟體開發中,經常會遇到一些程式碼問題,例如邏輯結構複雜、依賴關係混亂、程式碼冗餘、不易讀懂的命名等。這些問題可能導致程式碼的可維護性下降,增加維護成本,同時也會影響到開發效率。這時通常透過重構的方式對已有程式碼結構進行改進和最佳化。在重構的工作中,大部分的工作是人工的方式完成,是一個耗時且容易出錯的過程。對於研發人員來講,在不改變軟體的功能和行為的前提下,保證質量和效率完成對已有功能的重構,是一個極大的挑戰。本系列以Python實現自動化的工具,支援程式碼重構過程的實踐。
在第一篇《透過Python指令碼支援OC程式碼重構實踐(一):模組呼叫關係分析》的內容中,介紹了使用Python實現模組呼叫關係的分析,確定了呼叫資料項的程式碼塊超過了600處,如圖-1所示,這些呼叫點分佈在不同的元件中,是直接呼叫的關係。
△圖-1
在第二篇《透過Python指令碼支援OC程式碼重構實踐(二):資料項提供模組接入資料通路的程式碼生成》的內容中,重點介紹了使用Python實現了資料項提供模組接入資料通路時,公開資料項相關的程式碼生成(圖-2中的紅框部分),這時資料項讀寫由原來的直接讀寫方式改為透過資料通路的間接讀寫方式。
△圖-2
當資料項提供模組接入到資料通路後,資料項使用模組需要進行重構,以符合資料通路的標準。重構涉及到600多處呼叫程式碼段的適配(圖-3中的紅框部分),手工重構方式成本高、出錯機率高,並且在測試時需要逐項驗證,成本也很高。為了解決這個問題,我們使用Python指令碼實現了與資料通路的通訊程式碼的生成,可自動的為每個資料項封裝讀寫函式,和自動將原有的程式碼呼叫替換為升級後的程式碼呼叫,支援不同資料項的升級。這樣做實現了本次重構工作在測試及上線階段零 Bug。
△圖-3
本篇內容闡述如何利用Python編寫的自動化工具,實現將原資料項使用模組中直接對資料項提供模組中資料項的讀寫方式,升級為透過資料通路間接讀寫。包括每個資料項讀寫類的封裝和資料項使用模組的呼叫程式碼段適配。
GEEK TALK
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\bpattern = r'' + fromstr + r'\b'# 依次從檔案中讀,正則全字查詢及替換for line in f: newline = re.sub(pattern, tosrt , line) outfiledata += newline
GEEK TALK
03
小結
本篇是本系列的最後一篇,在本系列第一篇內容中介紹了透過Python指令碼實現公開介面及呼叫關係的分析,用來支援重構工作量及影響面的評估。在第二擴篇內容中,介紹了透過Python指令碼實現資料項提供模組接入資料通路的程式碼轉換。
本篇內容介紹使用 Python 編寫自動化工具,實現了將原資料項使用模組中直接對資料項提供模組中資料項的讀寫方式,升級為透過資料通路間接讀寫。包括每個資料項讀寫類的封裝和資料項使用模組的呼叫程式碼段適配。透過封裝每個資料項的讀寫類,併為每個資料項封裝了獨立的讀寫函式,和對原有呼叫程式碼的自動替換,這些工作是IDE提供的相關工具不可支援及定製的,基於Python編寫的自動化工具,降低了重構成本,並在測試及上線階段實現了零 Bug。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2995587/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 透過Python指令碼支援OC程式碼重構實踐(二):資料項提供模組接入資料通路的程式碼生成Python指令碼C程式
- 透過shell指令碼得到資料字典的資訊指令碼
- 透過shell指令碼生成查詢表資料的sql指令碼SQL
- 透過shell得到資料庫中許可權的指令碼資料庫指令碼
- 透過shell指令碼生成資料統計資訊的報表指令碼
- php百萬資料透過指令碼檔案寫入csvPHP指令碼
- 透過程式碼例項簡單瞭解Python sys模組Python
- 透過shell指令碼檢視資料庫表空間使用情況指令碼資料庫
- 資料庫監控指令碼(三)資料庫指令碼
- 透過Python指令碼理解系統程式Python指令碼
- 使用ajax指令碼取資料指令碼
- 適合時間序列資料的計算指令碼指令碼
- 三行Python程式碼,讓你的資料處理指令碼快別人4倍Python指令碼
- 應用適配資料庫還是資料庫適配應用資料庫
- 使用shell 指令碼備份資料指令碼
- Oracle多例項資料庫備份指令碼Oracle資料庫指令碼
- 利用python指令碼(xpath)抓取資料Python指令碼
- python指令碼批次建立資料表Python指令碼
- 直播間原始碼,透過Redis實現資料快取原始碼Redis快取
- 通過shell指令碼得到資料字典的資訊指令碼
- javascript引用型別資料使用程式碼例項JavaScript型別
- Python連線資料庫程式碼結構Python資料庫
- JVM的特性,透過程式碼來揭秘執行時資料區JVM
- ajax讀取資料庫資料程式碼例項資料庫
- 萬字長文解密資料異構最佳實踐(含完整程式碼實現)!!解密
- 透過cx_Oracle模組獲取資料Oracle
- GoldenGate使用Obey指令碼同步資料Go指令碼
- 基於python的大資料分析-資料處理(程式碼實戰)Python大資料
- Elasticsearch批量匯入資料指令碼(python)Elasticsearch指令碼Python
- Presto適配高斯資料庫REST資料庫
- 資料遷移指令碼指令碼
- 使用javascript清空表單元素資料程式碼例項JavaScript
- 測了一下 透過 DBCA 透過模板 複製資料庫(資料庫架構及資料)資料庫架構
- 資料結構程式碼常用模板資料結構
- 基於python的大資料分析-pandas資料讀取(程式碼實戰)Python大資料
- 基於python的大資料分析-pandas資料儲存(程式碼實戰)Python大資料
- 通過shell指令碼批量操作mysql資料庫指令碼MySql資料庫
- 【計算機系統設計】實踐筆記(2)資料通路構建:第一類R型指令分析(2)計算機筆記