張生棟,吳海濤,高建華
(上海師范大學 計算機科學與技術系,上海 200234)
軟件開發過程中,軟件維護的工作量和成本比起其它過程要大得多,而代碼異味的存在使得軟件的可維護性大大降低.代碼異味又稱代碼的壞味道,是開發人員在設計代碼時的不良選擇,開發人員的編程習慣、經驗不足或者是迫于在規定日期內完成項目的交付等原因都會引入代碼異味.Martin Fowler[1]定義了22種代碼異味,并詳細介紹了每一種代碼異味的重構步驟.近年來,代碼異味的研究受到重視,檢測代碼異味的方法層次不窮[2-5],并且異味的關聯從不同角度被廣泛研究.
Palomba[6,7]等人研究發現社區異味和代碼異味之間存在著相關性,他們從社會和技術債務中使用混合方法經驗收斂性評估提供兩者之間存在關系的證據,并且設計了一個預測模型,使用社區異味和幾個來自最新技術的已知社區相關因素來預測代碼異味強度.Tufano[8]等人對測試異味和代碼異味的共現進行了實證分析,實驗結果表明,部分測試異味與代碼異味之間存在著相關性,比如Assertion Roulette(斷言輪盤賭)和Spaghetti Code(意大利面代碼).Cardoso[9]和Sousa[10]都探討了設計模式和代碼異味之間的關系,并且都發現設計模式的使用不能避免代碼異味的存在.
Walter和Pietrzak[11]兩人使用了多標準的方法檢測代碼異味,其中有一條檢測的標準是:是否有其他代碼異味的存在.他們認為,一種代碼異味的存在可能意味著相關代碼異味的存在,這是由于某些代碼異味的共同起源:一個代碼錯誤會導致許多設計錯誤,并且所產生的代碼異味并不是相互獨立的.Fontana[12]等人在74個系統上對6種代碼異味進行實驗,深入研究了代碼異味之間的共存,他們發現Brain Method(大腦方法)容易與其他代碼異味共存,同時比較意外的結論是God Class和Data Class(數據類)并沒有發現共存現象.Pietrzak和Walter[13]定義分析了代碼異味之間的6種關系,并提出通過這些關系來減輕其他代碼異味的檢測工作量.Lozano和Mens[14]等人分析了3個開源系統,報告了4種代碼異味中發現的5種可能關系(簡單支持、相互支持、拒絕、公共重構、包含)的證據.發現Feature Envy和Long Method的相關性最強.
Abbes[15]等人首次提出了代碼異味交互的概念.他們實驗發現God Class(上帝類)和God Method(上帝方法)單獨存在時,對系統的影響不是很大,但是當他們一同存在時,對系統質量就會造成很大的影響.Yamashita和Moonen[16]等人根據實驗研究了12種代碼異味的交互,并分析了這些交互和維護問題之間的關系,他們使用自動檢測工具檢測代碼異味,并用主成分分析方法分析代碼異味,以識別共存的代碼異味模式.隨后Yamashita[17]等人在兩個開源系統和一個工業系統中對代碼異味之間的交互做了實驗,發現代碼異味之間的交互在不同的系統上表現是不一樣的.
Garg[18]等人通過位圖(Bitmap)的方法分析了兩個系統的7種代碼異味的關聯,通過百分比的形式比較了兩個系統中兩兩代碼異味共同出現的概率,他們發現每個系統都會出現代碼異味共現現象,同時他們發現Data Clumps、Internal Duplication(內部重復代碼)、External Duplication(外部重復代碼)比較常見.
Palomba[19,20]等人在30個系統一共395個版本上,針對13種代碼異味使用關聯規則算法得到了6個共存的代碼異味對.并且發現代碼異味同時出現的現象非常普遍,59%的臭味類受多個代碼異味的影響.
Walter和Fontana[21]等人采用成對相關分析、主成分分析和關聯規則3種方法,對92個java系統中檢測到的14種代碼異味的頻繁搭配進行了識別和實證驗證.
本文的目標是在前人的研究基礎上,針對主成分分析在布爾變量分析中會產生難以解釋的主成分含義的弊端,賦予代碼異味嚴重性,利用因子分析進行實驗,與主成分分析比較,并解釋每個因子的含義.
本文的主要工作如下:
1)根據代碼異味檢測工具檢測的不一致性,對數據集進行處理,將檢測到代碼異味為真的工具占檢測該代碼異味工具的比例作為該代碼異味的嚴重性值.
2)利用因子分析法對改進的92個系統的數據集上進行實驗.
3)分析比較實驗結果,并對得到的因子進行解釋和分類命名.
本文的結構如下:第2章介紹了本文研究的代碼異味和因子分析的相關概念;第3章研究了代碼異味的嚴重性賦值和本文用到的方法;第4章進行實驗和對實驗結果進行分析及解釋分類命名;第5章總結本文并指出將來的工作.
代碼異味是程序開發領域代碼的不良設計,可能會導致深層次問題的癥狀.繼Fowler[1]定義了22種代碼異味之后,Lanza和Marinescu[22]將代碼問題大致分為3類,并新定義了幾種代碼異味.本文研究了14種代碼異味,其中有7種代碼異味由Fowler[1]定義,6種由Lanza 和Marinescu[22]定義,另外1種由Trifu和Marinescu[23]定義,表1中列出了每種代碼異味及其描述(后跟定義者).

表1 代碼異味及其描述Table 1 Code smells and its description
因子分析是多變量降維統計技術.它通過將緊密相關的變量歸為同一個因子,以實現通過少量因子就可以盡可能表達原始數據中所有變量表達的信息的目的.
因子分析和主成分分析(Principal Components Analysis,PCA)都運用了降維的思想,因子分析的基本原理是提取對表達整個數據集信息具有最大貢獻的公共因子和不屬于公共因子但又不可忽略的特殊因子,然后兩者線性組合表示變量.而主成分分析的基本原理是將用原始變量線性組合表示主成分,且各個主成分之間獨立.因子分析是在主成分分析基礎上的擴展,相對于后者,前者對于變量之間的關系的關注更為側重.
圖1給出了因子分析的流程圖,進行因子分析首先需要確定所要分析的變量和樣本,通過計算變量間的相關矩陣、KMO值和Bartlett′s值來檢驗能否進行實驗,驗證可以則進行因子提取,提取后的因子經過旋轉后得到易于解釋的因子.

圖1 因子分析流程Fig.1 Process of factor analysis
2.2.1 因子分析模型
設有m個原始變量xi(i=1,2,…,m),它們之間可能相關,也可能獨立,將xi標準化得到新變量yi,則可以建立因子分析模型如式(1)所示:
yi=ai1F1+ai2F2+…+ainFn+ciSi(i=1,2,…,m)
(1)
其中Fj(j=1,2,…,n)線性組合表示每個變量,稱為公共因子,Si(i=1,2,…,m)僅與變量yi有關,稱為特殊因子,aij為系數,ci(i=1,2,…,m,j=1,2,…,n)稱為因子負荷,A=(aij)稱為因子矩陣.
可以將式(1)表示為如式(2)所示的矩陣形式:
y=AF+CS
(2)
其中,標準化后的變量y=(y1,y2,…,ym)T,因子矩陣A=(aij)m×n,公因子F=(F1,F2,…,Fn)T,因子負荷C=diag(c1,c2,…,cm),特殊因子S=(S1,S2,…,Sm)T.
2.2.2 指標檢驗數據
本文采用相關矩陣、KMO值、Bartlett′s值3個指標檢驗數據集能否用來進行因子分析.相關矩陣也稱相關系數矩陣,表示了兩兩變量之間的相關程度,是表示變量之間是否存在關系的最基本最常用的指標.相關系數由變量之間的標準差和協方差計算得來,通常用r來表示,其基本公式如式(3)所示:
(3)
KMO(Kaiser-Meyer-Olkin)值是本文的第二個檢驗指標,由于本文研究的代碼異味之間關系屬于多變量之間的關系,僅僅考慮簡單相關系數會有很大的誤差,需要同時考慮偏相關系數,KMO值便是對兩者的比較.其值是0~1區間的數值,通過比較簡單相關系數的平方和值和偏相關系數的平方和值來確定KMO值的大小.當前者比后者越大,KMO值越大,越趨近于1,同時也表明變量之間具有很強的相關性,反之則趨近于0,變量之間趨向于無相關性.0.5是區分有無相關性的標志,0.5~1區間內值越大越適合進行因子分析,0.5以下則不具備因子分析的條件.本文將0.5作為閾值,大于0.5表示可以進行因子分析.
Bartlett′s球度檢驗以變量的相關矩陣為切入點,其作用是檢驗數據的分布,以及各個變量間的獨立情況,其顯著性概率值小于0.05時才認為數據有效,可以進行因子分析.
2.2.3 公因子提取
公因子提取是在復雜的變量中提取最能夠表示整個數據集信息的公因子,每個公因子充分體現了變量之間存在的相關關系.多種方法可以用來提取公因子,其中主成分分析法、極大似然法、最小二乘法、主軸因子法和Alpha因子分析法等是比較常用的方法.本文使用的是主軸因子法,該方法從相關矩陣出發,重在解釋變量的相關性.
2.2.4 因子旋轉
公因子提取過程中不僅提取了公因子,而且得到了每個公因子中變量的公因子方差.此時提取的公因子并不能很好地解釋,需要進行旋轉來使得因子矩陣更加簡化,從而可以使得因子可以更直觀的被解釋,在因子旋轉的過程中,并不會影響提取的公因子的個數.
變量正交的正交旋轉和變量非正交的斜交旋轉是因子旋轉的兩種方法,其中正交旋轉有四次方最大法、最大方差法和均等變化等方法,斜交旋轉有直接斜交法和最優斜交法等方法.本文采用的正交旋轉的最大方差法,其主要原理是通過最大化各因子負載的平方的方差來簡化矩陣.
不同代碼異味檢測工具的檢測能力各不相同,主要是由于各個工具的檢測策略以及檢測規則閾值不同造成的,本文選用6種代碼異味檢測工具和3種方法對14種代碼異味進行檢測.
本文中的代碼異味檢測采用了不同的檢測工具,對于同一個類,假設需要進行檢測某代碼異味C,設有N個檢測代碼異味C的工具分別記為T1,T2,…,TN,其中檢測結果中檢測到代碼異味C存在的工具記為Tt1,Tt2,…,Ttn,個數記為n,檢測不到代碼異味C存在的工具記為Tf1,Tf2,…,Tfm,個數則記為m,可知N=n+m.本文定義代碼異味嚴重性值如式(4)所示:
SV=n/N
(4)
即檢測到代碼異味C存在的工具個數占所有檢測代碼異味C的工具個數的比值.代碼異味本身沒有明確的定量定義,也不存在一種權威的工具明確表明某類是否存在代碼異味.因此對于檢測結果不一致的情況,如果一個代碼異味同時被多數工具檢測到,可以認為這種代碼異味具有比較高的嚴重性,如果一個代碼異味僅有個別工具檢測到,可以認為該代碼異味嚴重性并不是很高,用n/N來代表其嚴重性既沒有忽略代碼異味存在的可能性,也沒有將處在閾值模糊界限上的類強行歸為布爾類型上的代碼異味.對于只有一種代碼異味工具檢測的代碼異味,本文仍然按照布爾類型處理.
主軸因子法提取因子的特點是從變量的相關矩陣出發,提取的公因子能夠盡可能的使得其中的變量相關,并且易于解釋其中的關系和結構.而主成分分析的切入點在于變量的方差,盡量使提取到的主成分解釋變量的方差,其分析過程如圖2所示.

圖2 主軸因子分析過程Fig.2 Process of principal axis factor analysis

3)求出φ的估計值φ(1)=R-A1A1′;
4)返回第一步用φ(1)代替φ,直到A的值和φ的值達到穩定為止.
算法1.主軸因子法提取因子算法
輸入:數據集D
輸出:因子矩陣

2. forA的值和φ的值穩定

4.前q個大于0的特征值(λ1,λ2,…,λm)>0及特征向量E1,E2,…,Em←|R*-λI|=0//I為單位矩陣

6. End for

本節對92個系統上的代碼異味進行實驗,實驗主要尋求解決以下幾個問題:
Q1:得到的因子能否從以往的論文中找到依據?
Q2:本文采用的主軸因子法是否比主成分分析更容易解釋變量的關系?
Q3:改進的數據集比原始數據有什么優勢,是否更加適合進行因子分析?
本文的實驗環境如下:操作系統是Windows 10,處理器是Intel(R)Core i5-4258U CPU @ 2.4GHz,內存4GB,實驗是在SPSS和PyCharm中完成,開發語言是Python.
本文用到的數據集是由6種代碼異味自動檢測工具和3種方法對92個系統中的14種代碼異味進行檢測得到,表2中報告了這6種工具和3種方法能夠檢測到的代碼異味.

表2 代碼異味檢測工具Table 2 Code smells detecting tools
進行因子分析的首要條件就是要保證變量之間存在相關性,以保證因子分析有意義,本文通關變量之間的相關矩陣、KMO測度和Bartlett′s球度檢測進行因子分析可行性檢驗.
4.3.1 相關矩陣
因子分析的第一步便是計算相關矩陣,表3展示了帶嚴重性值的代碼異味的相關矩陣.
本文采用|ρ|≥0.3作為閾值,表明至少存在弱相關性(Cohen[24],1988),正值表明兩個代碼異味之間經常一同出現,負值則表明一種代碼異味的存在通常排除另一種代碼異味.
有8對代碼異味滿足設定的閾值,表中數值加粗表示.其中{Extensive Coupling、Long Parameter List}、{Long Parameter List、God Class}在原始數據集中沒有出現,表中數值下劃線加粗表示.{Long Parameter List、God Class}代碼異味對在Walter[11]中有被實驗證實;{Extensive Coupling、Long Parameter List}只在Walter[21]的實驗中出現過,此代碼異味對單獨出現在DGDV(圖表生成器/數據可視化)數據集中,被認為是一種特殊情況,而本文的實驗表明這種代碼異味對并非是一種特例.同時發現Extensive Coupling代碼異味與Brain Class、God Class、Shotgun Surgery以及Long Method的相關值都接近于閾值0.3,說明Extensive Coupling與其他代碼異味關系緊密,這也與Extensive Coupling的定義相吻合.

表3 相關矩陣Table 3 Correlation matrix
4.3.2 KMO測度和Bartlett′s球度檢測
表4給出了KMO測度和Bartlett′s球度檢測的結果,其中KMO值為0.712,高于設定的0.5的標準;Bartlett′s球度檢測的顯著性值為0,低于0.05的標準,二者均滿足因子分析要求.

表4 KMO和Bartlett′s檢驗Table 4 KMO and Bartlett′s test
通過主軸因子法提取的因子矩陣需要通過旋轉后才使因子更加簡化和易于解釋,本文采用的旋轉方法是凱撒正態化最大方差法.表5是詳細的旋轉后的因子矩陣.
通過旋轉后的因子分析矩陣可以看出有6個因子被提取出來,圖3展示了提取的因子.下面對每一個因子中的代碼異味進行討論和分析.

表5 旋轉后的因子矩陣Table 5 Factor matrix after rotation

圖3 提取的因子Fig.3 Extracted factors
因子1.Brain Class、Long Method和God Class被歸為了因子1,Brain Class和God Class的定義很相似,都是傾向于集中系統功能的類,代碼異味檢測工具對他們的檢測策略都是基于大小度量的,所以檢測為Brain Class的類很大可能性上被檢測為God Class,Brain Class和God Class中的功能太過繁雜,包含了太多的方法,Long Method也會出現于此,從定義上看該方法也是過于繁雜,需要大量的代碼行、大量的變量和參數,Brain Class和 God Class更符合其存在條件,因此Long Method通常伴隨著Brain Class和God Class的存在而存在.3個代碼異味的共同點就是代碼繁雜,囤積了太多的功能.Yamashita[17]定義這種囤積了太多功能的類為“Hoarders”,在他的分類中將Feature Envy歸為“Hoarders”,通過本實驗的結果和對Feature Envy的定義來看均不支持歸為“Hoarders”,反而Long Method更符合要求.
因子2.Intensive Coupling、Long Parameter List、Extensive Coupling和Shotgun Surgery被歸為因子2,符合在Yamashita[17]定義的第3種關系“Wide interfaces”,其描述為如果維護任務需要在顯示這些異味的類中進行任何修改,則這些更改將導致由于功能耦合分散而產生的意外副作用.Extensive Coupling、Shotgun Surgery和Intensive Coupling根據定義可知3種代碼異味的存在主要是由于類的耦合,至于Long ParameterList可以理解為大量參數的引入導致“被調用對象”與“較大對象”間存在依賴關系,使得Long Parameter List代碼異味耦合嚴重,本文將這一因子不再定義為“Wide interfaces”,而是定義為“Coupling”.
因子3.Feature Envy和Data Class被歸為因子3,其中Data Class顯示負負荷,并不說明Data Class與此因子無關,正負與影響的大小沒有關系,負號表示Data Class與此因子呈負相關.根據Feature Envy的定義,兩個代碼異味之間確實存在著關聯,結果卻比較意外,兩者一般不會出現在同一個類中,標記為Feature Envy的類,一般不會再標記為Data Class,標記為Data Class的類同樣也很少會被標記為Feature Envy.這種情況很符合Pietrzak[13]等人定義的“Rejection”關系.
因子4.Tradition Breaker和Refused Parent Bequest被歸為因子4,從這兩個代碼異味的定義上看歸為一個因子也在預料之中,兩個代碼異味都是由于子類中除了父類的方法外含有其他的方法,都屬于繼承體系設計錯誤,因此本文將這一因子命名為“Inheritance extension”.
因子5.God Class被單獨歸為因子5,因子1中同樣存在代碼異味God Class,本文認為此代碼異味被單獨分離出來可能是由于該代碼異味出現頻率很高而且會使用其他類的數據,此代碼異味會與其他因子存在關聯,比較明顯的是因子1相關,經過斜交實驗發現因子1中的God Class被去除,也證明了兩個因子存在著關聯.
因子6.Feature Envy被單獨歸為因子6,Feature Envy的存在幾乎貫穿了整個數據集,根據統計,本文數據集中70%的類中存在該代碼異味,有如此高的占比率,很大程度上是由于檢測工具將被調用的類(Data Class除外)也標記為Feature Envy,Feature Envy更像是滴在清水里的墨水,很容易與其他類耦合在一塊.
表6總結了本文得出了幾種關系.同時可以先對Q1進行解答,本文得到的前4個因子都可以從過往的論文中找到依據,其中因子1、2、3的關系都有論文進行總結分類,因子4沒有總結,但是因子中變量的關系明確,因子5、6則無法找到充足的依據.

表6 代碼異味的關系和描述Table 6 Relationship and description of code smells
本節對主成分分析提取的因子和本文用到的方法得出的結果做了對比,同時對比了在主軸因子方法下,原始數據集與帶有嚴重性的異味數據集下的模型的好壞,并對Q2、Q3進行解答.
針對Q2:表7展示主成分分析提取的因子,可以看出,主成分分析下的前兩個因子中變量都比主軸因子法得出的結果多.特別地,盡管都進行了因子旋轉,但是主成分提取的因子1和因子2中都存在變量Extensive Coupling,從相關矩陣可以發現Extensive Coupling代碼異味與因子1中其它3種代碼異味相關系數都接近0.3,但構不成弱相關,可以視為一種極弱相關,本文的方法忽略這種極弱相關,不僅可以使不同因子中的變量盡可能獨立,而且簡化了因子變量,使得因子1更易于解釋;對于因子2,主成分分析提取的因子2變量中多了Schizophrenic Class代碼異味,結合相關矩陣,發現Schizophrenic Class與其他異味并不存在明顯的相關關系,可以認為本文方法得到的結果更具有可靠性;對于主成分分析提取的因子5中的Brain Class和Schizophrenic Class并未在相關矩陣中找到兩者存在相關關系的證據.綜合對比表5可以看到本文用到的方法提取的因子更加簡化,忽略了一些極弱相關,變量之間的關系更容易解釋.主軸因子法比起主成分分析法的優勢是忽略了特殊因子的影響,主成分分析得到的特殊因子各分量之間存在關聯,不滿足因子分析的前提條件,從而會使因子矩陣存在偏差[25].通過觀察表7可以看到主成分分析得到的公因子方差并沒有達到很高的水平,存在很多0.3~0.8的公因子方差值,這就說明無法忽略特殊因子的影響.同時本文的目的側重于確定代碼異味關系的內在結構,主軸因子法提取公因子正是以變量的相關矩陣為出發點確定其內在關系,而非側重變量的方差,因此得到的結果結合相關矩陣來看比主成分法提取的結果更簡化且容易解釋.
針對Q3:本文從相關矩陣代碼異味對、平均初始變量方差和平均KMO值3個方面對兩個數據集的結果進行了比較,如表8所示.

表7 主成分提取的因子矩陣Table 7 Factor matrix of principal component extraction

表8 數據集結果對比Table 8 Dataset results comparison
兩個數據集的相關矩陣得出的代碼異味對都是8對,原始數據集對于Feature Envy的關系提取的更多,主要原因是Feature Envy廣泛存在于整個數據集,占比率達到了70%,沒有嚴重性值約束下,更容易將其與其他代碼異味關聯.本文的數據集得出了兩對代碼異味{EC、LPL}和{LPL、GC}是原始數據集沒有得到的,其中{EC、LPL}在之前的論文中都沒有提及,唯一提及的是Walter[21]在一個特殊數據集中出現過這種異味對,但并沒有進行總結.
原始有數據集的平均初始變量方差為53.63%,比本文帶有嚴重性值的數據集的60.44%少了將近7%,之所以考慮初始變量方差而不是最終的旋轉載荷方差,是因為本文用的方法出發點不在于變量方差.
對于平均KMO值,本文數據集達到了0.718,比起原始數據集的0.693也要好了很多.
本文對帶有嚴重性值的代碼異味進行相關性分析,將檢測到代碼異味的檢測工具的比例作為代碼異味的嚴重性值,然后運用因子分析中的主軸因子提取因子的方法,對代碼異味進行相關性分析,從相關矩陣中得到了一種新的代碼異味對,同時得到6個因子,并對其進行解釋和分類,最后與主成分分析法和原始數據集的結果進行了比較,得出本文的方法可以對因子很好的解釋,數據集也更加適合因子分析.本文研究的代碼異味的相關性是以類為研究對象,沒有考慮由于耦合類之間的代碼異味的相關性,將來可以以包為研究對象,研究類之間的代碼異味的關聯.