天天看點

Code Review效率低?來試試智能文法服務

在人工代碼評審(Code Review,CR)中,對于純文字形式的代碼浏覽不可避免地将耗費大量的時間,影響CR的效率。那麼有沒有更智能的方法?阿裡雲雲效代碼智能文法服務基于雲端備份的快速代碼導航服務,無須本地克隆即可在頁面體驗熟悉的定義引用快速檢視跳轉功能,可大幅提升代碼評審的效率和品質。本文分享相關的技術原理與實作方法。

一 前言

代碼文本不是簡單的二維平面結構,看懂一段代碼需要反複地通過定義與引用的跳轉,才能将代碼深層次的邏輯和片段影響範圍了解透徹。純文字形式的代碼浏覽是網頁端代碼評審的最大痛點之一,朱熹老先生常說“心不在此,則眼不看仔細,心眼既不專一,卻隻漫浪誦讀,決不能記,記亦不能久也。”代碼文本扁平式地漫浪誦讀隻能達到眼到、口到的境界,如果你是一個認真負責的代碼評審者,阿裡雲雲效代碼智能文法服務一定是幫助你充分了解代碼變更,超越眼口,到達“心到”境界的功能。心既到矣,眼口豈不到乎?

那麼什麼是代碼智能文法服務呢?文法服務提供了基于雲端備份的快速代碼導航服務,無須本地克隆即可在頁面體驗熟悉的定義引用快速檢視跳轉功能,大幅提升代碼評審的效率和品質。

二 技術基礎

阿裡雲雲效代碼智能文法服務的底層技術是LSIF(Language Server Index Format),它是一種持久化語言的索引的圖存儲格式,通過圖的格式,表示了“代碼文檔”-> “文法智能結果”之間的事件關系。

在LSIF之前,LSP(Language Server Protocol)定義了編碼語言與各類終端代碼編輯器之前的互動協定。原先開發者需要為每一款編輯器都定義适配一種文法分析服務應用,那麼M個語言要在M個代碼編輯器中使用的話需要MxN個應用。而有了LSP的出現,開發者在解析代碼文法時隻需要遵循LSP協定格式,實作代碼補全、定義展示、代碼診斷等接口,就隻需要開發M+N個應用。

Code Review效率低?來試試智能文法服務

然而代碼分析往往需要耗費大量的時間和資源,當使用者請求某個文法服務(如檢視定義),後端需要克隆代碼,下載下傳依賴包,解析文法,建構索引(類比一下IntelliJ Idea初始化工程的場景),編輯器場景使用者已經習慣于這樣的方式,等待幾分鐘或許問題不大。但CR場景或者輕量級的代碼浏覽場景,這種方式就顯得時效性比較低了,幾分鐘後或許使用者已經完成了代碼浏覽,而且缺少持久化的存儲會導緻資源過度消耗。于是,LSIF就在這樣的背景下應運而生,秉承用空間換時間的思想,提前計算好文法分析結果以特定的索引格式存儲在雲上,進而快速響應不同使用者的多次請求。

援引官方示例來簡單介紹下LSIF,如下方代碼:

1、// this is a sample class

2、public class Sample {

3、}

假定隻有一種互動,當滑鼠移動到Sample的類名上,就會出現“this is a sample class”的注釋資訊。用LSIF的圖就可以如下描述。

Code Review效率低?來試試智能文法服務

一個sample檔案,包含了一個range資訊,這個range關聯了一個hoverResult。含義是該檔案的某個位置範圍内,觸發hover事件的話,就給出hoverResult存儲的結果。

如果用Json檔案描述這張圖的存儲,就可以得到如下結果:

1、{ id: 1, type: "vertex", label: "document", uri: "file:///abc/sample.java", languageId: "java" }

2、{ id: 2, type: "vertex", label: "range", start: { line: 0, character: 13}, end: { line: 0, character: 18 } }

3、{ id: 3, type: "edge", label: "contains", outV: 1, inVs: [2] }

4、{ id: 4, type: "vertex", label: "hoverResult", result: {["this is a sample class"]} }

5、{ id: 5, type: "edge", label: "textDocument/hover", outV: 2, inV: 4 }

實際一個工程的LSIF圖會非常複雜,經常會包含幾十萬個節點。感興趣的同學可以參考LSIF具體描述[1]。

三 實作方式

阿裡雲雲效的文法服務架構圖主要分為兩部分:

基于事件觸發的索引建構過程

基于使用者請求的文法服務響應

Code Review效率低?來試試智能文法服務

1 索引建構

使用者對代碼的浏覽場景主要集中在代碼評審和主幹分支的代碼浏覽,是以我們目前主要支援兩種場景的文法服務。文法服務接收來自代碼平台的事件消息,如代碼推送事件,評審的建立、更新、合并、關閉、重新開啟事件,來觸發文法服務建構。

我們的建構工作流排程主要基于阿裡巴巴開源的分布式排程架構tbschedule,該系統會通過zookeeper維護一個任務叢集,通過zookeeper做節點管理和任務分發,不重複不遺漏地快速處理排程任務。

針對不同語言,我們隻需要實作一次從源代碼到LSIF格式的轉換,就能将其應用在多種場景。多種代碼語言代碼語言都會被解析成統一的LSIF格式檔案。

針對阿裡巴巴内部主要的Java語言,我們利用開源Java代碼解析工具Spoon[2]将Java源代碼分析為AST(抽象文法樹),然後捕捉定義和引用、定義與注釋之間的關聯,将坐标資訊、注釋内容,文本類型,所屬檔案等資訊聚合,輸出為統一的LSIF的Json格式。

開發期間修複并适配了一些lsif-java的問題,如位置範圍資訊錯亂,召回多種遺漏的高亮詞類型,适配非Maven倉庫的索引建構。同時還修複了Spoon關于無法正确解析注釋中的部分注解的問題,PR已被Spoon社群接受合并[3]。

生成lsif.json檔案後,由于這個Json檔案較大,直接由前端加載并響應請求不太合理,後期增量生成與維護難度也很大,是以我們還需要一步:将lsif.json轉化為結構化資料,進而按需響應使用者查詢請求。lsif的圖存儲格式讓人自然地聯想到用圖資料結構存儲,圖查詢的速度也比較快,然而由于索引變化疊代較快,頻繁更換的ID導緻圖存儲難以适配增量方案,不同代碼庫不同語言的索引資料很難在一張圖中結構化,參考了社群的相關實踐,考慮到成本和性能,因為ES天然地适合大規模的資料存儲和索引,我們最後選擇了用ES(Elasticsearch)做結構化資料存儲。

我們将這種結構化的資料上傳到ES,然後文法服務後端伺服器會基于使用者的文法請求,構造ES請求Query,查詢定義、引用或注釋資訊,将其拼裝傳回。

對于分支,我們會持續更新和保留最新版本的索引資料;對于代碼評審,我們會建構源分支的每次Push版本和源目标分支的merge-base版本的索引。

索引建構的另外一個難點是增量計算。如上文所述,文法服務索引建構對資源的要求非常高,而現實中代碼庫不可避免地會存在頻繁送出的現象。如此引申出了兩個優化點:

利用增量的方式減少存儲内容的變更,加快索引建構速度。

利用分布式時序鎖減少頻繁請求帶來的壓力。

增量方案

每次分支索引建構成功,我們都會在資料庫中記錄分支對應的版本号,當該分支有了一次新的送出後,在生成lsif.json後,系統會比較兩個分支的Diff,擷取到變更檔案和變更類型,通過變更檔案來進一步提取索引受到影響的檔案(引用或定義的坐标資訊變更),分析出所有受影響的檔案和對應的ES增删操作後,完成增量索引上傳。這個增量的過程平均能減少45%的分支建構時間。

Code Review效率低?來試試智能文法服務

時序鎖管理

根據庫大小的差別,LSIF的索引建構時間為10秒至數分鐘不等,而使用者對同一個代碼倉庫的送出操作峰值可能會達到每分鐘近百次,即使我們采用了增量技術也很難滿足高頻的建構請求,并且送出事件觸達和排程任務執行無法保證精準的時序性。綜上所述,我們需要一個分布式時序鎖來保證任務排程的順序和盡量減少重複排程。

當同一代碼庫的不同推動消息紛湧而至,Redis維護的分布式鎖會做如下判斷:若該庫目前沒有正在運作的任務,将任務置于隊首,立即運作;若已有一個正在執行的任務,比較新來的Push消息是否是最新的,若是,則加入隊尾;當隊伍已有兩個成員時,則将任務丢棄,因為每次執行任務時,系統都會克隆分支代碼,基于最新的版本建構索引,如此就避免了多少次Push就需要執行多少次索引建構的可能性。考慮到線程意外退出的情況,隊首會每隔5秒鐘全局發送心跳,當隊尾或新來的任務監聽到心跳逾時,則會将隊首的任務放棄并執行新的任務。

Code Review效率低?來試試智能文法服務

2 文法服務響應

如前言的示例,使用者在使用文法服務時,主要有以下三個請求:

每次打開檔案擷取所有的可點選高亮詞

點選高亮詞擷取對應的定義與引用清單

點選定義和引用實作跳轉

針對第一個請求,系統會構造基于檔案路徑的過濾條件構造ES的Query請求,将目前檔案的所有高亮詞坐标資訊發送給前端,避免了前端做文法分詞,沒有建構好的檔案自然也不會在頁面上被高亮出來。另外,為了避免超大檔案對ES的壓力,前端會做分批動态加載。

針對第二個請求,我們在擷取定義與引用清單的過程中,不光要得到檔案名和位置資訊,還需要将對應的代碼内容展示出來,友善使用者了解。為了實作這個效果,我們新增了批量擷取檔案片段的接口。

對于第三個請求,同一個檔案内的跳轉會自動高亮到對應的代碼行,不同檔案間的跳轉會新開頁面并跳轉。

文法服務響應和文法索引建構是完全異步的,互不影響,支援獨立的資源擴縮。

3 索引清理

文法服務的索引大小約是代碼檔案内容的數倍,比較消耗存儲資源。是以針對使用者通常的使用習慣和場景,制定了一系列索引清理任務來避免資源過度的損耗。

當代碼評審合并或删除時,當分支删除時,系統會開始執行索引清理任務,釋放索引資源。

四 文法服務展望

缺少符号跳轉長久以來一直是頁面上代碼閱讀的痛點之一,各種文法協定和技術層出不窮,如LSIF、Kythe、SARIF、UAST、Tree-sitter,ctags,全球技術人都在為更智能的代碼分析,更好的代碼體驗,更高的代碼品質做努力。雲效文法服務後續也會逐漸加快文法建構速度,支援更多的代碼語言,滿足更多的文法場景,提升使用者的代碼浏覽體驗。

相關連結

[1]

https://microsoft.github.io/language-server-protocol/specifications/lsif/0.4.0/specification/

[2]

http://spoon.gforge.inria.fr/

[3]

https://github.com/INRIA/spoon/pull/3513
Code Review效率低?來試試智能文法服務

-END-

長按識别上圖二維碼進群,更多幹貨、優惠活動等你解鎖

進入産品進行體驗:

連結文字

本文作者:張昕東

快來試試,若有收獲,點個贊吧!!!