劉 旭
(SAP中國研究院 商務智能部,上海 201203)
代碼依賴與軟件架構緊密相關,代碼各模塊的相互依賴直接反映了高內聚及低耦合的設計目標.在軟件開發過程中和軟件維護階段,對于代碼依賴的分析有助于降低不必要和不合理的依賴,適時重構軟件以維護軟件架構的穩定性,減少程序錯誤,提高開發效率和降低維護成本[1].然而,隨著現代軟件代碼量的膨脹,代碼間的依賴關系日趨復雜,傳統的依靠開發人員經驗進行代碼依賴檢查的方式效率較低,智能化的軟件開發嘗試在軟件開發過程中引入數據驅動的思想以改善開發體驗[2],提高開發效率.
軟件架構的設計,實現和維護最終依靠架構師,開發和測試人員完成,而視覺是人類接受信息最主要的渠道.軟件可視化利用信息可視化和可視分析的方法進行軟件數據展示和分析,發掘數據的非結構化信息.利用可視化方式對代碼依賴數據進行分析,有助于技術人員更快地發現依賴關系的變化,還可發現關系中蘊含的模式[3].針對代碼依賴設計并實現功能完善的可視化系統,重點是需要分析代碼依賴數據的特點,選取既能夠降低視覺復雜度,又便于進行交互的可視化形式.目前相關的工作多集中在對代碼依賴數據的提取和分析,對可視化部分的研究較少,已有的數據可視化分析強調統計結果,多使用傳統的散點圖,折現圖等統計圖表.本文的重點在于如何針對依賴數據本身使用不同的現代可視化形式,并改善其交互效果.
在現代軟件的開發中,無論使用過程式,函數式還是面向對象的軟件模塊組織方式,最終程序的執行必然包括一系列方法的調用過程,這意味著代碼之間的依賴關系廣泛存在于軟件源代碼中.典型的代碼依賴一般存在于函數之間,文件之間和對象之間.在面向對象的開發方式中,繼承和方法調用是比較常見的對象之間產生依賴的原因.代碼依賴不可避免,但在軟件系統代碼量越來越龐大的今天,冗余的和破壞系統架構的代碼依賴會增加代碼理解和維護的難度.開發,測試和維護過程中對于代碼依賴的自動化檢查已經成為大型軟件系統研發的常見手段,最終目標是提高軟件代碼質量.
軟件開發過程傳統上主要基于開發人員的經驗進行.為了改進開發效率,需要引入數據驅動的方式,首要任務是充分利用開發過程中產生的各種數據,包括代碼依賴數據.軟件可視化試圖在軟件工程領域應用信息可視化與可視分析的發展成果,即結合人類視覺和計算機結構化信息處理的優勢[4],使用人機交互的方式從數據中得出結論.

圖1 信息可視化流程
圖1 顯示的是信息可視化的一般流程[5].可視化的最終實現目標是可顯示,通常還帶有交互性的圖形.在數據的富集和增強階段主要對數據進行標準化,聚合,過濾,去除不需要進行可視化的維度.可視化映射的重點是視覺編碼,也就是如何從數據映射到位置,顏色等屬性.視覺編碼和布局算法往往和選取的具體可視化形式有關.在數據分析已經完成的情況下,可視化被作為數據挖掘的結果呈現方式,但在可視分析逐漸被視為大數據分析的重要方法的情況下[6],高度交互性的可視化不僅是分析的結果,也是分析的手段,可以用于數據的再次獲取和分析.軟件可視化的關鍵在于使用適當的隱喻和交互設計,幫助技術人員進行分析推理,發掘隱藏在軟件數據中的模式,從而改善開發過程[7].
隨著智能化的發展和數據量的膨脹,在企業軟件開發中越來越強調敏捷[8],軟件研發的迭代周期越來越短.具有高度適應性和完善開發框架支持的B/S模式逐漸成為信息系統應用的主流,對于JavaScript,Python 等腳本語言的依賴與日俱增[9].JavaScript 是2017年GitHub 上最為流行的程序設計語言,廣泛應用于前端和后端的企業軟件開發,擁有龐大的代碼基礎.JavaScript 早期版本不具有模塊(module)的實現,ES6 雖然引入了內置的模塊實現,但此標準出現較晚,目前大量的jQuery 等JavaScript 軟件項目多使用RequireJS 等第三方模塊加載器定義和管理模塊[10],將每個文件定義為一個模塊,在模塊的開始部分定義模塊之間的依賴關系.

圖2 JavaScript模塊之間的依賴
圖2 是一個使用RequireJS 定義的JavaScipt模塊之間的代碼依賴示意圖,代碼示例取自jQuery.可以看到有的模塊同時依賴于幾個模塊,也有的模塊同時被其他幾個模塊依賴.模塊之間的依賴從結構上說是一個有向圖[11],但是在企業軟件開發實踐中,開發人員最關心的是如何快速定位依賴關系存在于哪兩個模塊之間,具體的依賴方向一般結合項目架構和開發經驗判斷,也可以由軟件開發者在修改代碼文件時查看,使用無向圖表示的代碼依賴已經可以滿足大部分檢查代碼依賴的需要.
本文試圖實現的原型系統dpViz 以JavaScript 為目標語言,以RequireJS 定義的模塊為代碼依賴的單元.目標是以無向圖的方式可視化各單元之間的依賴關系,可視化形式需要帶有交互性以便于進一步分析.原型系統dpViz 針對代碼的靜態分析結果,和一般的信息可視化系統類似,同樣遵循數據獲取,數據結構化處理,可視化布局和渲染等基本步驟.圖3 是使用SAP PowerDesigner 繪制的dpViz 組件圖.

圖3 dpViz 組件圖
從組件圖可以看到,dpViz 系統中各模塊之間的依賴較少,數據傳遞比較清晰,在獲取模塊依賴的原始數據之后,數據的結構化和可視化都可以歸為可視化數據挖掘領域,可視化形式的確定是設計重點.開發人員最關注的代碼模塊依賴信息無疑是哪些模塊之間有聯系,模塊位于何處,這些聯系有什么特點.由于理論上任何兩個模塊之間都可能有依賴,模塊間的依賴關系具有典型的網絡數據特點,同時,模塊在文件系統中位于不同的路徑,路徑層次與功能模塊有關,模塊的層次關系也需要被顯示.在信息可視化系統的設計中,經常組合使用多種不同的可視化形式以滿足用戶需要.dpViz的可視化形式選取了在信息可視化領域較為成熟的力導向(Force-Directed)節點連接圖[12]和層次邊聚合圖(Hierarchical Edge Bundles)[13].這兩種圖形對于網絡關系的可視化布局有不同的優化方式.
網絡數據可以借鑒圖論中常用的鄰接矩陣等方式進行可視化表示,但是節點連接圖可能是最為重要和常用的[14],很多布局設計也參考節點連接圖,這是因為視覺系統對于位置這一視覺編碼最為敏感.節點連接圖使用較小的圖形表示每個節點,節點之間的關系用連接線表示.代表節點的圖形可以相同,也可以分別有不同的顏色或形狀以表示更多維度的信息.節點連接圖的缺點是當節點數量和連接線變多時,連接線和節點很容易互相交疊在一起,使圖形變得難以分辨.設計較好的布局算法可以減少這種情況.
力導向布局算法可能是最為常用的節點連接圖布局算法,其基本思想是將網絡數據中的節點對應為現實中的帶同種電荷的小球,節點之間的連線對應于彈簧,而畫圖的區域對應于現實中的平面.當給定小球的初始狀態和彈簧長度之后,根據胡克定律,以彈簧連接的小球之間存在互相吸引或排斥的彈力,而各個帶同種電荷的小球之間存在庫侖斥力,因此這些小球會在這些力的作用下進行運動,一些小球會靠近,而一些小球會分開,這個過程會不斷重復下去.在平面存在摩擦力的情況下,力導向算法中的小球最后會逐漸趨于一個平衡位置,平衡位置的小球和彈簧即構成獲得的布局.
在算法的具體實現過程中,一般會在物理模型的基礎上進行改進[15].由于結點距離較遠時,由胡克定律所確定的彈簧引力太強,可以使用對數法則c1×log(d/c2)來確定彈簧的引力,其中d是結點之間的距離,c1 與c2 都是常數.在計算庫侖斥力時,由于庫侖力與距離的平方成反比,故可以使用c3/d2計算,其中d為結點間距離,c3 為常數.算法對結點位移的計算也可以簡化,并且設置一定的迭代次數來完成布局.使用偽代碼表示的算法如下:

算法中的c1,c2,c3,c4 都是常數,一個常用的經驗值是c1=2,c2=1,c3=1,c4=0.1.由于每兩個結點之間的斥力都需要被考慮,計算結點之間的斥力造成的位移需要的時間為O(n2),而計算邊的引力給結點帶來的位移需要的時間為O(e),如果此算法共進行k次迭代計算,那么需要的時間為O(k×(n2+e)),其中n為結點數,e為邊數.圖4 是使用SAP Lumira的布局算法生成的典型力導向布局節點連接圖.可以看到,如果布局時給定的彈簧長度都是一致的,最后獲得的布局結果中,有連接的節點之間的連線長度會趨于均勻,而電荷之間斥力的存在會使無連接線的節點之間趨于分散,這樣節點的分布也會比較均勻,同時使整個圖形呈現對稱性.這種對稱性的特點是其他的布局方式難以保證的.

圖4 典型的力導向節點連接圖
力導向算法基于物理模型,物理特性保證了迭代結果收斂,所以此算法的行為容易預測和理解,代碼的實現也較容易,并且可以方便地擴展到三維空間.交互性也是力導向算法的明顯優點.在布局的生成過程中,用戶可以看到結點集是怎樣從混亂的初始狀態展開成為比較具有可讀性的布局的,在繪制完成之后,還可以拖動一個或多個結點偏離平衡位置,看結點如何返回原位.力導向布局的這個特性讓它很適合用于繪制動態生成的數據的節點連接圖.但是,力導向算法在連接線較多的情況下,線和節點容易交叉,影響視覺效果.布局過程將改變圖中節點的位置,并且節點的位置在布局完成前不可預知,這使得力導向算法難以用于節點位置相對固定的場合.力導向算法的時間代價也較高.如果以n代表節點的數目,一般情況下,力導向算法需要n次迭代才能得到較穩定的結果,即使不考慮邊的引力計算,力導向算法需要的時間復雜度仍為O(n3),在結點較多的情況下,這樣的時間性能并不理想.
為了規避此布局的缺點,可以在實際使用的節點連接圖中加入過濾功能,減少顯示的節點和連接線的數量.在選中某個節點之后,用戶一般只會對于跟此節點關系密切的節點感興趣,例如在代碼依賴關系中,開發人員一般最關心的只是跟自己開發的那個模塊有關系的模塊.一種常用的過濾方法是對于某個選中節點,僅顯示該節點本身,子節點和二級子節點(子節點的子節點).圖5(a)是一個力導向布局的節點連接圖(局部),其中高亮的節點僅有兩個子節點,但節點個數較多,布局密集,視覺效果不佳.圖5(b)是過濾后的結果(局部),很容易從中看出依賴于高亮節點的子節點和二級子節點.

圖5 節點連接圖的過濾
層次邊聚合圖(Hierarchical Edge Bundles)在節點連接圖的基礎上嘗試用另一種方式來降低連接線的視覺復雜度,即聚合有一定關系的連接線成為線束,避免過多的連接線交叉[16].層次邊聚合圖在環形區域中用扇環顯示節點,不同層次的扇環代表節點之間的層次關系,節點之間的相對位置是固定的.根節點繪制在最外層(根節點必然是圓環,故往往省略根節點),需要繪制連接線的節點都排布為葉節點,這樣連接線都將繪制在環形區域的邊緣.在扇環內部用樣條繪制連接線表示葉節點之間的關系,樣條曲線的控制點位置跟父節點的位置有關.這樣繪制的樣條曲線將根據節點位置的不同分別聚合成為若干線束[17].具有同一祖先的葉節點,其連接線會比較容易聚攏在一起,而關系較遠的節點,對應的連接線會比較分散.線束的布局可以從一定程度上代表節點的層次關系,扇環的大小和顏色和連接線的顏色可以顯示更多維度的信息.圖6(a)是初始的節點連接圖,圖6(b)是轉換后的層次邊聚合圖,可以看到節點層次關系通過類似于Treemap (樹圖)的內隱方式表達,減少了連接線的數量[18].

圖6 生成層次邊聚合圖
從設計上來看,層次邊聚合圖針對的還是網絡關系的數據,其思路是將節點連接圖中的網絡關系從語義上區分為兩種不同性質的聯系:節點之間普遍存在的層次連接和僅存在于葉節點之間的網絡連接,將層次連接用扇環之間的層次關系表示,而葉節點之間的網絡連接用聚合線束表示.一般的網絡關系可以看作是這種網絡關系的特例,只需把所有節點看做是一個虛擬的根節點的葉節點,也可以使用這種布局方式繪制.
在JavaScript 項目的源代碼模塊組織結構中,代碼模塊往往放置于不同的文件夾下,不同的文件夾代表了軟件架構中不同的功能模塊.在這種情況下,代碼模塊之間具有網絡關系,而模塊與其所處的各層文件夾之間具有層次關系,各層文件夾可以被繪制為父節點,用父節點的顏色表示子節點的數量.在節點個數和節點之間連接線很多的情況下,可以考慮聚合葉節點的連接到最末一級或更高層次的父節點,將選定的父節點作為葉節點顯示,用連接線的顏色深度反映聚合的連接數量,這樣可以進一步減少連接線的數量.圖7(a)和圖7(b)顯示了聚合前后層次邊聚合圖的變化,可以看到父節點之間的關系更加清晰.

圖7 聚合葉節點
系統的實現首先需要考慮到模塊依賴數據的獲取和處理.在基于數據的應用系統中,原始數據往往都是不規范的,會有很多空值,錯誤值,數據的規范化需要占用大量的系統資源[19].但是由于dpViz 系統需要的代碼依賴數據來自于代碼本身,可以通過程序化的方式獲取,這樣獲取的原始數據會比較容易規范化.dpViz 使用的方式是使用Java 語言讀取選定路徑下的JavaScript 文件,從JavaScript的define中解析出其依賴的模塊.在獲取依賴關系之后,可以進行一些基本的數據處理,例如排除重復的依賴項,將路徑中的”./”和”../”進行適當替換.處理完成之后獲得的基本對應關系可以使用Java Swing中的JTable 控件來展示為圖8所示的二維表格(以jQuery 代碼為例),便于用戶瀏覽和更改數據.

圖8 用表格瀏覽依賴關系
數據結構化處理和可視化部分是dpViz 系統的核心功能,dpViz 這部分的實現使用JavaScript 結合SVG 完成.JavaScript的執行性能已經可以滿足大型企業應用的需要[20],SVG 作為Web 矢量圖形的標準,以其跨平臺特性在可視化開發領域受到廣泛應用[21],為底層圖形的繪制提供了完善的支持[22].對于選定可視化形式布局的實現是系統的關鍵.使用SVG 進行開發可以使用的第三方庫有很多,其中的D3 作為數據驅動方式的可視化庫,已經實現了很多數據處理和布局功能,合理利用D3 可以明顯提高可視化布局的開發效率.
在dpViz 可視化部分的具體實現中,先使用Java 將模塊依賴數據存儲為JavaScript 文件,寫入文件系統,然后使用JavaScript 在Web中載入這些數據文件,將二維數組方式存儲的數據結構化為具有網絡結構的JavaScript 數據對象,再根據數據對象進行布局并繪制.對于力導向節點連接圖的布局,在d3.layout.force中有完整實現,可以參考其算法并加以改進.層次邊聚合圖分為扇環和連接線兩部分.扇環的布局可以參考d3.layout.partition 函數實現.扇環本身不是SVG 原生支持的基本形狀,需要使用path 繪制,具體過程可借助d3.svg.arc 完成.層次邊聚合圖的連接線可在扇環的布局確定之后,使用d3_svg_lineBasis 函數完成.
可視化的布局和繪制完成之后,還需要實現交互性功能以便于可視分析.由于在實際進行數據分析的時候節點往往較為密集,高亮鼠標盤旋或選中的某些圖形元素是交互性的基本要求.節點較多時,文字標簽往往只能縮略,高亮狀態下需要顯示完整標簽.對于力導向節點連接圖,需要允許用戶拖放各個節點改變布局效果,從而可以通過不同的布局方式分析數據特點,還需要實現僅顯示選中節點的子節點和二級子節點的過濾功能,便于針對特定節點進行進一步分析.層次邊聚合圖在可視分析中較實用的是調整連接線的聚合程度,鼠標拖放的旋轉和葉節點的聚合效果.這些都可以通過數據的過濾和布局的更新實現.

圖9 使用dpViz 對某系統模塊可視分析
dpViz 已經作為企業級軟件代碼依賴的可視分析工具試用,其分析結果對軟件開發工作顯示出實用價值.圖9 是將dpViz 用于分析某商務智能系統模塊過程中所生成的力導向節點連接圖和層次邊聚合圖 (為保護知識產權,圖中隱藏了文本標簽).從圖中容易看出,圖9(a)和圖9(c)的部分是模塊化較好的部分,這兩個部分節點較密集,但與其他部分依賴關系較少.圖9(b)的部分節點多而密集,連接線也很多,模塊化程度較差.圖9(d)是兩個與其他部分無關聯的孤立的文件,很可能是系統試驗性開發中留下的無用文件,應該清除,通過進一步檢查源程序證明了這一點.圖9(e)的部分與其他模塊依賴較多,通過提高該部分的代碼性能和可讀性夠有效改善整個系統的代碼質量.在可視分析過程中,可以發現力導向節點連接圖和層次邊聚合圖顯示出互補性.力導向節點連接圖交互性較好,允許用戶拖放改變布局,但在節點和連接線很多的情況下布局較慢,視覺復雜度高.層次邊聚合圖布局較快,對連接線較多的情況處理很好,但在交互性分析方面表現較差.
現代軟件代碼仍然主要由開發人員編制,而人傾向于反復在同一個地方犯錯,靜態代碼分析與檢測一直是消除軟件錯誤的重要方法[23].可視化以其高度的人機交互性,逐漸從海量數據的分析結果進化為分析手段.作為靜態軟件代碼可視化的一個重要組成部分,代碼依賴關系的可視化可以極大提高開發人員對于軟件系統的理解,提高軟件開發,測試和軟件再工程的效率.dpViz 作為代碼依賴可視化的嘗試,盡管使用的可視化形式還比較有限,支持的程序設計語言種類也較單一,但完整地設計并實現了代碼依賴可視化的各模塊,使用了力導向節點連接圖和層次邊聚合圖這兩種現代可視化形式,并創造了節點過濾和葉節點聚合功能,增加了圖形的交互性,系統的試用驗證了企業軟件開發效率的提升.
對于dpViz的改進,主要的方向可以是從代碼依賴特征入手,創造和改進更有效的可視化形式和交互方式.從軟件度量的角度看,代碼模塊包含的信息很多,針對大量高維網絡數據節點設計有效可視化方式一直是信息可視化的難點.對于不同的代碼模塊類型和不同的依賴方式,以及循環依賴,間接依賴等特殊依賴關系的可視化也是軟件開發過程中非常實用的軟件可視化功能.軟件生命周期是一個時間上動態的過程,通過軟件倉庫挖掘可以得到代碼依賴關系不斷變化和發展的數據,如何使用可視化工具分析這些數據的特點和趨勢也是有待解決的問題[24].隨著軟件開發社區的發展,代碼依賴的范圍并不局限于單個工程項目內部,跨項目的代碼依賴可視化可能需要與單個項目不同的實現方法和目標.希望dpViz 對于代碼依賴可視化的努力能夠為進一步研究和系統實現提供思路.