有了代碼變更分解提交工具SmartCommit,再也不擔心復合提交了


摘要:文將介紹一個代碼提交輔助工具SmartCommit,其主要功能是通過雜糅變更分解算法自動生成分組提交方案,接受開發者的反饋和交互式調整,漸進式地引導和輔助開發者做出符合最佳實踐的原子提交。

本文分享自華為雲社區《有了代碼變更分解提交工具SmartCommit,再也不擔心復合提交了》,作者: 敏捷的小智。

在群體協同的軟件開發中,代碼提交作為開發者最頻繁的日常操作之一,有必要遵循“代碼提交原子性”這一最佳實踐。然而,多項研究發現,在實際的開源和工業項目中普遍存在“復合提交”現象,即開發者經常將一段時間內做出的所有代碼變更一次性提交,即使其中的代碼修改包含了多種不相關的變更意圖或對應於多個開發和維護任務。本文將介紹一個代碼提交輔助工具SmartCommit [1],其主要功能是通過雜糅變更分解算法自動生成分組提交方案,接受開發者的反饋和交互式調整,漸進式地引導和輔助開發者做出符合最佳實踐的原子提交。

代碼提交的原子性

代碼變更提交是以Git為代表的版本管理系統基礎功能,也是開發者最頻繁的日常操作之一。在群體參與的軟件開發和維護中,個體開發者從不同的目的出發對代碼做出變更,被Git以代碼提交(commit)為單位進行記錄。代碼提交是版本管理系統其他功能工作的基礎,時間順序上的提交序列構成了代碼倉庫的提交歷史(commit history),跟蹤和記錄了軟件倉庫每一次變更的內容、時間、描述、提交者等信息。因此,清晰的代碼提交歷史在代碼審查、團隊協作、分支管理、持續集成、問題定位和修復等活動中具有重要作用。

如何恰當地使用代碼提交記錄、組織和管理個體貢獻,是群體高效協同進行軟件開發和維護的基礎。“面向任務的原子性代碼提交”是Git官方文檔中倡導的最佳實踐,也是一種被開源社區(如Angular、Vue等)和知名軟件公司(如Google、Microsoft等)明確提倡的規范和要求。

“代碼提交原子性”的具體含義是:每次提交中的代碼變更應該高內聚(cohesive)和自包含(inclusive),聚焦於一個軟件開發或維護任務(如添加新特性,修復 bug,重構等)。遵循這種最佳實踐,對於軟件開發者來說,在群體協同進行軟件開發時,有利於他人理解和審查代碼變更、定位問題引入提交並回退、揀選復用歷史變更等;對於軟件倉庫挖掘(MSR)的研究者來說,面向任務的提交有助於減少數據中的噪音,從而提供更清晰的演化歷史數據。

Git文檔中對於代碼提交原子的描述 [2]

Google工程實踐中對於變更提交者的要求 [3]

Angular項目中對於提交信息的規范 [4]

原子提交與復合提交

在群體協同的軟件開發中, “原子提交”(Atomic Commits)對於開發者群體的有效協同有着重要作用;然而,多項研究已發現,在開源項目和工業項目中普遍存在“復合提交” (Composite Commits)現象(大概占總commit數目的10~40%)。

與原子提交相反,復合提交指開發者將一段時間內做出的所有代碼變更一次性提交,即使其中雜糅了針對多個開發/維護任務的代碼修改。復合提交產生的原因主要包含三個方面:

  1. 在日常開發中開發人員經常會有意識或無意識地同時進行多個任務,例如在開發新功能時重構代碼結構(即 floss refactoring),或在優化功能的同時修復 bug或code smell。
  2. 盡管部分團隊或開源社區為開發者提供了代碼貢獻規范,但其中很少涉及對提交風格的明確規定或指導。
  3. 雖然部分版本控制系統或工具提供了挑選和組織細粒度代碼變更的功能(如 Git interactive staging,GitKraken/Fork等),但基本上完全依賴開發者的主動觸發和手動選擇,需要較高的認知和使用成本。

典型的原子提交(左)與復合提交(右)對比

代碼變更分解相關研究

針對復合提交問題,學術界提出了多種方法和技術對雜糅的代變更進行分解,如基於啟發式規則的方法[5]、基於程序切片技術的方法[6]、基於數據流和控制流依賴的方法[7],基於模式匹配的方法[8] 等。但是,這些方法存在以下共性的限制和不足:

  • 大部分采用事后分解的方式,在復合提交已經被記錄為版本歷史之后的某個階段(如代碼評審、提交揀選、歷史切片等)再檢測或分解復合提交。
  • 現有技術的分解結果往往因粒度過細而難以與開發者意圖和任務對應,因此不適用於代碼提交階段。
  • 現有技術未在代碼變更分解算法中考慮開發者的背景知識。考慮到原子代碼提交這一最佳實踐在實際執行中與項目背景、團隊要求、開發者習慣等因素相關,因此代碼變更分解過程需要結合開發者對於問題背景、領域、項目的知識,允許一定的靈活性和主觀性。

小結

面向任務、高內聚、自包含的原子提交是一種被廣泛,但由於多任務並行、缺乏統一規范、工具支持不足等原因,違反這一最佳實踐的復合提交現象仍然普遍發生。雖然現有研究中針對此問題已經存在很多研究,但其限制和不足使得現有方法難以真正被應用於實際的代碼提交工作流中。

代碼變更交互式分解工具SmartCommit

為了克服現有方法的限制和不足,本文將介紹SmartCommit,一個基於圖(Graph)的交互式代碼變更分解算法,其目標是在開發階段漸進式地引導和輔助開發者面向開發和維護任務進行原子代碼提交,從而從根源上避免復合提交的產生。

首先,SmartCommit將雜糅變更分解問題轉換成一個漸進式的方案優化問題:

給定一個變更集 = {c1, c2, c3, …, cn} 作為輸入,變更分解問題的目的是將 划分為一個由若干非空集合構成的列表 G = [ 1, 2, …, ],每個集合稱為一個變更分組(Change Group),對應於一個開發或維護任務。

如下圖所示,若將所有細粒度變更提交為一個 commit 視為初始狀態(Initial State),將每個細粒度代碼變更被獨立提交為一個commit 視為極端狀態(Extreme State),則變更分解的目的是在初始狀態和極端狀態之間尋找一個滿足代碼提交原子性的可接受狀態(Accepted Decomposition)。

但是,由於實際情況的復雜性和代碼提交的靈活性,變更分解過程很難完全自動地通過算法完成。因此,SmartCommit將變更分解過程視為一個人機交互的漸進式優化過程:由算法提供初始的分解方案建議(Initial Suggestion),再通過粗粒度控制(Coarse Control)和細粒度調整(Fine Tuning)兩種交互機制,輔助開發者快速形成期望的變更分組提交方案。算法可利用計算機的程序分析能力和精細量化評估算法,生成的分解方案可以為開發者提供一個比較好的起點;交互機制可以利用開發者對算法生成方案的反饋和調整,引導算法生成的分解方案盡量靠近期望狀態。考慮到代碼提交本身就是一個人機交互的過程(開發者需要通過命令添加變更、描述變更、關聯問題單等),SmartCommit的變更分解機制是一個自然的人機結合方法,可綜合利用算法的計算優勢和和開發者的信息優勢,共同完成雜糅變更分解任務。

雜糅變更集的交互式分解過程示意

以上思路被實現為SmartCommit算法,下圖為其工作流程,主要包含以下四個階段:

1. 變更集預處理(Preprocessing)

給定一個Git 工作區或一次復合提交,抽取出其中的代碼變更並以代碼變更塊(diff hunk)為單位進行表示,將所有代碼變更塊構成的集合稱為變更集(changeset);

2. 變更關系圖構建(Graph construction)

在代碼變更關系圖的基礎上,將變更中涉及的節點以代碼變更塊為單位進行聚合,將每個代碼變更塊作為點,將代碼變更塊之間的顯式和隱式關系作為邊,構建一個代碼變更塊關系圖(Diff Hunk Graph);

3. 變更交互式分解(Interactive decomposition)

通過一個以邊為中心的圖划分算法, 將代碼變更塊關系圖的點集划分為若干獨立子集,將每個子集轉換為一個變更組(change group),作為建議的分解方案提供給用戶進行交互式調整;

4. 變更一鍵式提交(Commits submission)

當分解方案達到可提交狀態時,開發者可以選擇需要提交的若干個變更組,附上描述該分組中代碼變更的信息,一鍵式將多個變更組提交到版本控制系統中。

SmartCommit算法流程概覽

由於篇幅所限,下文將主要圍繞SmartCommit算法的三個核心部分進行介紹:

1. 數據結構:用於建模分布在項目不同位置的代碼變更間關聯關系的圖結構

2. 分解算法:基於圖划分算法、以關系為核心生成代碼變更分解方案的算法

3. 交互機制:通過交互機制綜合算法分析能力與開發者背景知識的交互機制

數據結構

為了建模和管理細粒度的代碼變更間關系,我們采用了圖(Graph)這種數據結構,設計了“代碼變更關系圖(Change Relation Graph)”。代碼變更關系圖的點集由變更塊(Diff Hunk)組成,每個變更塊對應工作區或某次提交中一個獨立的代碼編輯/修改;邊集由變更塊間關系(Relation)組成,每條邊從某一個維度刻畫了所連接的兩個變更塊之間的關聯及其強度。

對於點集,SmartCommit通過分析輸入的工作區或某個復合提交,根據Git diff結果(基於文本)和代碼抽象語法樹(AST)抽取變更塊相關信息。必要的信息包括:

1. index:由file_index:hunk_index組成,唯一定位一個變更塊在源碼中的位置。file_index表示當前變更所屬的源文件(父文件)在變更集中的索引,從 0 開始編號;hunk_index表示當前變更在父文件內所有變更中的索引,從 0 開始按起始行號排序;

2. change_type:變更動作類型,如新增、刪除、修改等;

3. base_hunk/current_hunk:該變更塊在變更前(base)和變更后(current)版本中對應的代碼塊,其中包含文件類型、文件路徑、行號范圍、代碼片段、AST子樹等信息。

對於邊集,SmartCommit綜合了相關研究中被證明與變更間耦合性存在一定相關性的指標,從多個維度評估變更塊之間的關聯關系以及強度:

1. 結構性關聯(Structural Correlation)

指代碼變更塊之間直接或間接的語法和語義依賴,這些關系通常有方向性且不能在提交中被破壞(例如,方法的調用不能在其聲明/定義之前被提交,否則將在中間提交版本中包含編譯錯誤);

2. 啟發式關聯(Heuristic Correlation)

指可能推斷出多個細粒度變更源於同一編輯動作的啟發式規則,如代碼變更塊之間的相似度(similarity)和鄰近度(proximity),其目的是檢測系統性變更(systematic edits)、應用於克隆代碼的變更、定義域相同的相鄰變更等;

3. 重構性關聯(Refactoring Correlation)

旨在檢測出由同一次系統性或結構性變更產生的、分布在項目不同位置的多處細粒度代碼變更,主要指各種類型的重構操作;

4. 邏輯性關聯(Logical Correlation)

由程序語義上並不相關但編輯動作上一致的常見操作產生的多處變更,如代碼格式化變更、死代碼清理、文本上的移動等。

以上每種類型的關聯關系分別對應一種類型的邊,關聯關系的強度被作為邊的權值,其計算方法詳見論文中的細節描述和代碼實現。除了以上關系之外,代碼變更關系圖可以很容易地擴展其他維度的關聯關系,例如演化耦合關系(evolutionary coupling)、編輯時間差(time stamp difference)等。但是,為了獲得這些信息,算法在實現中需要依賴於特定類型的版本管理工具(VCS)或集成開發環境(IDE)對代碼編輯歷史的日志記錄。

分解算法

得到當前工作區中變更集對應的代碼變更塊關系圖后,我們把對變更集的分解問題轉化為針對代碼變更塊關系圖的圖划分問題,即綜合節點之間存在的邊以及邊上的權重,將代碼變更塊關系圖的點集划分為一組相互獨立(mutually exclusive)的子集。

借鑒多層次圖划分(Multi-level Graph Partitioning)思想,SmartCommit采用了一種基於 Kruskal 算法、以邊為中心的圖划分算法。該算法以一個代碼變更塊關系圖和一個可選的權重閾值作為輸入(當用戶未設置閾值時采用Max-gap Splitter算法動態確定一個臨界點)。首先,算法創建一個空的優先級隊列 用於保存邊,以及一個並查集數組 用於保存變更分組(每個頂點初始化為其中的一個元素,即將每個代碼變更塊單獨作為一個分組)。對於圖中的每條邊 ( , ),將其以三元組 ( , ( , )) 的格式添加到優先級隊列 中,其優先級的確定首先根據邊的權值降序排序,其次按邊起始節點的ID 和目標節點的ID 升序排序。然后,算法進入一個循環:彈出當前隊列中優先級最高的邊;如果當前邊的兩個端點已經在同一分組中,則這條邊被忽略,循環繼續;否則,如果其權重大於閾值,則合並兩個端點所在的分組。如果邊的權重小於閾值,或者 為空,則循環終止,從 中生成連通集作為結果;如果還存在單獨作為一組的節點,則將這些節點合並為一組,並追加到其他分組之后。最后,所有節點分組被輸出,作為代碼變更塊關系圖划分的結果。

基於Kruskal和Max-gap Splitter算法的圖划分過程

交互機制

基於圖划分算法得到的節點分組將被轉換為代碼變更分組,並作為算法建議的分組方案,以適當的形式提供給開發者進行檢查。如果建議的分組方案與開發者的期望有所偏差,開發者可以通過兩種交互式操作進行調整:

• 粗粒度控制(Coarse control)

通過控制算法終止條件,重新運行分解算法以產生不同粒度的分解方案。在SmartCommit的實際實現中,代碼變更塊關系圖會被緩存到內存或硬盤中,因此重新運行算法不需要重新進行圖的構建,生成速度較快。粗粒度控制的目的是利用開發者對於算法生成方案的反饋,指導算法更快速地接近期望的分組狀態,生成不需要或僅需少量細粒度調整的方案。

• 細粒度調整(Fine tuning)

通過在不同分組之間移動單個或多個代碼變更塊,將少數分配錯誤的變更塊移動到所應屬於的分組中。細粒度調整的目的是允許開發者對提交進行細粒度的微調,從而修正算法的結果或排除部分不需要提交的變更。

在通過調整得到可接受的分解方案后,開發者可以選擇部分或全部的變更組一鍵式產生多個提交,將選擇的變更組記錄為版本管理系統中的一系列連續的commit。

需要聲明的是,由於需要考慮不同項目之間的通用性,SmartCommit默認的分組粒度為廣義上的開發和維護任務(如實現新功能、修復問題、重構等),而非具體的細粒度變更(如添加類、改變方法參數、修改返回值類型等)。SmartCommit遵循semantic-release [9](一個自動化的版本發布工具)和commitizen [10](一個規范commit message的工具)對開發和維護任務的分類,因此可以配合這些工具進行使用。

Commitzen的提交類型分類規約

小結

為了從根源上避免復合提交的產生,SmartCommit着眼於在開發階段引導和輔助開發者考慮代碼變更的原子性。與此前工作的不同的是,SmartCommit基於圖結構從多個維度建模代碼變更間關系,面向廣義的維護和開發任務對變更進行分組,並引入了人機交互機制結合算法和開發者各自的優勢。

工具原型實現

SmartCommit的核心算法使用了Java進行實現[11],配套了一個基於NodeJS和Electron的GUI界面[12]。借助Java和Electron的跨平台特性,該工具支持 Windows、Linux、以及macOS 三種操作系統;既可以被包裝為獨立的桌面軟件或IntelliJ IDEA 插件進行使用,也可以通過配置成Git 子命令git sc,在代碼提交時通過Git 命令行進行調用。

作為華為-北京大學2019-2020技術合作項目,SmartCommit已於2020年初落地為一個Intellj IDEA插件,被華為雲、消費者雲、歐拉、雲核心網等部門多個團隊的工程師用於日常的代碼提交中。除了開源的基礎變更分解功能之外,華為內部的版本還額外提供了提交類型分類、提交信息自動生成、關聯問題單(Issue ID)推薦等功能。

實驗評估與驗證

研究者同時在開源項目和工業環境中對SmartCommit進行了實驗評估,並通過比對實驗結果交叉驗證算法和工具的效果。通過在3000個來自知名開源項目的模擬復合提交上進行受控實驗,以及對華為內部83名工程師長達36周的使用數據分析,結果表明:

1. 准確率:SmartCommit 產生的初始分解方案在 10 個開源項目上可達 71.00%–83.50% 的准確率(中位數) ,在2個工業項目上分別達到了74.70% 和 70.45% 的准確率(中位數)。

2. 交互性:在沒有用戶參與的情況下,僅通過細粒度調整需要更多的步驟(1-15 次操作);但在實際使用中存在粗粒度控制的情況下,所需的調整步數在 80% 的情況下不超過 5 步。

3. 運行性能:在90%的情況下,SmartCommit 可以在 5 秒內完成分析,且運行時間不隨着輸入變更集規模的增大而顯著增加,大多數用戶認為SmartCommit 的性能在日常工作中可以接受。

4. 實用價值:受訪的10名活躍用戶表示SmartCommit 可以有效地幫助開發者遵循最佳實踐,並且帶來了額外的收益,例如針對分組后的變更組更容易編寫commit message、在分組后更容易發現不應該提交的變更(如本地配置、個人信息、敏感數據)等。

總結

針對復合代碼提交問題,本文介紹了一個基於靜態程序分析和圖划分算法的代碼變更輔助分解與提交工具SmartCommit。該工具可自動分析細粒度變更間關系,並自動對雜糅變更或非原子提交進行分解;配合GUI前端界面,可交互式、漸進式地輔助開發者遵守面向任務進行提交這一最佳實踐。SmartCommit在開源和工業項目中分別進行了實驗驗證,結果表明其自動分解算法為開發者提供了一個提高代碼提交原子性的起點,且通過交互機制降低了開發者遵循最佳實踐的成本。

作為一項從研究成果中構建的工具原型,SmartCommit在實際使用中也存在一些限制和不足,例如:

1. 目前的實現只支持Git項目和Java語言代碼。

2. 算法未充分利用所有維度的變更間關聯,自動分解准確率存在進一步提升的空間。

3. 工具目前主要面向開發和提交階段,可替代git diff/add/commit/push等命令,是否可以應用於代碼評審階段?

為了滿足以上需求,進一步提升SmartCommit的效果並擴大可用范圍,我們目前正在開發SmartCommit的2.0版本,通過重構實現以下改進:

1. 圖構建方面:抽取並解耦語言相關的圖構建部分,使用通用的代碼分析器替代目前專用於Java的JDT Parser,以支持更多的語言。

2. 圖分解方面:使用矩陣形式存儲多重圖,增加更多維度的變更間關聯信息,並結合自頂向下的圖划分與自頂向上的點聚類算法,進一步引入數據驅動的方式提高自動分解算法的准確率。

3. 交互與應用:收集用戶在交互時產生的反饋數據並加以利用;為核心算法增加適用於處理Pull request的 API,集成變更描述生成算法等。

【參考資料】

[1] Bo Shen, Wei Zhang, Christian Kästner, Haiyan Zhao, Zhao Wei, Guangtai Liang, and Zhi Jin. SmartCommit: a graph-based interactive assistant for activity-oriented commits. In Proceedings of the 29th ACM Joint Meeting on European Software Engineering Conference and Symposium on the Foundations of Software Engineering (FSE), pp. 379-390. 2021.

[2] https://git-scm.com/docs/gitworkflows#_separate_changes

[3] https://google.github.io/eng-practices/

[4] https://github.com/angular/angular/blob/master/CONTRIBUTING.md

[5] K. Herzig and A. Zeller. “The impact of tangled code changes”. In: 2013 10th Working Conference on Mining Software Repositories (MSR). 2013: 121–130.

[6] W. Muylaert and C. De Roover. “Untangling Composite Commits Using Program Slicing”. In: 2018 IEEE 18th International Working Conference on Source Code Analysis and Manipulation (SCAM). 2018: 193–202.

[7] M. Barnett, C. Bird, J. Brunet et al. “Helping developers help themselves: Automatic decomposition of code review changesets”. In: 2015 IEEE/ACM 37th IEEE International Conference on Software Engineering. 2015: 134–144.

[8] M. Dias, A. Bacchelli, G. Gousios et al. “Untangling fine-grained code changes”. In: 2015 IEEE 22nd International Conference on Software Analysis, Evolution, and Reengineering (SANER). 2015: 341–350.

[9] https://github.com/semantic-release/semantic-release

[10] https://github.com/commitizen/conventional-commit-types

[11] https://github.com/Symbolk/SmartCommitCore

[12] https://github.com/Symbolk/SmartCommit

 

點擊關注,第一時間了解華為雲新鮮技術~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM