【騰訊TMQ】結合靜態代碼掃描來給插件間接口把把脈

作者:吳靜純

團隊:騰訊移動品質中心TMQ

導讀

如火如荼的EP建設中小鵝收到了一個小小的需求,如何知道每個版本變更了哪些插件間接口呢,有沒有及時覆蓋?

問開發,看代碼,看變更日誌貌似有那麼點不太智能,重點是也不能保證有沒有遺漏,不能解決測試童鞋的完美主義兼強迫症,有沒有一份及時統一的視圖可以來review插件間接口的變更和覆蓋情況呢?

插件間接口示例

既然是統計插件間接口,我們先認識下手管的插件間接口定義,在手管插件化框架中,各插件相互平行,插件間接口調用即插件數據傳遞通過框架封裝的統一接口進行通信,由框架進行底層的數據封裝和傳遞,具體實現爲各插件間維護一份插件對外的插件間接口配置,編譯時在框架生成對應的插件常量,插件內部重載消息函數通過判斷傳遞的接口常量進行對應消息處理從而實現接口間同步/異步數據傳遞。

插件間接口變更統計

每次編譯前框架都會解析接口配置xml生成統一的插件接口常量表,那插件的變化情況我們可以從這裏入手,從每次編譯生成的常量定義中來找到各版本插件接口的變更情況,通過與上個版本的插件列表定義的插件名參數及返回值做一一對比,就可以知道當前版本的接口變更情況以便及時補充接口用例:

7.4變更接口有69個,數量有些超出大家的想象,也順手給7.0~7.5版本做了個小統計,統計結果手管目前的插件間接口達到了740+,每個版本呈不斷增長趨勢。

這麼多接口是否都是有效的接口呢?

目前已有的插件間接口用例覆蓋程度有多少呢?

經過這麼多版本的迭代相信應該有不少多餘的水分,插件內的代碼各FT通常會清理的比較及時也有一些現成的工具做冗餘代碼清理,但對外的接口大多擔心外部兼容性及依賴問題通常清理不及時,有沒有什麼好的辦法來梳理下,給這些對外接口把把脈呢?

插件間接口規則抽象

有沒有類似調用鏈的分析工具呢?但插件化框設計各插件是平行的,調用鏈均指向框架接口無法解決我們的問題。雖然現成的調用鏈工具達不到需求,但我們可以借鑑下調用鏈的方法,重新抽象規則來建立一張我們想要的接口定義-實現-調用的關係圖:

抽取規則如下:定義-實現-調用是一個正常接口的三要素。如果三要素有任一缺失,我們可以推測該接口可能無人調用可以清理或者實現者已清理但仍有調用。

規則一:接口定義,在框架中有定義的插件及插件接口常量認爲插件已定義。

規則二:接口實現,在插件工程中有調用到本插件常量的則認爲是本插件內部的接口實現,如projectA中有調用CosntA.functionid.interfacea1,可以認爲是接口a1已實現,記錄插件A的a1接口的實現地址。

規則三:接口調用,在插件工程中調用到非被插件常量的則認爲是外部接口調用,如projectA中有調用ConstB.functionid.interfaceb1,則認爲工程A調用了插件B的b1接口,在b1接口的調用鏈中添加該插件的調用記錄及文件地址。

插件間接口規則實現

考慮插件間接口是通過傳遞接口常量來完成數據傳遞,我們可以通過代碼掃描來構建我們的上述規則,結合我們的自定義需求來看看目前android常用的三款靜態代碼掃描工具:

從擴展性的角度看,coverity作爲商業軟件雖然官方文檔也支持自定義擴展,但相關資料太少,個人更傾向於lint和findbugs,不會寫還可以從源代碼裏面偷偷師,考慮到插件間接口傳遞的是接口常量,字節碼在編譯優化過程中常量字段被替換可能導致部分路徑無法回溯,也不利於我們對結果做進一步的整理分析,所以最終選定lint進行源碼掃描處理。

選定了工具之後實現部分就水到渠成了,按lint規則擴展來添加需要的檢查規則,下圖虛線模塊是每個自定義規則需要擴展的地方:

1、註冊規則,聲明掃描範圍爲JAVA_FILE_SCOPE:


2、實現檢測器,檢測器是實現檢查邏輯的主體,自定義的FunctionDetector檢測器繼承自Detector並實現Detector.JavaScanner接口,並定義我們關注的掃描節點:


(1)查找插件接口定義:

在掃描工具中我們可以按抽象語法樹來進行代碼節點的查找,在Android Lint中scanner通過lombok.ast(Abstract Syntax Tree抽象語法樹)API來進行代碼節點的查找,有興趣的童鞋可以參照Eclipse AST介紹。

前面說到,手管編譯前編譯腳本會根據插件配置在框架生成相應的插件及接口常量類:

因此插件接口我們可以重寫visitClassDeclaration(ClassDeclarationnode)函數在類聲明節點中查找解析相應的類文件,將functionid的內部類的所有常量定義加入接口名list,並收集相應的location信息:


(2)查找插件接口實現和調用:

獲取插件接口實現,調用本插件的插件接口常量可以認爲是該插件間接口的實現,在visitVariableReference(VariableReference node)重載函數中對於調用到的常量判斷爲插件常量格式(如PiConst.FunctionId.FunctionName)則獲取其插件常量判斷是否爲本插件的接口,如是,獲取其location信息寫入實現位置。

獲取插件接口調用,調用非本插件的接口常量則認爲是對外部接口的調用,將插件名及location信息加入到該接口的調用列表中。

3、確認全部插件工程都掃描完成後,在afterCheckProject(Context)重載函數中判斷每個接口狀態:

1)有實現有調用列表的爲正常接口;

2)無實現仍有調用的爲冗餘未清理接口,可清理接口定義及調用;

3)有實現但無調用的疑爲冗餘未清理接口,可清理接口定義及實現;

4)僅有定義,疑爲冗餘未清理接口,可清理接口定義。

得到了748個接口的狀態信息,有30%接口有清理空間,我們抽查了主界面的幾個,比如主界面REPORT_MESSAGE接口爲5.x的消息中心接口,在7.0改版時該功能已全部去掉但仍有6個其他業務插件引用在繼續給主界面發消息。

是否可清理呢?

答案是肯定的,接口定義及外部插件的引用均可刪除,只刪除定義會導致編譯不過通知引用插件刪除相應的調用即可。舊版本插件調用是否會有crash問題呢?

插件化框架無法保證插件是一定存在的,插件進行接口間調用時就需要進行容錯處理,所以插件間接口也不是隻增不減的,可以刪除。

我們粗略做個統計:

接口定義(xml配置接口及參數返回值定義不會進入編譯)常量接口1行,非normal接口共240個;

接口實現,接口參數及返回值均值爲2.05個,假設爲10行,有實現但無調用的有148個;

接口調用,import及無效調用假設每個引用5行,無實現的調用列表有40個。

240+148*10+40*5=1920保守估計可以清理約2000行代碼,相關的資源及配置也可以做進一步清理。

插件間接口視圖的其他應用擴展

除了代碼清理,插件間接口梳理結果是否還有其他應用呢?

比如查看插件用例覆蓋程度,插件間接口測試也是通過調用插件接口調用來進行接口驗證,因此調用列表中包含pitest插件的可認爲是已覆蓋的插件間接口,過濾調用列表中包含pitest的有178個,目前插件間接口pitest的覆蓋率爲23.8%。

比如作爲插件用例的下架指引,狀態爲非NORMAL或者插件列表如果僅有pitest插件的可推測該接口已廢棄,測試用例可以考慮從日常監控中下架。

比如調用列表數量是否可認爲是接口覆蓋的優先程度,調用較多的是否可認爲該接口使用率更高優先級更高,需要更多的關注和驗證呢?

……

插件間接口整理只是我們靜態代碼掃描在缺陷/規範掃描之外結合業務的一個小應用,通過梳理業務定義處理規則,把代碼的問題回到代碼中來處理。照此思路,一些日益膨脹的公共lib庫,裁剪對外提供的sdk也可以進行精確的梳理瘦身,其他各層級的接口是否也可以梳理出這樣的可視化圖表呢?統計點是否也可以進行類似的梳理驗證呢?

結合業務代碼掃描我們還可以做的更多,也許你也有更多的代碼掃描的應用場景也歡迎大家一起探討~

關注微信公衆號:騰訊移動品質中心TMQ,獲取更多測試乾貨!

版權所屬,禁止轉載!

發佈了162 篇原創文章 · 獲贊 103 · 訪問量 34萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章