孫美榮,楊春花
(齊魯工業(yè)大學(xué)(山東省科學(xué)院)信息學(xué)院,濟(jì)南 250353)
重構(gòu)[1,2]是一種有紀(jì)律、經(jīng)過(guò)訓(xùn)練、有條不紊的程序整理方法,是現(xiàn)代軟件開(kāi)發(fā)和維護(hù)中用于提高軟件可維護(hù)性和軟件質(zhì)量的常用手段,在代碼整理過(guò)程中可以將不小心引入的錯(cuò)誤降低.
現(xiàn)代的軟件開(kāi)發(fā)一般基于版本管理系統(tǒng)進(jìn)行,軟件工程師為了維護(hù)系統(tǒng)或提高系統(tǒng)的性能,每天會(huì)提交大量的代碼.文獻(xiàn)[3]指出代碼變更伴隨著軟件系統(tǒng)的整個(gè)生命周期,不斷的代碼變更,會(huì)使軟件的復(fù)雜度大幅度的提高[4].代碼變更是指日常的bug修復(fù)、代碼重構(gòu)、功能增加,這使得代碼評(píng)審者[5]和軟件工程師在理解代碼時(shí)不得不人工對(duì)代碼進(jìn)行探查,以區(qū)分哪些變更的代碼是重構(gòu),哪些不是.
而且日志描述往往反映不了代碼變更的真正行為,廖湘科等人在《大規(guī)模軟件系統(tǒng)日志研究綜述》[6]中,通過(guò)對(duì)軟件(Apache,Squid,PostgreSQL,SVN以及Coreutils等)系統(tǒng)的失效報(bào)告進(jìn)行隨即檢測(cè),發(fā)現(xiàn)77%的系統(tǒng)失效都是常見(jiàn)的錯(cuò)誤診斷,而57%的錯(cuò)誤是沒(méi)有進(jìn)行日志記錄.
文獻(xiàn)[7] 對(duì)微軟54位資深開(kāi)發(fā)人員進(jìn)行的調(diào)研,發(fā)現(xiàn)96%的人認(rèn)為日志在軟件開(kāi)發(fā)和維護(hù)中有重要作用,認(rèn)為日志是了解變更代碼的主要信息來(lái)源,然而業(yè)界人員對(duì)日志的重視度不高,且代碼變更書(shū)寫(xiě)不規(guī)范,導(dǎo)致評(píng)審員和維護(hù)人員花費(fèi)大量的時(shí)間去定位及分析這些代碼變更.因此,為了使得變更的代碼易于理解,及提高代碼質(zhì)量,將重構(gòu)模式從代碼變更中隔離出來(lái)是非常必要的.
重構(gòu)模式的識(shí)別是在變更后的代碼中尋找符合特定重構(gòu)模式的代碼修改,是重構(gòu)的反過(guò)程.劉陽(yáng)等人[8]提出了一種重構(gòu)檢測(cè)算法,是基于版本元素匹配原理,對(duì)函數(shù)抽取重構(gòu)進(jìn)行了識(shí)別,但是并沒(méi)有涉及其它類(lèi)型的重構(gòu)模式.
本文通過(guò)對(duì)四個(gè)開(kāi)源項(xiàng)目的變更代碼進(jìn)行探查,抽取類(lèi)是最為常見(jiàn)的重構(gòu)模式.因此,本文對(duì)抽取類(lèi)重構(gòu)模式進(jìn)行了研究,提出了識(shí)別的算法.
抽取類(lèi)(Extract Class)[9]重構(gòu)模式,一般用于處理過(guò)長(zhǎng)的類(lèi).一個(gè)類(lèi)如果包含過(guò)多的功能及屬性,會(huì)導(dǎo)致這個(gè)類(lèi)過(guò)于臃腫.為了提高類(lèi)的高內(nèi)聚,低耦合,就會(huì)將一些不必要的或不經(jīng)常用的方法提煉到另一個(gè)類(lèi)中,來(lái)為這個(gè)類(lèi)服務(wù).如圖1是一個(gè)用類(lèi)圖形式表示的抽取類(lèi)模式示例.類(lèi)Person中過(guò)多的屬性oficeAreaCode和officeNumber以及功能代碼getTelephoneNumber()被抽取到了一個(gè)新類(lèi)TelephoneNumber中,且在移動(dòng)代碼的地方對(duì)新增加類(lèi)方法的引用.
圖1 抽取類(lèi)模式示例
根據(jù)上述抽取類(lèi)模式的例子,不難發(fā)現(xiàn),抽取類(lèi)模式變更具備如下3個(gè)特性:
① 變更文件中的某些屬性及方法被刪除;
② 文件中刪除的屬性和方法移動(dòng)到其它類(lèi)文件中;
③ 在原文件刪除代碼的位置有對(duì)被移動(dòng)方法的引用.
其中,①和③的判定需要基于變更代碼塊的語(yǔ)法信息進(jìn)行識(shí)別,因此我們借用了ChangeDistiller1https://bitbucket.org/sealuzh/tools-changedistiller/src/工具獲得一個(gè)變更文件中所有的代碼變更,如聲明對(duì)象:ADDITIONAL_OBJECT_STATE、插入語(yǔ)句:STATEMENT_INSERT、刪除語(yǔ)句:STATEMENT_DELETE、移除屬性:REMOVED_OBJECT_STATE.它是BeatFluri等人[10,11]編寫(xiě)的一個(gè) Tree differ 算法,將對(duì)變更前后抽象語(yǔ)法樹(shù)進(jìn)行對(duì)比,獲取分類(lèi)變更.它可以區(qū)別多種方法類(lèi)型的變化或類(lèi)等級(jí)上的變化.
而特性②的判定需要基于文本的相似性進(jìn)行識(shí)別.我們借助了Levevshtein2https://en.wikipedia.org/wiki/Levenshtein_di stance.算法,即文本相似性判斷.Levevshtein是一種計(jì)算兩個(gè)字符串間差異程度的字符串度量(string metric)算法,即一個(gè)單詞變成另一個(gè)單詞要求的最少單個(gè)字符編輯數(shù)量(如:刪除、插入和替換).那么兩個(gè)字符串的相似度算法:
Similarity=(Max(x,y)-Levenshtein)/Max(x,y)
其中,x和y為源串和目標(biāo)串的長(zhǎng)度,本文x指刪除的代碼語(yǔ)句,y指在新類(lèi)文件中的代碼行.注意:本文研究的是重構(gòu)模式識(shí)別,不是字符串的相似性,所以并沒(méi)有對(duì)該算法進(jìn)行改進(jìn).
識(shí)別方法的具體流程如下:
1)利用ChangeDistiller獲取兩個(gè)相鄰文件的所有變更類(lèi)型的詳細(xì)信息如:變更類(lèi)型、變更內(nèi)容、變更內(nèi)容所屬的雙親;
2)根據(jù)每條變更所屬雙親實(shí)體進(jìn)行分組,將相同雙親實(shí)體分到一個(gè)組內(nèi),并對(duì)每個(gè)組內(nèi)的所有刪除的語(yǔ)句和增加的語(yǔ)句按行號(hào)進(jìn)行排序,其中代碼行號(hào)通過(guò)讀取文本行獲得;
3)依據(jù)重構(gòu)模式的特性從2)所得分組中,借用Levevshtein算法進(jìn)行相似度匹配(刪除語(yǔ)句和新增文件的代碼行),查找相應(yīng)的元素,具體過(guò)程見(jiàn)下節(jié).
圖2顯示了該算法的框架,具體流程如下文.
1)代碼變更抽取
通過(guò)ChangeDistiller獲取所有的代碼變更,包括聲明對(duì)象、刪除的語(yǔ)句、原方法中新增的語(yǔ)句等.一個(gè)代碼變更由變更類(lèi)型(ChangeType)、變更實(shí)體(ChangeEntiy)和變更雙親實(shí)體(ParentEntity)構(gòu)成.
2)代碼變更分組
通過(guò)上述步驟1),每條變更可獲得一個(gè)元組的集合C={ 3)代碼塊抽取判定步驟, 將每組CS分為兩部分,令刪除部分為ldelete和增加部分為ladd,即CS=cg.ldelete+cg.ladd. ① 若父類(lèi)實(shí)體是類(lèi)等級(jí)上的變更.判斷cg.ladd是否為新增類(lèi)文件的聲明對(duì)象,若是則,再判斷cg.ldelete是否為該新增類(lèi)文件的屬性; ② 若父類(lèi)實(shí)體是方法變更.cg.ladd是否為新增類(lèi)文件的方法,若是則,再判斷cg.ldelete是否為該方法的方法體; ①、②的過(guò)程我們借用了文本相似性工具Levevshtein對(duì)相應(yīng)元素進(jìn)行判定,若以上步驟成立,則成功識(shí)別一個(gè)抽取類(lèi)重構(gòu)模式. 方法擴(kuò)展:新文件fnew獲取及相關(guān)操作,1)新增文件fnew是根據(jù)本次提交的revision_id與上次revision_id–1進(jìn)行比較獲得,若revision_id–1中的文件集不包含文件f,則認(rèn)為文件f為新增文件fnew;2)通過(guò)Java的反射機(jī)制獲取新增類(lèi)文件的屬性fnew.field、方法fnew.method、類(lèi)名fnew.class. 圖2 抽取類(lèi)思想 編寫(xiě)抽取類(lèi)重構(gòu)模式識(shí)別的偽代碼算法. 1 輸入:一次提交的版本號(hào)revision_id 2 輸出:重構(gòu)模式集合P3 獲取revision_id中所有的代碼文件集F4 for eachf∈Fdo 5 獲取f的前相鄰版本fold6 依據(jù)fold和f,獲取所有的變更集合C7 對(duì)集合C進(jìn)行分組得到集合CG8 獲取所有屬性的變更組集合CGfieldCG10 for eachcg CGdo 11 for eachfnew∈Fdo 9 獲取所有方法的變更組集合CGmethodCG 12 ifcg CGfield13 for eachcg CGfield14 獲取CGfield.CS中所有的新增語(yǔ)句cg.CS.ladd15 獲取CGfield.CS中所有的刪除語(yǔ)句cg.CS.ldelete16 ifcg.CS.ladd.entity=“FIELD”17 ∩Levevshtein(cg.CS.ladd.parententity,fnew.class)18 ifcg.CS.ldelete.entity=“FIELD”19 ∩Levevshtein(cg.CS.ldelete.conten t,fnew.field)20 end if 21 end if 22 end for 23 else ifcg CGmethod24 for eachcg CGmethoddo 25 獲取CGmethod.CS中所有的新增語(yǔ)句cg.CS.ladd26 獲取CGmethod.CS中所有的刪除語(yǔ)句cg.CS.ldelete 27 ifcg.CS.ladd.entity=“RERURN_STATEMENT”28 ∩Levevshtein(cg.CS.ladd.parententity,fnew.method)29 ifcg.CS.ldelete.entity=“RERURN_STATEMENT”30 ∩Levevshtein(cg.CS.ldelete.content,fnew)31 end if 32 end if 33 end for 34 end if 35 end if 36 成功識(shí)別一個(gè)抽取類(lèi)模式p 37 end for 38 end for 39 end for 40P=P∪{p} 算法偽代碼中第4行指遍歷集合F中的所有文件f;第5行通過(guò)ChangeDistiller可知f是否為變更文件;第8行指獲得集合CG中的屬性集合;第9行指獲取集合CG中的方法集合;16~19指在變更類(lèi)型為“FIELD”的情況下,判斷屬性是否移動(dòng)到fnew中;27~30 指在變更類(lèi)型為“RERURN_STATEMENT”情況下,判斷刪除的方法及方法體是否移動(dòng)到新文件fnew中,且在刪除代碼的位置有對(duì)該方法的引用;第36行若以上步驟為真,則成功識(shí)別一個(gè)抽取類(lèi)模式;第40行返回所有識(shí)別成功的重構(gòu)模式集. 我們通過(guò)minigit3https://github.com/SoftwareIntrospectionLab/MininGit工具獲取了四個(gè)開(kāi)源項(xiàng)目jEdit4https://github.com/linzhp/jEdit-Clone,eclipse JDT Core5https://github.com/eclipse/eclipse.jdt.core,Apache maven6https://github.com/apache/maven/,and googleguice7https://github.com/google/guice/,在某段時(shí)間的變更歷史,該工具將該時(shí)間段內(nèi)所有的變更信息(包括所有提交的版本、每個(gè)版本的日志、源文件等)抽取到了MySQL數(shù)據(jù)庫(kù)中. 我們前期通過(guò)人工分析變更日志和源代碼探查,判定了一些存在重構(gòu)模式的版本.項(xiàng)目的信息及人工判定的重構(gòu)版本數(shù)據(jù)見(jiàn)表1. 表1 開(kāi)源項(xiàng)目詳細(xì)信息表 通過(guò)人工檢測(cè)4個(gè)開(kāi)源項(xiàng)目,獲得抽取類(lèi)重構(gòu)模式的數(shù)目分別為:jEdit中含有24個(gè)版本,maven中含有69個(gè)版本,goole_guice中含有22個(gè)版本,eclipse中含有6個(gè)版本. 圖3、4是取自項(xiàng)目maven版本68ca923 中DefaultMetadataResolutionReques.java(left.java)和DefaultRepositoryRequest.java(right.java)的部分變更內(nèi)容;其中減號(hào)表示刪除代碼行,加號(hào)表示增加代碼行.圖5是該算法對(duì)這一變更檢測(cè)的信息輸出.其中ChangeDistiller獲取聲明對(duì)象FIELD、刪除代碼,以及文本讀取代碼塊行號(hào). 該實(shí)驗(yàn)對(duì)表1中的數(shù)據(jù)進(jìn)行驗(yàn)證,通過(guò)實(shí)驗(yàn)后檢測(cè)得到的實(shí)驗(yàn)結(jié)果,見(jiàn)表2. 圖3 left.java 圖4 right.java 通過(guò)表2可以得出,該試驗(yàn)進(jìn)行的抽取類(lèi)模式識(shí)別,其平均準(zhǔn)確率為82.6%,準(zhǔn)確率在77.3%~91.6%之間略有波動(dòng).準(zhǔn)確率沒(méi)有達(dá)到100%的原因與借用的代碼相似性比較算法有關(guān).因?yàn)楸粶y(cè)文本是重構(gòu)代碼變更文本,所以被提取的代碼塊并不是簡(jiǎn)單的復(fù)制/粘貼.程序員在進(jìn)行代碼重構(gòu)時(shí),為了提高代碼質(zhì)量的可理解性、可維護(hù)性和可擴(kuò)展性,被提取的代碼行中的有些元素可能被替換,如新增方法中的參數(shù)、局部變量等,但整個(gè)操作并不會(huì)改變軟件功能.因此,對(duì)本文反過(guò)程重構(gòu)模式識(shí)別中代碼塊提取過(guò)程會(huì)有些影響. 圖5 實(shí)驗(yàn)結(jié)果 表2 實(shí)驗(yàn)結(jié)果 我們提出了一種基于變更類(lèi)型和文本相似性比較的重構(gòu)模式識(shí)別方法,并設(shè)計(jì)和實(shí)現(xiàn)了對(duì)抽取類(lèi)(Extract Class)模式的識(shí)別.通過(guò)實(shí)驗(yàn)驗(yàn)證,該方法可以比較準(zhǔn)確地識(shí)別Extract Class模式. 除了Extract Class模式,該方法還適用于其它存在代碼移動(dòng)的重構(gòu)模式,如Extract Superclass,Move Interface等.后續(xù)工作包括將該方法應(yīng)用于這些模式的識(shí)別.2 算法
3 實(shí)驗(yàn)驗(yàn)證
3.1 數(shù)據(jù)源
3.2 結(jié)果及實(shí)驗(yàn)分析
4 結(jié)束語(yǔ)