項目中有一個需求,對一個基類而言,擁有一個比較方法和拷貝方法,某些地方需要頻繁地對這兩個方法進行調用。對於所有子類而言,需要重寫這兩個方法,並在其中維護類內一些成員變量。例如有一個變量m_iMyVal,在Copy方法中需要維護this.m_iMyVal = data.m_iMyVal;在IsEqual方法中需要維護if(this.m_iMyVal != data.m_iMyVal) return false;...等等。當子類中的變量一旦多了,譬如一個類中擁有十余個甚至更多這樣的變量,並且日后有可能刪除或添加,維護起來就有點痛苦了,因為需要在兩處都補充對應的方法,而且每一個都不能寫漏寫錯。
這些子類一般都是數據類,在編寫代碼的時候往往是一口氣寫相當多的變量,如果寫一個然后到兩個地方編寫對應的代碼,這樣可能的確出錯率比較低,但是很容易打斷思路;但是把所有變量寫完后再去補充代碼,就會有寫漏寫錯之嫌,一旦寫漏當時可能沒有bug,后期查錯起來非常痛苦,從而導致開發效率下降。另外麻煩的一點在於,不同類型的變量,比較方法和拷貝方法可能不盡相同(如浮點數需要eps,list需要循環比較等等)。
那么有沒有寫一個變量然后告訴系統這個變量是需要在比較和拷貝方法中補充代碼的,然后有“人”幫我自動補充上去呢?由於筆者使用的是C#語言開發,自然而然想到了添加標簽的方法,之后通過反射找到所有帶有這種標簽的成員變量,在比較和拷貝方法中直接進行操作即可。
但是擔心這種做法由於反射有性能較低之嫌,因此暫時不作考慮,轉而使用下面這種利用Python自動生成代碼的方法。
利用Python讀取文件,正則查詢帶有某些標簽或者注釋的變量,把這些變量收集起來,然后替換原文件的比較和拷貝方法即可。
那么工作流就變成了:
1.寫需要的成員變量
2.如果需要自動為其生成代碼,則添加對應的標簽(不采用注釋的方法是因為標簽可以被自動補全,防止正則找錯)。
3.執行批處理文件,自動把需要的文件生成代碼(如果之前有這部分代碼,則是替換)。
整個過程幾乎不耽誤什么時間,但是自動生成代碼使得開發效率大大提升。
Python源碼如下:

1 # -*- encoding:utf-8 -*- 2 3 import os 4 import sys 5 import re 6 7 8 def process_file(path): 9 keyword = 'debug_util.' 10 modify_flag = False 11 12 auto_start = "// python automatic generation start" 13 auto_end = "// python automatic generation end" 14 normalLabel = "\[NormalLabel\]" 15 floatLabel = "\[FloatLabel\]" 16 vec3Lable = "\[Vec3Label\]" 17 18 normalCmp = "[NormalLabel]" 19 floatCmp = "[FloatLabel]" 20 vec3Cmp = "[Vec3Label]" 21 22 with open(path, 'rb') as f: 23 txt = f.read() 24 # print ("txt is " + txt) 25 pattern = "(%s|%s|%s)[\s\S]+?public\s+\w+\s+(\w+)" % (normalLabel, floatLabel, vec3Lable) 26 matchStr = re.findall(pattern, txt) 27 # print (matchStr) 28 labelList = [] 29 varList = [] 30 for i, packed in enumerate(matchStr): 31 labelName = packed[0] 32 varName = packed[1] 33 labelList.append(labelName) 34 varList.append(varName) 35 # print (labelName + ".." + varName) 36 37 # 開始找類的名稱 38 pattern = "public class (\w+)\s*:\s*SkillBaseData" 39 matchStr = re.findall(pattern, txt) 40 className = "" 41 for i, packed in enumerate(matchStr): 42 className = packed 43 break # 類名只會有一個 44 45 46 ## 找注釋部分 47 index = 0 48 begin = txt.find(auto_start, index) + len(auto_start) 49 end = txt.find(auto_end, index) 50 copyContent = "" 51 equalContent = "" 52 for labelName, varName in zip(labelList, varList): 53 copyContent += "\t\t\tthis." + varName + " = data." + varName + ";\n" 54 if labelName == normalCmp: 55 equalContent += ("\t\t\tif(this.%s != data.%s) return 1;\n" % (varName, varName)) 56 elif labelName == floatCmp: 57 equalContent += ("\t\t\tif(FloatNotEqual(this.%s, data.%s)) return 1;\n" % (varName, varName)) 58 elif labelName == vec3Cmp: 59 equalContent += ("\t\t\tif(Vec3NotEqual(this.%s, data.%s)) return 1;\n" % (varName, varName)) 60 61 62 auto_code = "" 63 auto_code += (("\n\t\tpublic override void CopyDataFrom(SkillBaseData baseData)\n\t\t{\n\t\t\tvar data = (%s)baseData;\n" % className) + copyContent + "\t\t}\n") 64 auto_code += (("\n\t\tpublic override int IsEqual(SkillBaseData baseData)\n\t\t{\n\t\t\tvar data = (%s)baseData;\n" % className) + equalContent + "\t\t\treturn 0;\n\t\t}\n") 65 print (txt[:begin] + auto_code + txt[end:]) 66 new_txt = txt[:begin] + auto_code + "\t\t" + txt[end:] 67 with open(path, 'wb') as f: 68 f.write(new_txt) 69 70 def okPath(path): 71 if path.endswith("TestSkillData.cs"): 72 return True 73 74 return False 75 76 if __name__ == '__main__': 77 cwd = os.getcwd() 78 _len = len(cwd) - 22 # 22是最后一級目錄的長度,在這里是魔法數字,據需求而改動,當然也可以用其他方法找到需要的目錄= =。 79 cwd = cwd[:_len] + 'SkillDerivedData' 80 print(cwd) 81 # cwd = cwd + '/Package/Script/Python/' 82 directiory = os.walk(cwd) 83 for root, dirs, files in directiory: 84 for f in files: 85 if okPath(f): 86 process_file(os.path.join(root, f)) 87 # print("name is " + f)
寫完C#直接跑下面這個bat即可。
最后生成代碼的效果如下圖所示,感覺還算比較美觀的= =。(針對三種不同的標簽提供了不同的處理方法):
總的來說,學會了一種新的處理問題的方法,收獲挺大的。最后感謝一下教會我這種方法的@倉鼠和提供正則幫助的@聰哥。