透過Python指令碼支援OC程式碼重構實踐(三):資料項使用模組接入資料通路的適配

ITPUB社群發表於2023-11-16

來源:百度Geek說
作者 | 劉俊啟

導讀 
introduction

在軟體開發中,經常會遇到一些程式碼問題,例如邏輯結構複雜、依賴關係混亂、程式碼冗餘、不易讀懂的命名等。這些問題可能導致程式碼的可維護性下降,增加維護成本,同時也會影響到開發效率。這時通常透過重構的方式對已有程式碼結構進行改進和最佳化。在重構的工作中,大部分的工作是人工的方式完成,是一個耗時且容易出錯的過程。對於研發人員來講,在不改變軟體的功能和行為的前提下,保證質量和效率完成對已有功能的重構,是一個極大的挑戰。本系列以Python實現自動化的工具,支援程式碼重構過程的實踐。

在第一篇透過Python指令碼支援OC程式碼重構實踐(一):模組呼叫關係分析》的內容中,介紹了使用Python實現模組呼叫關係的分析,確定了呼叫資料項的程式碼塊超過了600處,如圖-1所示,這些呼叫點分佈在不同的元件中,是直接呼叫的關係。

透過Python指令碼支援OC程式碼重構實踐(三):資料項使用模組接入資料通路的適配

△圖-1

在第二篇透過Python指令碼支援OC程式碼重構實踐(二):資料項提供模組接入資料通路的程式碼生成》的內容中,重點介紹了使用Python實現了資料項提供模組接入資料通路時,公開資料項相關的程式碼生成(圖-2中的紅框部分),這時資料項讀寫由原來的直接讀寫方式改為透過資料通路的間接讀寫方式。

透過Python指令碼支援OC程式碼重構實踐(三):資料項使用模組接入資料通路的適配

△圖-2

資料項提供模組接入到資料通路後資料項使用模組需要進行重構,以符合資料通路的標準。重構涉及到600多處呼叫程式碼段的適配(圖-3中的紅框部分),手工重構方式成本高、出錯機率高,並且在測試時需要逐項驗證,成本也很高。為了解決這個問題,我們使用Python指令碼實現了與資料通路的通訊程式碼的生成,可自動的為每個資料項封裝讀寫函式,和自動將原有的程式碼呼叫替換為升級後的程式碼呼叫,支援不同資料項的升級。這樣做實現了本次重構工作在測試及上線階段零 Bug。

透過Python指令碼支援OC程式碼重構實踐(三):資料項使用模組接入資料通路的適配

△圖-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為使用方模組名稱,這樣就比較清楚,是那個模組中的資料項更新能力封裝。


GEEK TALK

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/,如需轉載,請註明出處,否則將追究法律責任。

相關文章