作者 | 劉俊啓
導讀
在軟件開發中,經常會遇到一些代碼問題,例如邏輯結構複雜、依賴關係混亂、代碼冗餘、不易讀懂的命名等。這些問題可能導致代碼的可維護性下降,增加維護成本,同時也會影響到開發效率。這時通常通過重構的方式對已有代碼結構進行改進和優化。在重構的工作中,大部分的工作是人工的方式完成,是一個耗時且容易出錯的過程。對於研發人員來講,在不改變軟件的功能和行為的前提下,保證質量和效率完成對已有功能的重構,是一個極大的挑戰。本系列以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代碼重構實踐(一):模塊調用關係分析