黃子杰 ,陳軍華 ,高建華
1(上海師范大學 計算機科學與技術系,上海 200234)
2(華東理工大學 計算機科學與工程系,上海 200237)
JavaScript(簡稱JS)是一種使用弱類型和動態類型的解釋型編程語言,JS 程序的結構和變量類型在運行時才會明確,因此,大量為Java 等強類型語言設計的靜態分析手段難以奏效,導致其代碼質量缺乏工具保障.同時,它還包含諸多增強語言靈活度(flexibility)的特性,例如原型鏈、閉包、頭等函數等,它們形成了復雜的代碼組件繼承、組合方式.因此,編寫JS 程序頗具挑戰性,期間易產生Code Smell 和Bug[1,2].
Code Smell 是軟件程序中存在不良設計和不良實現的征兆[3],其強度可由度量指標(metric)量化.正確地檢測和量化Code Smell 可以指導軟件重構,提高軟件的可用性和可靠性.
然而,現有Code Smell 度量不足以理解JS 程序的復雜性和程序質量,常見的Code Smell 度量很少被JS 相關的研究使用[4],需對其進行開發或改進,以適應JS.目前已出現面向JS 的Code Smell 檢測工具,例如SonarQube,JSNose[1],它們僅面向微觀層面上(即函數、語句)的內聚或耦合問題.
JSNose 主要討論了函數、代碼塊及對象內部的Code Smell,并引入了3 種通用類Code Smell 的檢測方式來檢測對象內部的問題,例如過大對象和不當的繼承,但研究不涉及多個對象間的設計問題.研究還提及了一種文件層面的Code Smell,它面向JS,HTML 和CSS 這3 種語言在代碼中混寫的耦合問題.Saboury 等人[5]根據語言標準的新變化,擴充并修正了JSNose 的定義,此類修正主要發生在語法和語句層面上,涉及作用域和閉包等問題.該研究還就Code Smell 對JS 代碼易錯性(fault-proneness)的影響排序,結果顯示,部分涉及不當賦值的Code Smell 更易引發程序錯誤.
上述微觀研究可以提出程序實現的重構建議、局部提升程序的質量,但無法有效分析粒度更大的(例如類、模塊)設計問題.Palomba 等人[6]發現:因為大粒度的代碼組件過于復雜,模塊和類層面的Code Smell 相比函數Code Smell 更難被有效地識別,解決這類問題的效率和成功率極低,因此需要理論和工具輔助.
類(class)是JS 的一種常見設計模式,在Silva 等人[7]抽選的60 個JS 程序樣本中,68%使用了類,34%較頻繁或頻繁地使用了類.優秀的類設計體現為高內聚和低耦合[8],內聚(cohesion)和耦合(coupling)可用于衡量面向對象程序功能分布的合理性,內聚是軟件組件內功能的關聯程度,耦合是軟件組件間交互的頻繁程度.
由于JS 類的信息檢測存在困難,導致部分Code Smell 的通用檢測方法難以復用,因此Code Smell 的相關工作仍未充分涉及JS 類的常見設計問題.提出一個JS 類的內聚耦合Code Smell 檢測方式是迫在眉睫的任務.
本文的主要貢獻為:
(1) 結合代碼的文本和結構信息,提出了一個檢測JS 類的內聚耦合Code Smell 的靜態分析方法JS4C,它可以量化Blob、Feature Envy(簡稱FE)和Dispersed Coupling(簡稱DC)這3 種Code Smell,并檢測JS開源軟件中的內聚耦合問題.
(2) 細化并改進了類耦合Code Smell 的檢測方式:首先,根據耦合、內聚的設計問題對Fowler 定義的22種Code Smell 進行了分類,對比并區分了FE 的兩類檢測方式;其次,引入了DC 細化耦合問題檢測的粒度,改進了其CDISP 度量,以適應JS 的弱類型語言特性.
(3) 指出了文本信息源對JS Code Smell 檢測的重要性.實驗發現,引入文本特征可以提升Code Smell 的檢測效果.
本文第1 節介紹本文所涉研究領域的相關工作以及其中存在的困難和問題.第2 節介紹本文所涉3 種Code Smell,簡述檢測的各個階段,包括預處理、檢測和結果輸出,是對檢測流程的概覽.第3 節研究3 種Code Smell的文本和結構方式檢測算法,并給出一個綜合判定策略.第4 節給出實驗,以驗證檢測結果的準確性,并探究對檢測準確性造成負面影響的因素.最后得出結論,并討論今后的工作.
圖1 為3 個擁有相同屬性和方法的Rental 類,圖1(a)的程序是一個Java 類,其余兩段程序為JS 類,JS 類的實現參考了Fowler 的影音店案例[9,10]和JS 類檢測的文獻[7].

Fig.1 Demonstration code showing Java and JS class implementations圖1 Java 和JS 類的實現代碼示例
JS 類是基于JS 原型繼承(prototype-based inheritance)構建的一種經典模型[11],它的實現可分為兩類[12].
(1) 在早于ES2015 的語言標準中,類是基于函數的設計模式,實現方式不唯一,圖1(b)是一種常見的實現.
(2) 如圖1(c),ES2015 及更新的標準用糖衣語法(syntactic sugar)簡化了類的實現方式.
檢測JS 類首先需要定位構造器(constructor,例如圖1 中的灰色高亮部分)和成員屬性(例如_data),再遍歷函數的原型鏈(例如圖1(b)的Rental.prototype)搜索其成員函數和子類.構造器是類的顯著特征,可用于創建對象.定位構造器,可通過查找類的實現函數,或查找構造器的調用語句(例如new 語句).目前,已有效果較好的、基于抽象語法樹遍歷的開源JS 類檢測工具JSDeodorant[13]、JSClassFinder[7].
本文需要檢測的JS 類信息為類的成員(即函數和屬性)、類的函數及其簽名、類引用的外部數據提供者(foreign data provider,簡稱FDP)和類引用的內部成員.某類的“外部數據”指的是軟件系統中非本類的類成員.分析引用關系,可通過遍歷代碼中的訪問操作(access)實現.訪問操作是指對對象的引用、對其成員屬性的引用(reference)或對其成員函數的調用(call).
檢測JS 類及相關信息的挑戰包括:
(1) 檢測難以適用于所有的類實現方式.實現類的設計模式,有至少5 種較為常用的、適用于不同版本語言標準的方式[7,13],給軟件組件的判定和標準化帶來困難.
(2) 檢測類與類間的關系困難.受JS 的語言特性限制,類成員屬性的類型在靜態分析過程中是未知的.類型信息的缺失,導致分析引用關系的困難.這種缺失無法使用動態分析完全彌補,因其依賴爬蟲,不適用于運行在服務端的JS 程序.
結構分析是最常見的Code Smell 檢測方法,通常包括2 個階段,即信息收集和檢測判定:在信息收集階段,分析工具通過遍歷抽象語法樹獲取檢測對象的結構信息;在檢測判定階段,需要根據度量的定義將收集得到的信息計算為度量值,再依據規則判定是否存在Code Smell.規則根據Code Smell 的定義設計,它通常由一系列度量閾值條件構成,規則也可以包含其他非數值型特征(例如函數名等文本特征)[14].
本文檢測3 種Code Smell,包括Blob,FE 和DC.Blob 體現低內聚,其余二者體現高耦合,定義分別為:
(1) Blob 表現為一個類,它實現了系統中的多種不同職責、體積龐大且復雜,其函數間的內聚性低[6,14,15].
(2) DC 表現為一個函數,與其所在類相比,它更依賴多個非所在類的操作[16,17].
(3) FE 表現為一個函數,與其所在類相比,它更依賴某個特定的、非所在類的操作[6,15].
根據軟件組件的粒度及設計問題類型,表1 給出了Fowler[9]定義的22 種Code Smell 的分類情況.其中,FE因同時涉及方法和類的耦合,故出現兩次.God Class 亦稱Blob,后文均使用Blob 的表述.

Table 1 Code Smell classification by the types of design problems and their granularity表1 以設計問題種類及其粒度為依據的Code Smell 的分類
Silva 等人[7]指出:JS 軟件項目極少使用類的繼承特性,在樣本中僅占8%.故本文僅討論與類繼承無關的Code Smell.Palomba 等人[18]提出,Refused Bequest 同Message Chains 以及FE 同Inappropriate Intimacy 具有極高的同現性.故本文不涉及Message Chains 和Inappropriate Intimacy.林濤等人[19]指出:Divergent Change 與Shotgun Surgery 矛盾對立、存在權衡關系,且其檢測過程需要對多版本進行分析.故不列入研究范圍.Middle Man 是一種耦合Code Smell,表現為類代理(delegate)了過多其他類的操作,可視作FE 的一個特殊情況.
綜上,在表1 的22 種Code Smell 中,本文聚焦Blob 與FE.因FE 的量化標準不一,故需分類討論.根據表2,標準可分為兩類:其一,確定被檢類與一或多個類發生耦合,但不確定與其耦合的類;其二,確定被檢類與一個類發生耦合,并確定與其耦合的類.為了細化耦合問題的檢測,本文對FE 取后者的定義及檢測方式.為了覆蓋同多個類發生耦合情況,本文引入DC[16].

Table 2 Comparison of three Code Smells related to class coupling表2 3 種類耦合相關的Code Smell 對比
結構分析的挑戰,是由JS 語言特性和Code Smell 度量指標的特性共同構成的,它們包括:
(1) JS 的語言特性導致結構分析所需的精確信息難以獲取,檢測耦合Code Smell 需要推斷類型或依賴其他信息源.
(2) 部分Code Smell 的量化標準不一.
(3) 類的Code Smell 檢測方式均針對以Java 為代表的非解釋型語言,對于JS,需要改進部分度量.
文本分析與結構分析的主要區別在于信息收集階段,前者不依賴或較少依賴抽象語法樹中的結構信息,而是將源碼視作文本片段,并使用文本挖掘算法計算文本相似度等度量信息.
Palomba 等人[6,15,16]運用結構分析和文本分析,對Java 軟件項目的多個歷史版本進行了挖掘,研究分析了兩種檢測方式獲取的Code Smell 強度,得出了其解決的難易程度、變化的趨向、產生的原因等性質.研究指出,基于結構方式和文本方式的檢測算法互補,文本方式是重要的信息源,將兩種信息源結合的工作較少.
因為結構分析方法在檢測JS 變量的類型信息時,所能獲取的信息遠不如Java 項目詳實,所以文本分析和結構分析的互補對JS Code Smell 的檢測更為重要.
文本分析方法可分為有監督和無監督兩類.有監督的文本分析需要額外的人工干預來預先標注文本,以形成帶標簽(Label)數據集,故不適用于Code Smell 的檢測.在無監督的文本分析方面,Blei 等人[21]提出了潛在語義分析(LDA)算法,該算法被應用于Bavota 等人[22]檢測Code Smell 的方法中;Le 等人[23]基于Google 開源的、運用深度模型的Word2Vec 詞向量算法,提出了將文檔用向量表示的算法.
LDA 是一種常用的文檔主題生成式模型,包含詞、主題和文檔這3 層結構.LDA 使用詞袋模型表示一個文檔,并將一個文檔視作隱含主題的有限混合,其中的每個單詞由一個主題生成,文檔之間的關聯可以由主題間的關聯決定.LDA 具備比LSA 等基礎主題建模方法更強的擬合能力.
Word2Vec算法可利用神經網絡語言模型訓練過程中獲得的權重矩陣參數,將詞語轉化為向量.Skip-gram是Word2Vec算法使用的一種語言模型,它根據當前詞匯預測上下文.Skip-gram 模型共有3 層,即輸入層、隱藏層(hidden layer)、輸出層.神經網絡通過調整隱藏層至輸入層、輸出層的權重矩陣進行訓練,調整訓練的過程可概括為:假如若干詞具有近似的輸出,則可反推詞間具有較高的相似性.基于Skip-gram 模型,Mikolov 進一步提出了Doc2Vec,它基于詞向量表示文檔向量.本文采用PV-DBOW(distributed bag of words version of paragraph vector)模型,其設計思想和Skip-gram 相近,即:通過預測文檔的內容訓練一個矩陣,此矩陣即為文檔矩陣.通過計算文檔矩陣間的余弦相似度,可獲取相似度特征.
檢測流程包括4 個階段:信息抽取階段、預處理階段、檢測階段、判定階段,如圖2 所示.
(1) 信息抽取階段:從軟件項目的開源社區獲取特定Release 版本的源碼,配置類檢測引擎的運行參數后開始類的檢測.類檢測引擎基于JSDeodorant[13]改進,JSDeodorant 是開源JS 類檢測工具,文獻報告其平均表現可達到95%的精確率和98%的召回率.在此基礎上,本文借助Google Closure Compiler[24]分析源碼所得的JS 語句類型信息,通過擴展對象類型推斷[25],實現對類信息的檢測.
(2) 預處理階段:本階段為檢測階段所需的輸入做準備.對于結構分析,需對檢測到的JS 類信息去重和計算頻次,以便用于度量指標;對于文本分析,需將文本標準化.文本標準化的具體流程如下:
? 分詞.對駝峰和下劃線命名方式的變量名進行分詞,將所有英語字符都轉換為小寫形式.
? 去除停用詞.將與業務邏輯無關的保留關鍵字、英語停用詞、特殊字符等內容剔除.
? 提取詞干.去除單詞的詞綴,得到其詞根.
(3) 檢測階段:本階段對輸入數據運行檢測算法,并計算動態閾值,最終輸出值域為[0,1]的強度值.其中,本文對FE 運用了Fokaefs 等人[20]提出的檢測算法,對DC 運用了經改進的非嚴格NSCDISP 度量,對Blob運用了LCOM5 度量[26].在基于相似度的文本檢測方面,本文對FE 和Blob 的檢測使用了Doc2Vec算法[23],并以LDA算法的結果作為對照組.由于文本分析對DC 的檢測效果不佳,故未予采用.
(4) 判定階段:對于一個代碼片段,檢測階段可輸出多個強度值.根據Code Smell 之間的關聯和結構和分析方法的特點、優劣,本文提出了一種綜合判定策略,最終得出該代碼片段的Code Smell 的種類及值域為[0,1]的強度值,強度值越高,Code Smell 越嚴重.

Fig.2 Code Smell detection process of JS4C圖2 JS4C 的Code Smell 檢測流程
Code Smell 檢測的傳統方法使用代碼的結構信息作為信息源[6],圖3 展示了本節涉及的結構信息和度量.

Fig.3 Overview of structural analysis圖3 結構分析概覽
3.1.1 結構方式檢測DC
Palomba 等人[16]對Java 的DC 檢測使用了CINT(coupling intensity,耦合強度)、CDISP(coupling dispersion,耦合分散度)和FDP(foreign data provider,外部數據提供類)這3 種度量.其中,CINT 為被檢測函數中經去重的函數調用次數,FDP 指標用是所有被訪問的數據所屬類的去重集合.CDISP 的計算公式如式(1)所示.

由于JS 的語言特性限制了類型檢測,即便類型推斷可確定部分類型,也無法確定全部函數的入參類型和數據訪問操作的變量類型.CINT 和CDISP 的計算都依賴類型判定,若不作改進,檢測難以進行.
據此,本文提出非嚴格的NSCDISP(non-strict coupling dispersion),它的非嚴格是針對FDP 和CINT 必須精確檢測函數入參(input parameter)的類型而言的.盡管引用的對象類型難以精確檢出,但類中的數據訪問操作總數和類的成員尚可明確,仍可利用以上信息得知引用操作的數量.
CDISP 的計算依賴FDP 和CINT.本文對CINT 的檢測不作改動,使用非嚴格的FDP 個數檢測方式:對于不涉及函數入參的數據訪問,沿用原FDP 的檢測方式,若無法檢測到所屬類,則將其視作一個單獨的FDP;對于涉及函數入參的數據訪問,則計算WRFDP.
WRFDP(FDPwith weighted reference)是面向入參外部引用權重的度量,具體方式為:對于函數f,有入參集合Pf.計算f在所屬類中被調用的總次數ntc(number of total calls).遍歷Pf,對Pf的每個元素pfi,計算pfi被調用時入參形參的所屬類為外部類的次數ncfp(number of calls as foreign param).計算ncfp 與ntc 比值,其值域在[0,1]之間.ncfp 計算的基本思路是:分析被檢函數的每次調用,若調用的入參不源于所屬類,則計入ncfp.
對于被檢函數的每個入參,均計算其WRFDP,如式(2)所示.

非嚴格的NSFDP 的計算公式如式(3)所示.

本文將非嚴格的NSCDISP 作為DC 的強度(intensity)值Idc,其計算公式為

NSCDISP 和CINT 的檢測閾值(文獻[16]中的HIGH)取系統檢測結果的平均數(AVG)與標準差(STDEV)之和[17],未達到閾值的相關度量值將被改為0,下同.
在計算出軟件系統內的全部Idc后,對于該系統的全部函數{f1,f2,f3,…,fi},可以得到一個集合SIdc={Idc(f1),Idc(f2),Idc(f3),…,Idc(fi)},對所得結果進行Min-Max 標準化,使其值域落入[0,1]區間,得出最終的強度值集合NSIdc(normalized structural intensity),如式(5)所示.

3.1.2 結構方式檢測FE
對于被檢類Ccurrent,遍歷系統的類全集C.對于C中的每個元素Ci,檢測Ccurrent對Ci成員(函數、屬性)經去重后的訪問次數ai,并依據ai的值對C中的類從大到小排序,若排序第一的類Ctop不等價于Ccurrent,則判定存在FE.將Ccurrent對Ctop的成員訪問次數atop記為ATFM(access to foreign members),將Ccurrent對自身的成員訪問次數acurrent記為ATLM(access to local members),FE 的強度為式(6)[20].

若Ife>0,即可判定FE 存在.用類似第3.1.1 節的方式對所得結果進行Min-Max 標準化,得到結果集合NSIfe.
3.1.3 結構方式檢測Blob
利用結構方式檢測Blob 有兩個角度:類的體積和類的內聚性[26].前者根據代碼長度或類成員的總數,后者采用LCOM5(lack cohesion of method 5)度量.由于本文的主題僅和內聚、耦合相關,且類體積相關的度量需指定固定的閾值,故不將類的體積納入檢測考慮的因素.
對于類C,獲取其函數成員總數k、屬性成員總數l、被訪問的自身成員(函數、屬性)經去重后的總數a,定義LCOM5 為式(7),其值域在[0,2]間,檢測閾值取第三分位點(75%)的值[27].

用類似第3.1.1 節的方式對所得結果進行Min-Max 標準化,得到結果集合NSIblob.
圖4 展示了文本分析的方式,以經文本標準化引擎處理的全部程序文本為訓練集,將每一個類作為一篇文檔,訓練Doc2Vec 模型.通過該模型,可計算任意兩組代碼段文本的余弦相似度.余弦相似度的值域落在[?1,1]間,負值的物理意義即為負相關.由于負值偶發,且本文不關注代碼段負相關的信息,參照Positive PMI[28,29]對類似問題的處理方式,將余弦相似度的負值視為0,即對于文本段Ta,Tb,其中a與b為任意代碼片段,其相似度定義如式(8)所示.


Fig.4 Overview of textual analysis圖4 文本分析概覽
本文采用類似的方式,利用LDA 計算文本的相似度,并將其作為對照組,相似度值域為[0,1].
本文參考Palomba 等人[18]使用系統內全部非零度量值的中位數(non-null median)作為TIfe和TIblob的閾值.
3.2.1 文本方式檢測FE
此方法的基本思路是:對于被檢函數f,若存在一個非所屬類,它與f的相似度比f同所屬類的相似度相比更高,則存在耦合,可判定檢出FE.定義f所屬的類為CO,計算f所有類的集合SCALL中每個類的文本相似度.即:對于任意Ci∈SCALL,計算PSIM(f,Ci),記錄最大值為MaxPSIM,如式(9)所示.

對于FE,定義Code Smell 的強度[6]為式(10).

3.2.2 結構方式檢測Blob
此方法的基本思路是:類的函數間越不相關,則類的內聚性越低,Blob 的強度越大.
對于被檢類C及其函數成員的集合F,如式(11)所示,定義Code Smell 的強度[6]為

其中,ClassCohesion 為類函數間的相關性計算函數,其定義[15]如式(12)所示.

本文按設計問題將Code Smell 分為內聚類和耦合類,圖5 中的Blob 屬于內聚類,圖6 中的DC 和FE 屬于耦合類.
判定分為兩個步驟:首先,篩選Code Smell 度量強度超過閾值的檢測對象;其次,根據文本和結構方式的強度確定Code Smell 的最終強度.對于Blob,通過結構和文本分析檢測到的Code Smell 之并集識別.如式(13)所示,強度值Iblob取兩者的最大值:

對于DC,本文采用結構分析識別,即NSIdc>動態閾值,如圖6.強度值Idc取結構分析的強度如式(14)所示.

對于FE,本文將存在結構耦合(0 Fig.5 Unified identification strategy of cohesion design problem圖5 內聚設計問題的綜合判定策略 Fig.6 Unified identification strategy of coupling design problem圖6 耦合設計問題的綜合判定策略 本文的先進性體現在以下幾處:在結構分析方面,本文討論并選定了3 種Code Smell 的檢測方式和閾值,針對JS 檢測任務提升了DC 檢測涉及的FDP 和CDISP 指標的寬容度,使其適應類型不明的情況;在文本分析方面,本文使用了更優的文本分析算法,并利于文本語義特征實現文本和結構分析的檢測結果互補.針對兩種分析方法的特點,本文制定了3 種Code Smell 的綜合判定策略. 將文本分析和結構分析結合是可行且有效的. ?一方面,代碼組件的文本中蘊含業務邏輯和組件功能(例如service,manager)信息,這些信息在結構分析時往往難以被充分利用[30].已有學者嘗試改善這一情況,例如:Moha 等人[26]檢測Blob 時,在結構方式的基礎上根據類名判斷組件功能;Palomba 等人[15]通過實驗得出文本和結構分析的檢測結果互補,相互結合可以獲得更好的檢測效果,將兩種信息源結合的工作較少. ?另一方面,JavaScript 的類型系統和Java 等強類型語言不同,在變量聲明時無需明確類型,導致傳統的靜態分析難以奏效,無法獲得像強類型語言一樣詳實的類型信息. 判定Blob 使用文本和結構方式的最大值的原因是:(1) Blob 的結構方式只需要檢測訪問本地和外部數據的次數,對于外部數據的訪問,無需明確操作涉及的類,因此獲得的結構信息是完整的;(2) 本文的強度值均標準化至[0,1]區間,可以直接比對. 判定FE 時,之所以優先取文本方法的檢測值,是因為結構方式檢測FE 需要確定耦合對象所屬的類.由于JS使用弱類型系統,變量的類型在分析中僅靠推斷得出,因此這一結構信息可能檢測不全.文本方式根據語義相似度確定是否耦合,不受此限制,因而更可靠. 判定FE 時,使用CINT 作為閾值的原因為:一方面,計算CINT 無需確定耦合對象所屬的類,因此該度量的值是可靠的;另一方面,存在業務邏輯類似但實則毫無關聯的代碼,它們可能會導致文本方式的誤判,例如將代碼拷貝誤判為耦合,使用CINT 閾值可以應對這種情況. 為了驗證JS4C 的有效性,本文以表3 中的開源項目作為測試數據集,對JS4C 的檢測結果進行評估對比.實驗主要尋求以下幾個問題的解答. Q1:JS4C 能否準確檢測內聚和耦合的設計問題? Q2:JS4C 有哪些已知問題?是否仍有改進空間? Q3:文本檢測算法是否提升了JS4C 的檢測效果? Q4:哪些因素會影響實驗評估結論的有效性? Q5:如何基于JS4C 給出JavaScript 類的重構建議,并實現其應用意義? Table 3 Open source project dataset used for experiment表3 實驗所用開源項目數據集 為了保證基礎數據的準確性,實驗首先對基礎度量數據進行了驗證.由于缺乏對比的工具,本節實驗參照同類Code Smell 的工作[1,3],將對比基準確定為人工檢測.檢測工作由本文的第一作者及一名擁有3 年工作經驗的Web 應用開發者分別獨立進行后,針對分歧討論完成. 其中,Doc2Vec 和LDA 的相似度值分別使用Deeplearning4j 和JGibbLDA庫計算,它們被軟件分析相關研究[31]和開源社區廣泛驗證和使用,因此文本分析的人工驗證僅涉及輸入數據和運算的準確性.結構方式的度量值均源于抽象語法樹的結構信息,驗證工作將人工計算的結果與工具的結果進行了對比. 獲取可靠的度量值后,實驗需要進一步驗證JS4C 對實際內聚、耦合設計問題的檢測效果.本文對數據集中的項目采用了與度量值驗證相同的人工檢測流程,特別地,對于耦合問題,除了判定有無設計問題外,還需識別出所有人工判定發生耦合的對象,否則視為錯判. 為了回答Q1 和Q2,本文使用精確率(precision)和召回率(recall)度量檢測的效果,指標的數值越大,表明檢測的效果越好.將Code Smell 檢測視作一個二分類問題,將存在Code Smell 的類歸為正樣本類,不存在Code Smell的類歸為負樣本類.令預測為正的正樣本數量為TP(true positive,正確率)、預測為正的負樣本數量為FP(false positive,誤報率)、預測為負的正樣本數量為FN(false negative,漏報率),精確率和召回率分別如式(16)、式(17)所示: 本文對數據集中全部的項目進行了檢測,本節以Awesome-qr.js 案例和Three.js 為例,說明實驗過程.本節的度量數據在第4.2 節中的表7~表9 展示. Awesome-qr.js 的1.2.0 版本[32]約有1 500 行代碼,可在客戶端或服務端運行.它擁有兩個工具類、7 個功能類,且多個類具有低內聚特征. 表4 列出了對該項目受FE 和DC 影響的全部函數及檢測結果.由于上述函數的代碼及變量命名均無顯著的業務特征(例如mod 和multiply)或特征過多、分散(例如draw),甚至未出現在軟件項目中,故文本分析難以確定目標的耦合類.然而,它們的確存在耦合問題,對于這類較為困難的任務,JS4C 體現出了良好的適應性. Table 4 Detection results and effectiveness of FE and DC in Awesome-qr.js表4 Awesome-qr.js 中FE 和DC 的檢測結果和檢測效果 表5 列出了該項目Blob 的檢測結果.在上述檢測結果中,對于QR8bitByte,文本分析未檢測到Code Smell,而結構分析檢出了最高強度的Code Smell.因為類中定義了多個未經函數成員訪問的屬性,內聚性不足,但函數成員的名稱卻與業務邏輯有關.對于AwesomeQRCode,它未包含任何屬性成員,故結構分析會失效,但文本分析卻可以檢測出它實現了不相關的多種職責. Table 5 Detection results and effectiveness of Blob in Awesome-qr.js表5 Awesome-qr.js 中Blob 的檢測結果和檢測效果 Awesome-qr.js 的體積較小,在相關性分析中難以獲取足夠的有效數據,需要使用規模較大的軟件演示后續步驟.Three.js[33]是利用WebGL 和HTML5 特性實現的開源瀏覽器端三維引擎,截至2018 年9 月,該項目已有近25 000 次代碼提交(commit)、88 個發布(release)版本和近1 000 名代碼貢獻者(contributor).本文利用JS4C 對Three.js r95 版本代碼中的232 個類進行了檢測,并分析Code Smell 的分布規律及它們之間的關系,其中,共103個類涉及本文討論的3 種Code Smell. 對于系統內所有的類,獲取3 種Code Smell(Blob,FE,DC)強度的集合BLOB,FE,DC.本文利用Spearman 等級相關方法(Spearman’s rank correlation coefficient)[34]分析三者間的兩兩相關性,該方法可用于未知概率分布的兩組順序數據,取上述兩組數據作為輸入值,可輸出其相關系數ρ及相關性的顯著程度P.判定顯著程度前,需要選定一個顯著等級(significant level)值α,實踐中,α通常取值0.05[6].在該取值下,當P<α時,可認為兩組數據的差異具有顯著性,即具有統計意義;當P<0.01 時,可認為兩組數據的差異非常顯著.相關系數ρ利用單調方程評價兩個統計變量的相關性,它的值域為[?1,1].如表6 所示,ρ值可對應相關性,當數據中沒有重復值,且兩組變量完全單調相關時,ρ相關系數則為+1 或?1. Table 6 Spearman’s rank coefficient value ρ and its correlation level表6 Spearman 秩相關系數ρ的值及其相關性 本文對Three.js 的3 種Code Smell 檢測結果進行兩兩分析,得出結論:FE 和DC 呈現弱強度的負相關性,其中ρ為?0.30(P=0.001);BLOB 和FE 呈現中等強度的正相關性;BLOB 和DC 的差異不具統計意義. 本文進一步探究耦合設計問題和內聚設計問題的關系. 將Code Smell 分為兩類,令內聚Code Smell(Blob)為一類,令耦合Code Smell(FE,DC)為另一類.類低內聚的強度ILC取類的Iblob.一個類具有多個函數,因而具有多個FE 與DC 強度值,對于耦合Code Smell,本文取其均值mean(Ife),mean(Idc),類高耦合IHC的強度優先考慮同一個類發生耦合,即取mean(Ife),若mean(Ife)≤0,則取mean(Idc).對于系統內所有的類,可以獲得低內聚強度的集合LC和高耦合強度的集合HC. 本文利用Spearman 秩相關系數分析LC和HC的相關性,僅考慮IHC,ILC均大于0 的情況,得到P值遠小于0.05(6.3e?18),ρ為0.46.實驗發現,該項目耦合和內聚的強度具有弱正相關性. 將低內聚、高耦合類交集的元素個數與低內聚、高耦合類并集的元素個數的比值作為同現率,得出同現率為66.88%.本文進一步使用Wilcoxon 秩和檢驗(Wilcoxon rank-sum test)[35]分析兩組數據的總體分布是否差異顯著:若差異不顯著,則相關性和同現率無實際意義.秩和檢驗亦輸出顯著程度P,顯著等級取值和α相同.分析得出,LC和HC的分布顯著不同(P=6.27e?34). 通過與第4.1 節類似的方式,本文對數據集中的所有項目進行了檢測,檢測結果在表7~表9 中列出.在表7和表8 中,為了驗證文本方式對結果的貢獻,實驗單獨列出了去除文本方式后的數據(即“純結構”列).“重合度”為使用Doc2Vec 和LDA 作為文本算法的檢測結果中重復的部分(交集)占全部檢測結果(并集)的比重;“非重合部分占比”為去除重復部分的全部檢測結果中,二者檢測出的信息分別所占的比重. Table 7 Detection results of coupling design problems (%)表7 耦合設計問題的檢測結果 (%) Table 8 Detection results of cohesion design problems (%)表8 內聚設計問題的檢測結果 (%) Table 9 Other detection results (%)表9 其他檢測結果 (%) 對于Q1,根據表7 和表8 中Doc2Vec 列的精確率和召回率數據,可見其總體表現與檢測Java Code Smell的同類文獻[15]相仿,JS4C 能夠比較準確地檢測和識別內聚和耦合問題.如表7 所示,使用Doc2Vec 作為文本檢測算法的JS4C 在檢測耦合問題方面的整體表現更好,在保持與其他方式相當精確率的前提下,大幅提升了召回率;如表8 所示,在檢測內聚問題時,其優勢更為全面和顯著. 本文還參考相關文獻得出的內聚和耦合問題的關聯和性質,通過表9 中第4 列、第5 列的其他指標驗證了其中的結論,以印證對Q1 的回答.Badri 等人[36]指出,內聚和耦合的度量間存在相關性.數據集的6 個項目中,有4個得出了類似結論,其中,Awesome-qr.js 相關性不顯著的原因是數據不足.Chahal 等人[8]認為:“高內聚、低耦合”盡管在設計原則中并列出現,但實現高內聚不意味著低耦合,然而在復用性較差的設計中,二者會同時出現.根據秩和檢驗P值,可知內聚和耦合問題的分布顯著不同,因此它們并不是同一種問題;通過同現率,可印證兩種問題有較高概率同時出現. 對于Q2,JS4C 包含因上游軟件和算法等特性較難排除的已知問題,以及針對軟件的具體情況而排除的干擾因素,排除干擾因素可以有針對性地提升在特定項目中的表現,已知問題則需要在后續工作中持續改進. 已知問題包括:(1) JS4C 基于類檢測工具JSDeodorant 擴展,檢測效果受其制約,盡管JSDeodorant 是目前效果最好的JS 類檢測工具,但由于實現類的方式多樣,工具會漏檢部分構造不規范的類(例如使用對象定義且沒有構造器的情形),影響到了檢測精度;(2) 文本過短會導致文本模型得出不合理的相似度,因此目前有較多代碼行數較小的類被誤判為低內聚類;(3) 由于JS 使用弱類型系統,目前使用被調用函數簽名的特征推斷對象的類,如果特征不明顯,對象所屬的類可能無法被準確推斷,進而影響NSCDISP,ATFM 等結構方式度量計算. 除此之外,還有能因地制宜而排除的干擾因素. 在檢測耦合問題的實驗中,JS4C 在Three.js 和FloraJS 的例子中的精確率表現不甚理想,主要的錯判包括2類情況. (1) 與瀏覽器支持的WebGL、Canvas 和頁面事件等功能接口交互的類和函數中,JS4C 的結構方式將其識別為一種不明來源的耦合,它們實為對瀏覽器底層功能的封裝.對于這類問題,可以通過建立瀏覽器底層功能函數庫的白名單,并將這些函數列入文本檢測流程中的“停用詞”范疇,以優化檢測效果. (2) 由于文本相似度檢測的特性,FE 的檢測會受到Duplicate Code(重復代碼)的干擾.在Code Smell 優先級排序的相關研究中[37],Duplicate Code 優先于FE,該問題可通過Code Smell 排序的方式規避. 在檢測內聚問題的實驗中,受干擾導致的錯判包括3 類情況. (1) 數據類(data class)或模型類(model)中與業務邏輯無關的存取和序列化等函數提高了結構方式內聚性的度量值、干擾了判斷:一方面,JS 的存取函數并沒有通用的模板代碼,其命名和實現方式不遵循特定規則;另一方面,部分存取函數也包含少量的業務邏輯,因此不能簡單地排除全部的存取函數. (2) 代碼中存在大篇幅的、與業務邏輯無關的變量默認值,影響了文本方式的判斷結果. (3) 部分工具類有低內聚的特性,易被判為低內聚類.對于該問題,一方面,工具類是否為反模式仍有爭論;另一方面,若實有必要,可以通過工具函數的檢測算法[38]將其排除. 對于Q3,文本檢測算法提升了檢測效果:一方面,在表9 中,易見文本信息源占據了約4 成的比例,是重要的信息源;另一方面,文本方式檢測出了更多的、有效的信息.根據表7 和表8 中Doc2Vec 和純結構方式的對比,易見前者使召回率有普遍提升,即檢測到了更多的設計問題;Doc2Vec 檢測出的額外信息沒有對精確率造成嚴重影響,在內聚問題的檢測中還使精確率有顯著提升,即檢測到了有效的信息. 在文本方式的算法中,Doc2Vec 方法的整體優于LDA.從表7 和表8 中的“重合度”可以看出,兩種文本算法的檢測結果具有一定相似性;但從“非重合部分占比”數據可以看出,在余下的不重合部分,Doc2Vec 方法普遍比LDA 能檢測出更多不同的數據.根據Q1 的結論,可見這些數據顯著提升了檢測效果.因此,Doc2Vec 更適合作為本文的文本分析檢測算法. 對于Q4,本文實驗評估的有效性威脅包括以下3 點. (1) 數據集項目的選取問題.本文從GitHub 和JS 類檢測的文獻[7]中選取了運行環境多樣、功能不同且將類作為主要設計模式的JS 項目用于驗證實驗結果,但僅涉及了6 個開源項目,無法涵蓋JS 程序的全部應用領域. (2) 人工標注設計問題的主觀因素.由于缺乏可供參照的同類工作和數據集,因此用于驗證有效性的設計問題是人工標注的,存在主觀因素.因此,本文參考其他學者[3]的做法,引入具備JS 開源項目經驗的開發者合作標注問題,以期還原現實的軟件開發和維護情境. (3) 動態閾值的選取問題.由于缺乏JS 類度量指標的統計數據,為了保障檢測方法的適應性,本文不使用固定閾值檢測.對于Java Code Smell 的文獻中采用固定閾值的度量(例如結構方式的DC 和FE),本文根據閾值得出的方式針對每個項目分別統計,形成動態閾值.對于這類改為動態閾值的度量,圖7 展示了全部數據集類中它們原始數值的分布,并用分別標注出了原始文獻中的固定值(虛線)和統計數據集得出的值(實線).根據兩者差值的絕對值同固定值的比值計算,可以得出不同度量的動態閾值與固定閾值有5%(LCOM5)~20%(CINT)不等的差異. Fig.7 Distributions and thresholds of 3 metrics圖7 3 種度量的分布及閾值 對于Q5,JS4C 可以量化代碼異味強度,通過提升JS 類的內聚、降低耦合來消除強度高的代碼異味,進而實現高效的JS 類重構.然而,近期有研究指出:除了異味強度外,還有代碼的運行環境、模塊和組件的重要程度等情境因素影響代碼異味的重構優先級[39],在特殊的情境下,代碼異味可能不構成軟件設計問題.因此,在實際應用場景中,未被納入考慮的情境因素可能降低代碼異味強度的參考價值.這一問題可以通過實現情境感知的代碼優先級排序來解決,它綜合考慮影響代碼質量和重構優先級的多方面因素,代碼異味強度是其中的一維特征[39]. JS 已成為最常用的編程語言之一,然而在JS 項目中,仍未充分實現常見類Code Smell 的檢測.本文針對DC、FE 和Blob 這3 種Code Smell,結合文本分析和代碼結構靜態分析,提出了一個檢測JS 類的內聚耦合Code Smell的方法JS4C,并在實驗部分,通過對6 個開源項目的分析,證明了JS4C 對內聚和耦合的設計問題有良好的檢測效果. 后續工作有3 個方向:其一,研究的范圍可以拓展到基于瀏覽器端框架的業務代碼,檢測與框架設計相關的內聚耦合Code Smell;其二,對大量的工業軟件項目進行大樣本的檢測,進一步提高檢測工具的穩定性和表現,并對Code Smell 及耦合、內聚問題進行詳細的質性分析;其三,可以將結構檢測和文本檢測的結合方法進一步改進,近期出現了基于深度學習模型、抽象語法樹中的名稱和結構特征及詞向量分析代碼功能的方法[40],其對Code Smell 檢測的幫助也是值得研究的.


3.4 可行性分析
4 實 驗

4.1 實驗過程




4.2 實驗結果



4.3 實驗討論
4.4 有效性威脅

5 結論和后續工作