網站配色的方案最新新聞事件今天疫情
背景:
最近在做代碼大語言模型生成項目代碼的課題。代碼生成現在大部分的工作是在做即時代碼生成,這個有點類似代碼智能提示,只不過生成的可能是一段片段代碼;然而對于整個項目代碼的生成做的團隊并不多,原因大致如下:
1.項目代碼比較復雜,關聯(lián)的代碼文件較多,文件關系、類關系、方法關系理清楚比較難
2.項目代碼涉及的代碼量較大,也就是上下文較多;要讓模型理解整個項目抓住重點很難
3.項目代碼量大,所以要求模型允許輸入的token長度較長
4.項目代碼生成如何抽象問題、設計任務、構建語料訓練模型詩歌難題
也正是因為上面的幾個問題點,導致雖然項目代碼的生成是一個很有商業(yè)價值的方向,但卻找不到合適的解決方案。今天介紹的這篇文章《CodePlan: Repository-level Coding using LLMs and Planning》并未提出項目代碼生成方案,但是提出了項目代碼增刪改后如何自動的把應用到發(fā)生改變的代碼片段自動修改的方案。
這個方案的解決思路如下:
1.對項目代碼做依賴關系的分析,構建代碼方法依賴關系數
2.定義了代碼片段可能的幾種變化行為,并針對行為定義了幾種actiom
3.當代碼片段發(fā)生變化,把信息結構化成prompt輸入LLM,LLM根據prompt觸發(fā)對應項目修改action
4.循環(huán)3直到所有的代碼依賴全部更新完為止
依賴圖的示例,帶有關系作為邊緣標簽的注釋
代碼片段可能的幾種變化行為和對應actiom
prompt模版
論文翻譯:
軟件工程活動,如包遷移、修復來自靜態(tài)分析或測試的錯誤報告以及向代碼庫添加類型注釋或其他規(guī)范,涉及廣泛編輯整個代碼存儲庫。我們將這些活動形式化為存儲庫級別的編碼任務。
最近的工具,如GitHub Copilot,由大型語言模型(LLMs)驅動,已成功提供了高質量的解決方案來解決局部編碼問題。存儲庫級別的編碼任務更加復雜,無法直接使用LLMs解決,因為存儲庫內的代碼相互依賴,整個存儲庫可能太大,無法適應提示。我們將存儲庫級別的編碼視為一個規(guī)劃問題,并提出了一個任務不可知的框架,稱為CodePlan來解決它。CodePlan合成了一系列多步編輯(計劃),其中每一步都會調用存儲庫中的代碼位置上的LLM,上下文來自整個存儲庫、先前的代碼更改和任務特定的說明。CodePlan基于一種新穎的增量依賴分析、變更可能影響分析和自適應規(guī)劃算法的組合。
我們在兩個存儲庫級別的任務上評估了CodePlan的有效性:包遷移(C#)和臨時代碼編輯(Python)。每個任務在多個代碼存儲庫上進行評估,每個存儲庫都需要對許多文件進行相互依賴的更改(2至97個文件之間)。以LLMs自動化處理這種復雜程度的編碼任務以前尚未實現。我們的結果顯示,與基線相比,CodePlan與實際情況更匹配。CodePlan能夠使5/6個存儲庫通過有效性檢查(例如,無錯誤地構建和進行正確的代碼編輯),而基線(沒有規(guī)劃但具有與CodePlan相同類型的上下文信息)無法使任何存儲庫通過這些檢查。我們將在https://aka.ms/CodePlan發(fā)布我們的數據和評估腳本。
CCS概念:? 計算方法學→不確定性下的規(guī)劃;? 軟件及其工程→軟件維護工具;軟件演進;自動編程。
附加關鍵詞和短語:自動編碼、存儲庫、LLMs、靜態(tài)分析、計劃、一系列編輯。
**1 引言**
大型語言模型(LLMs)的卓越生成能力[24, 28, 30, 35, 57, 73]開辟了自動化編碼任務的新途徑?;贚LMs構建的工具,如Amazon Code Whisperer [14]、GitHub Copilot [38]和Replit [66],現在廣泛用于根據自然語言意圖和周圍代碼的上下文完成代碼,并根據自然語言指令執(zhí)行代碼編輯[78]。這些編輯通常針對代碼的小區(qū)域,例如完成或編輯當前行,或整個方法的主體。
雖然這些工具有助于軟件工程的“內循環(huán)”,其中開發(fā)人員在編輯器中編碼并編輯小代碼區(qū)域,但在軟件工程的“外循環(huán)”中有一些涉及整個代碼存儲庫的任務。例如,如果我們的代碼存儲庫使用了一個名為𝐿的庫,而庫𝐿的API從版本𝑣𝑛更改為版本𝑣𝑛+1,我們需要遷移我們的代碼存儲庫以正確調用修改后的版本。這樣的遷移任務不僅涉及對調用庫𝐿相關API的存儲庫的所有區(qū)域進行編輯,還涉及對存儲庫的區(qū)域(跨文件邊界)進行編輯,這些區(qū)域在更新后的代碼上具有傳遞性的語法和語義依賴關系。
這在圖1中進行了說明,圖中顯示了復雜數庫的API變化。我們的任務是根據這個變化遷移我們的代碼存儲庫。圖3的左側顯示了我們的代碼存儲庫的相關部分,其中使用了復雜數庫。具體來說,文件Create.cs具有方法func,該方法調用庫中的create_complex方法,而Process.cs具有方法process,該方法調用func。
我們可以將圖1中的任務描述和func的主體傳遞給LLM,以生成func的修改后代碼,如圖3的右側所示。如圖所示,LLM已正確編輯了對create_complex API的調用,以使其返回Complex類型的對象,而不是兩個浮點數值的元組。請注意,此編輯導致方法func的簽名發(fā)生了變化-它現在返回Complex類型的對象。這需要對調用方法func的調用者進行更改,例如圖3左下角顯示的文件Process.cs中的process方法。如果沒有對process方法的合適更改,我們的代碼將無法構建!圖3右下角顯示了將存儲庫置于一致狀態(tài)以使其構建無錯誤的process方法的適當更改。
問題闡述。上述遷移任務代表了一類任務,涉及為各種目的編輯整個代碼存儲庫,例如修復靜態(tài)分析或測試中的錯誤報告,修復有缺陷的編碼模式,重構或添加類型注釋或其他規(guī)范。每個這類任務都涉及一組種子規(guī)范,例如圖1中所示的規(guī)范,這些規(guī)范是代碼編輯任務的起點。這些種子規(guī)范通常會觸發(fā)代碼的其他編輯要求,而這些要求需要在代碼存儲庫中的依賴關系中傳播,以執(zhí)行存儲庫中的其他編輯以完成編碼任務。通常,這種跨依賴傳播的編輯工作是手動完成的。
我們的目標是構建一個存儲庫級別的編碼系統(tǒng),該系統(tǒng)可以自動為像圖3中的process方法所需的派生規(guī)范生成規(guī)范,以將存儲庫置于有效狀態(tài)。這里,有效性是相對于一個“oracle”來定義的,可以實例化為多種強制存儲庫級別正確條件的方式,例如無錯誤地構建、通過靜態(tài)分析、通過類型系統(tǒng)或一組測試,或通過驗證工具。我們定義一個由LLM驅動的存儲庫級別編碼任務如下:
提出的解決方案。在本文中,我們提出了一種通過將(LLM驅動的)存儲庫級別編碼視為一個規(guī)劃問題來計算派生規(guī)范的方法。自動規(guī)劃[37, 67]旨在解決多步問題,其中每一步執(zhí)行多個替代操作之一,以達到目標狀態(tài)。它在許多領域廣泛應用,如運動規(guī)劃[47]、自動駕駛[39]、機器人學[44]和定理證明[26]。
我們提出了一個稱為CodePlan的任務不可知框架,它合成一個多步計劃來解決存儲庫級別編碼任務。如圖2所示,CodePlan的輸入包括一個存儲庫、一個通過自然語言指令或一組初始代碼編輯表達的任務種子規(guī)范、一個正確性oracle和一個LLM。CodePlan構建一個計劃圖,圖中的每個節(jié)點標識LLM需要履行的代碼編輯義務,并且一條邊表示目標節(jié)點需要在源節(jié)點之后履行。CodePlan監(jiān)視代碼編輯并自適應擴展計劃圖。編輯Δ來自任務描述𝑠𝑒𝑒𝑑𝑠,而編輯Δ則根據一種新穎的增量依賴分析、變更可能影響分析和自適應規(guī)劃算法的組合來確定和上下文化。合并塊將LLM生成的代碼合并到存儲庫中。一旦計劃中的所有步驟都完成,存儲庫將由oracle進行分析。如果oracle驗證存儲庫,則任務完成。如果發(fā)現錯誤,則將錯誤報告用作下一輪計劃生成和執(zhí)行的種子規(guī)范。
再次考慮圖1中指定的示例API遷移任務,在圖3的代碼上。CodePlan使用圖1中的指令作為種子規(guī)范執(zhí)行方法func的編輯。通過分析圖3(a)到(b)之間的代碼更改,它將更改分類為逃逸更改,因為它影響到方法func的簽名。變更可能影響分析確定func的調用者可能會受到影響,因此自適應規(guī)劃算法使用調用者-被調用者依賴關系推斷出一個派生規(guī)范,以編輯調用func的方法process。種子和派生更改都通過為LLM創(chuàng)建適當的提示來執(zhí)行,生成的代碼存儲庫通過oracle,即構建無錯誤。請注意,這只是一個簡單的示例,只有一次更改傳播。在實踐中,派生更改本身可能需要傳遞其他更改,而CodePlan處理這種情況。
提出的解決方案。?與我們的規(guī)劃方法相比,一個更簡單的替代方案是使用oracle來推斷派生規(guī)范。例如,在圖3中進行種子更改后,構建系統(tǒng)可以找到process方法中的錯誤。這有重要的限制。首先,即使它們導致行為更改,也不是所有更改都會引發(fā)構建錯誤,例如,將返回值從True更改為False而不更改返回類型。其次,構建系統(tǒng)在代碼中斷時對因果關系毫不關心。例如,如果根據種子規(guī)范更改了覆蓋方法的簽名,那么相應的虛擬方法需要進行類似的更改。然而,構建系統(tǒng)(在運行在存儲庫的中間、不一致快照時)指責覆蓋方法不符合虛擬方法。試圖天真地修復構建錯誤會導致回滾種子更改。CodePlan的靜態(tài)分析和規(guī)劃組件克服了這些限制。我們在實驗中將CodePlan與使用構建系統(tǒng)迭代識別破壞性更改并使用LLM修復它們的基線進行了比較。我們的定量和定性結果表明,CodePlan優(yōu)于這種以oracle為導向的修復技術。
貢獻。?就我們所知,迄今為止還沒有明確識別和解決LLM對存儲庫進行的代碼編輯的影響以及系統(tǒng)地規(guī)劃一系列相互依賴的編輯的問題,這是自動化存儲庫級別編碼任務的問題。
在存儲庫級別編碼任務的領域中,已經發(fā)現兩種上下文對于提示LLMs非常有用:(1)空間上下文,通過靜態(tài)分析[9, 34, 51, 59, 61, 70, 71, 77]或檢索[81, 85]提供跨文件信息給模型,和(2)時間上下文,以歷史編輯為條件對倉庫中的預測進行調整[23, 40, 64, 76]。由于CodePlan監(jiān)視代碼更改并維護存儲庫范圍的依賴關系圖,因此我們在一個統(tǒng)一的框架中提供了這兩種形式的上下文?,F有技術假定開發(fā)人員提供下一個編輯位置,并且不考慮編輯對依賴代碼的影響。相反,通過推斷每個更改的影響,CodePlan將更改傳播到依賴代碼,從而自動化存儲庫級別編碼任務通過一系列編輯的方式。
總結一下,我們在本文中做出了以下貢獻:
- 我們首次正式化了使用LLMs自動化存儲庫級別編碼任務的問題,這需要分析代碼更改的影響并在整個存儲庫中傳播它們。目前沒有系統(tǒng)且可擴展的解決方案來解決這個問題。
- 我們將存儲庫級別編碼視為一個規(guī)劃問題,并設計了一個稱為CodePlan的任務不可知框架,該框架基于增量依賴分析、變更可能影響分析和自適應規(guī)劃算法的新穎組合。CodePlan合成了由LLM執(zhí)行的多步編輯鏈(計劃)。
- 我們使用gpt-4-32k模型在兩個存儲庫級別編碼任務上進行了實驗:C#存儲庫的包遷移和Python存儲庫的時間代碼編輯。我們將其與使用oracle(C#的構建系統(tǒng)和Python的靜態(tài)類型檢查器)來識別派生編輯規(guī)范(與CodePlan中使用的規(guī)劃相反)的基線進行比較。我們在基線中使用了與CodePlan相同的上下文化方法。
- 我們的結果表明,與基線相比,CodePlan與實際情況更匹配。CodePlan能夠使5/6個存儲庫通過有效性檢查,而基線無法使任何存儲庫通過。除了2個專有存儲庫外,我們將在https://aka.ms/CodePlan發(fā)布我們的數據和評估腳本。
**2 設計**
在本節(jié)中,我們首先概述用于自動化存儲庫級別編碼任務的CodePlan算法(第2.1節(jié))。然后,我們介紹了CodePlan的靜態(tài)分析(第2.2節(jié))和自適應規(guī)劃以及計劃執(zhí)行(第2.3節(jié))組件。
**算法 1:CodePlan算法,用于自動化存儲庫級別編碼任務。Cyan和Orchid中的數據結構和函數在第2.2和第2.3節(jié)中有解釋。**
CodePlan算法(算法1)接受四個輸入:
- 存儲庫的源代碼 𝑅。
- 任務的一組種子編輯規(guī)范 Δ。
- 一個oracle Θ。
- 一個LLM 𝐿。
該算法的核心數據結構是一個計劃圖 𝐺,它是一個具有多個根節(jié)點的有向無環(huán)圖(第4行)。計劃圖中的每個節(jié)點都是一個元組 ?𝐵, 𝐼, 𝑆𝑡𝑎𝑡𝑢𝑠?,其中 𝐵 是存儲庫 𝑅 中的一塊代碼(即代碼位置的序列),𝐼 是編輯指令(類似于圖1中所示的示例),𝑆𝑡𝑎𝑡𝑢𝑠 要么是 𝑝𝑒𝑛𝑑𝑖𝑛𝑔 要么是 𝑐𝑜𝑚𝑝𝑙𝑒𝑡𝑒𝑑。
CodePlan算法還維護一個依賴圖 𝐷(第5行)。圖4說明了依賴圖的結構。我們將在第2.2.1節(jié)中詳細討論它。目前,只需知道依賴圖 𝐷 表示存儲庫 𝑅 中代碼塊之間的語法和語義依賴關系。
在第6-9行的循環(huán)中,直到 Δ 不為空為止執(zhí)行。第7行調用 InitializePlanGraph 𝑠𝑒𝑒𝑑𝑠 函數(第11-13行),該函數將 Δ 中的所有更改添加為計劃圖的根節(jié)點。每個編輯 𝑠𝑒𝑒𝑑𝑠 規(guī)范包括一個代碼塊 𝐵 和一個編輯指令 𝐼。對于根節(jié)點,狀態(tài)設置為 pending(第13行)。然后,在第8行調用 AdaptivePlanAndExecute 函數,該函數執(zhí)行計劃,更新依賴圖以反映每個代碼更改,并根據需要擴展計劃。一旦計劃圖完全執(zhí)行,就會在存儲庫上運行 oracle Θ。它返回錯誤位置和診斷消息,這些將形成下一輪的 Δ。如果存儲庫通過了oracle的檢查,則返回一個空集合,并且CodePlan算法終止?,F在讓我們討論 AdaptivePlanAndExecute,這是主要的工作流程。它迭代地選擇每個待處理節(jié)點并處理它。處理帶有編輯規(guī)范的待處理節(jié)點,其中包含塊 𝐵 和編輯指令 𝐼,涉及以下五個步驟:
- 第一步(第19行)是提取要編輯的代碼片段。簡單地提取塊 𝐵 的代碼會丟失與周圍代碼的關系信息。另一方面,保留整個文件占用了提示空間,并且通常是不必要的。我們發(fā)現,當一個塊屬于一個類時,周圍的上下文最有幫助。對于這樣的塊,我們繪制包含類。也就是說,除了塊 𝐵 的代碼之外,我們還保留了包圍類及其成員的聲明。正如我們稍后討論的那樣,這種草圖表示還有助于更容易地將LLM的輸出合并到源代碼文件中。?
- 第二步(第21行)是收集編輯的上下文。編輯的上下文(第38-41行)包括(a)空間上下文,其中包含與塊 𝐵 相關的代碼,例如從塊 𝐵 中調用的方法,以及(b)時間上下文,其中包含導致需要編輯塊 𝐵 的先前編輯。時間上下文由從計劃圖的根節(jié)點到 𝐵 的路徑上的編輯形成。?
- 第三步(第23-24行)使用第一步中提取的片段、編輯規(guī)范中的指令 𝐼 和第二步中提取的上下文構建編輯的提示,并使用提示調用LLM以獲取編輯后的代碼片段。?
- 第四步(第26-28行)將編輯后的代碼合并回存儲庫。由于代碼已更新,許多依賴關系,如調用者-被調用者、類層次結構等,可能需要更改,因此這一步還會更新依賴圖 𝐷。?
- 第五和最后一步(第30-35行)進行自適應規(guī)劃,以傳播當前編輯對依賴代碼塊的影響。這涉及對編輯塊中的更改進行分類,并根據更改類型選擇在依賴圖中遍歷和定位受影響塊的正確依賴關系。例如,如果當前塊 𝐵 中方法 𝑚 的編輯涉及到方法簽名的更新,那么所有調用者 𝑚 都會受到影響(圖3的情況)。對于每個受影響的塊 𝐵′ 和依賴關系 rel,該關系將塊 𝐵 與 𝐵′ 連接到依賴圖中,我們得到一對 ?𝐵′, rel?。如果計劃圖中存在 𝐵′ 的節(jié)點并且它處于待處理狀態(tài),則我們將從 𝐵 到 𝐵′ 的邊添加到計劃圖中,標記為 rel。否則,該邊將添加到新創(chuàng)建的 𝐵′ 節(jié)點中(第34行)。塊 𝐵 被標記為已完成(第31行)。?
- **2.2 靜態(tài)分析組件**
現在我們關注CodePlan中使用的靜態(tài)分析組件。我們將涵蓋算法1中帶有青色背景的所有數據結構和函數。
**2.2.1 增量依賴分析**
LLM可以提供一個代碼片段和編輯指令來進行編輯。雖然LLM可以準確執(zhí)行所需的編輯,但分析編輯對存儲庫其余部分的影響超出了LLM調用的范圍。我們認為靜態(tài)分析非常適合完成這項工作,并提出了相應的增量依賴分析。
**DependencyGraph.** 依賴分析[12]用于跟蹤代碼元素之間的語法和語義關系。在我們的情況下,我們對導入語句、方法、類、字段聲明和語句之間的關系感興趣(不包括僅在封閉方法內部定義的局部變量)。正式地說,依賴圖 𝐷 = (𝑁,𝐸),其中 𝑁 是一組節(jié)點,代表上述的代碼塊,𝐸 是一組帶有標簽的邊,邊的標簽表示邊的源節(jié)點和目標節(jié)點之間的關系。圖4說明了我們跟蹤的所有關系,這些關系作為標記的邊。這些關系包括(1)語法關系(ParentOf 和 ChildOf、Construct 和 ConstructedBy)表示代碼塊 𝑐 與在語法上包圍 𝑐 的塊 𝑝 之間的關系;特殊情況是構造函數和其封閉類之間的關系,由 Construct 和 ConstructedBy 表示,(2)導入關系(Imports 和 ImportedBy)表示導入語句與使用導入模塊的語句之間的關系,(3)繼承關系(BaseClassOf 和 DerivedClassOf)表示類與其超類之間的關系,(4)方法重寫關系(Overrides 和 OverriddenBy)表示重寫方法與被重寫方法之間的關系,(5)方法調用關系(Calls 和 CalledBy)表示語句與其調用的方法之間的關系,(6)對象實例化關系(Instantiates 和 InstantiatedBy)表示語句與創(chuàng)建該對象的構造函數之間的關系,(7)字段使用關系(Uses 和 UsedBy)表示語句與使用的字段聲明之間的關系。
**ConstructDependencyGraph.** 依賴關系是通過靜態(tài)分析跨存儲庫中的源代碼生成的。我們將存儲庫的源代碼表示為抽象語法樹(AST)的森林,并在AST子樹之間添加依賴關系邊緣。文件級別的分析用于派生語法和導入關系。所有其他關系需要跨類、跨過程的分析,可以跨越文件邊界。特別是,我們使用類層次分析[32]來派生語義關系。
**ClassifyChanges.** 正如在第2.1節(jié)中討論的,CodePlan在第四步中將LLM生成的代碼合并到存儲庫中。通過對比前后的代碼,我們對代碼更改進行分類。表1(第一列和第二列)給出了原子更改的類型及其標簽。廣義上,更改分為修改、添加和刪除更改,進一步根據更改的構造進行分類。我們區(qū)分方法體和方法簽名的更改。同樣,我們區(qū)分類聲明的更改、其構造函數的更改或其字段的更改。還會識別導入語句或使用導入的語句的更改。這些都是原子更改。LLM可以在給定的代碼片段中進行多個同時編輯,導致多個原子更改,所有這些更改都由ClassifyChanges函數識別。
**UpdateDependencyGraph.** 當LLM生成的代碼合并時,與更改站點的代碼相關的依賴關系會重新分析。表1(第三列)根據ClassifyChanges推斷的標簽提供了更新依賴圖 D 到 D' 的規(guī)則。對于修改更改,我們重新計算已更改代碼的關系,但不包括構造函數。構造函數與其封閉類之間有一個語法關系,不需要重新計算。對于添加更改,為添加的代碼創(chuàng)建新的節(jié)點和邊緣。與語法關系對應的邊緣以直接方式創(chuàng)建。如果更改同時添加了一個元素(導入、方法、字段或類)及其使用,我們將在分析使用它的語句之前為添加的元素創(chuàng)建一個節(jié)點。方法的添加需要特殊處理,如表中所示:如果添加了一個重寫方法 C.M,那么如果調用是在類型為 C 的接收對象上發(fā)出的,則將與匹配的被重寫方法 B.M 的Calls/CalledBy邊緣重定向到C.M。刪除重寫方法需要與表1中所述的類似處理。所有其他刪除更改需要按照表中所述刪除節(jié)點和邊緣。
**2.2.2 變更可能影響分析**
在第五步中,CodePlan通過LLM進行的代碼更改來識別可能受到影響的代碼塊。讓 Rel(D, B, rel) 表示通過關系 rel 在依賴圖 D 中與塊 B 相連接的代碼塊的集合。讓 D 和 D' 分別表示在表1中的更新之前和之后的依賴圖。
**獲取受影響的塊。** 表1的最后一列告訴我們如何為每種更改類型識別受影響的代碼塊。當編輯方法 M 的主體時,我們執(zhí)行逃逸分析 [22, 29] 來確定是否已受到調用 M 的調用者中可訪問的任何對象(逃逸對象)的更改影響。如果是的話,M 的調用者(通過 Rel(D, M, CalledBy) 識別)被標識為受影響的塊。否則,更改局限于該方法,沒有受影響的塊。如果編輯了方法的簽名,則通過繼承層次結構中的方法重寫關系與之相關的調用者和方法受到影響。簽名更改本身可能會影響 Overrides 和 OverridenBy 關系,例如,@Override 訪問修飾符的添加或刪除。因此,在更新后的依賴圖 D' 中通過這些關系相關的塊也被視為受影響,如表1所示(帶有 MMS 標簽的行)。當修改類 C 的字段 F 時,使用 F 的語句、C 的構造函數以及 C 的子類/超類都會受到影響。當修改類時,按照 D 和 D',實例化它以及其子類/超類的方法都會受到影響。對構造函數的修改具有類似的規(guī)則,只是這種更改不會更改繼承關系,因此只需要 D。當修改導入語句 I 時,使用導入模塊的語句會受到影響。
添加和刪除更改比修改更改要簡單,它們的規(guī)則設計沿用了上面討論的原則。出于篇幅考慮,我們不會逐步解釋每個更改。我們假設代碼中不會使用新添加的類或導入。因此,添加它們不會導致任何受影響的塊。在我們的實驗中,我們發(fā)現表1中的規(guī)則已經足夠了。但是,如果需要,CodePlan可以輕松配置以適應表1中規(guī)則的變化。
**2.3 自適應規(guī)劃和計劃執(zhí)行**
現在我們討論來自算法1的Orchid背景中的數據結構和函數。
**2.3.1 自適應規(guī)劃**。在使用GetAffectedBlocks識別受影響塊之后,CodePlan創(chuàng)建需要使用LLM解決的更改義務,以使依賴代碼與更改保持一致。如第2.1節(jié)所討論的,這是一個迭代過程。
**PlanGraph。** 計劃圖P = (𝑂, 𝐶)是一個帶有一組義務𝑂的有向無環(huán)圖,每個義務都是三元組 ?𝐵, 𝐼, 𝑠𝑡𝑎𝑡𝑢𝑠?,其中B是一個塊,I是一條指令,狀態(tài)要么是待定的,要么是已完成的。𝐶中的邊記錄了原因,源義務和目標義務之間的塊之間的依賴關系。換句話說,邊的標簽標識了在表1中的更改可能影響規(guī)則中的哪個Rel子句導致創(chuàng)建目標義務。
**ExtractCodeFragment。** 如第2.1節(jié)的第一步所討論的,僅提取塊B的代碼是次優(yōu)的,因為它會丟失上下文。ExtractCodeFragment函數獲取代碼塊所屬的整個類,保留B的完整代碼,并僅保留類和其他類成員的聲明。我們發(fā)現這很有用,因為類和其他成員的名稱和類型為LLM提供了額外的上下文。通常情況下,LLM需要同時進行多個更改。例如,在我們的一些案例研究中,LLM必須添加字段聲明,將參數傳遞給構造函數并在構造函數中使用它來初始化字段。通過將周圍代碼的草圖作為代碼片段提供給LLM,LLM可以在正確的位置進行這些更改。代碼片段提取邏輯通過遍歷AST并“折疊”掉已經草繪的子樹(例如,方法主體)來實現。如第1節(jié)所述,即使存在多個同時更改,這種草繪表示也允許我們將LLM生成的代碼放回AST而不會產生歧義。
**GetSpatialContext。** CodePlan中的空間上下文是指代碼塊在代碼庫中的排列和關系,有助于理解類、函數、變量和模塊的結構和相互作用。它對于進行準確的代碼更改非常關鍵。CodePlan利用依賴圖來提取空間上下文,將代碼表示為節(jié)點以及它們之間關系的邊。這個圖使CodePlan能夠遍歷代碼庫,識別相關的代碼塊,并保持對它們的空間上下文的意識。因此,在生成代碼編輯時,依賴圖使CodePlan能夠進行上下文感知的代碼修改,使其與代碼的空間組織保持一致,從而增強了其代碼編輯能力的準確性和可靠性。
**GetTemporalContext。** 計劃圖記錄了所有更改義務及其相互依賴關系。通過將計劃圖的根節(jié)點到目標節(jié)點的所有路徑線性化,可以提取時間上下文。每個更改都是在更改之前和之后的代碼片段的一對。時間上下文還說明了“原因”(記錄為邊標簽),將目標節(jié)點與其前驅節(jié)點連接起來。例如,如果節(jié)點A通過CalledBy邊連接到B,那么B的時間上下文是A的前后片段,以及一條說“B調用A”的語句,這有助于LLM理解最新時間更改(對A進行更改)與當前義務(對B進行更改)之間的因果關系。
**2.3.2 計劃執(zhí)行。** CodePlan迭代選擇計劃圖中的待定節(jié)點,并調用LLM來履行更改義務。
**MakePrompt。** 在提取了要編輯的代碼片段以及相關的空間和時間上下文之后,我們構建了一個要傳遞給LLM的提示,其結構如下。我們首先使用特定于任務的說明p1,然后列出到目前為止在存儲庫中進行的與要編輯的片段相關的編輯p2。接下來的部分p3說明了p2中出現的每個片段與要編輯的片段的關系。然后是空間上下文p4和要編輯的片段p5。
**Oracle和計劃迭代。** 一旦計劃圖中的所有節(jié)點都標記為已完成,并且沒有添加新節(jié)點,就完成了一次存儲庫級別的代碼編輯迭代。如圖2所示,對存儲庫調用了Oracle。如果Oracle標記了任何錯誤(例如,構建錯誤),則將錯誤位置和診斷消息添加為下一次迭代的種子更改,然后自適應規(guī)劃再次開始。如果Oracle沒有標記任何錯誤,CodePlan終止。
**3 實施**
在本節(jié)中,我們提供了構成我們方法核心的實施組件的詳細概述。
**依賴圖構建。** CodePlan方法的核心是依賴圖,它有助于表示代碼塊之間復雜的關系。為了從代碼存儲庫構建這個依賴圖,我們采用了系統(tǒng)性的方法。最初,我們解析存儲庫中的所有代碼文件,利用tree-sitter庫[25]生成類似AST的結構。這種結構化表示簡化了代碼庫中各種基本代碼塊的識別。例如,圖5示例了tree-sitter生成的C#代碼片段的AST結構。代碼塊在不同的級別進行識別,包括類、方法、導入語句和非類表達式。例如,在圖5中,以class_declaration節(jié)點為根的子樹對應于SyncSubscriberTest類。
**C#中的關系識別。** 在C#存儲庫的背景下,在依賴圖中建立邊涉及到在AST內部仔細跟蹤關系。我們?yōu)閳D4中列出的每種關系類型制定了自定義邏輯,包括重要的連接,如調用者-被調用者、覆蓋-被覆蓋、基類-派生類等。為了說明,對于調用者/被調用者關系,我們在AST中查找invocation_expression節(jié)點。隨后,我們處理這些節(jié)點下的子樹,以解析關鍵細節(jié),如目標類和調用方法的名稱。有了這些信息,我們在啟動方法調用的代碼塊和目標類中的相應方法塊之間創(chuàng)建Calls/CalledBy關系鏈接。雖然我們?yōu)檫@些關系實施了自定義邏輯,但值得注意的是,由于其固有的靈活性,也可以將用于C#的其他依賴關系分析工具(如C#的語言服務器(LSP)[5]、CodeQL [2]或類似解決方案)集成到我們的系統(tǒng)中。
**Python中的關系識別。** 對于Python存儲庫,我們使用Jedi [4] - 一種靜態(tài)分析工具,用于在整個代碼庫中查找符號的引用和聲明。這些功能被用來識別依賴圖中的關系,如調用者-被調用者、覆蓋-被覆蓋和基類-派生類。
**集成GPT-4進行代碼編輯。** CodePlan充分利用了GPT-4 [57]的卓越能力來有效地執(zhí)行代碼編輯。在為編輯模型構建輸入數據時,我們仔細提供了時間上下文、空間上下文和實際要編輯的代碼,以代碼片段的形式提供。這些代碼片段表示包含編輯位置的類或方法,并以草圖表示,如第2.1節(jié)所述。這種草圖表示確保了模型為每個編輯位置提供了豐富的上下文,從而顯著提高了生成的編輯的質量和準確性。
**語言可擴展性。** 盡管我們當前的實現能夠有效支持C#和Python存儲庫,但擴展到其他編程語言的存儲庫是一項簡單的工作。它主要涉及創(chuàng)建具有圖4中識別的關系的依賴圖,并將其整合到CodePlan框架中,從而使其能夠無縫適應各種編程語言。
**4 實驗設計**
在本節(jié)中,我們將解釋我們如何進行實驗來測試CodePlan。我們將首先討論我們使用的不同數據集。然后,我們將討論我們比較CodePlan的方法,這些方法類似于我們的參考點。最后,我們將解釋我們如何測量結果,以查看CodePlan與其他方法相比的性能如何。
**4.1 數據集**
在我們的實驗中,我們利用了多樣化的數據集,代表了各種復雜性和規(guī)模的代碼存儲庫。這些數據集使我們能夠在不同的現實世界情境中全面評估CodePlan的性能。
**內部存儲庫(Int-1和Int-2)。** 這些存儲庫是專有的,屬于一家大型產品公司。它們的特點是規(guī)模龐大、模式復雜,并且代表了生產級別的代碼庫。我們主要關注的任務是將這些存儲庫從傳統(tǒng)的日志框架遷移到現代的日志框架。這個遷移涉及到非平凡的更改,包括使用日志工廠創(chuàng)建特定于服務的日志記錄器、通過調用鏈傳遞日志記錄器、管理類層次結構、在不同范圍(類、方法等)存儲日志記錄器引用,以及在靜態(tài)和非靜態(tài)類/方法中處理日志記錄器。這兩個生產存儲庫Int-1和Int-2之所以被選擇,是因為它們具有不同的編碼風格和設計模式,為我們的評估提供了全面的內部數據集。
**外部存儲庫(公共GitHub)。** 我們還考慮了來自GitHub的外部存儲庫,以豐富我們的數據集。選擇這些存儲庫是為了代表兩種不同的編碼任務:遷移和時間編輯(后面會討論)。
**遷移任務。** 這個任務涉及遷移API或解決代碼庫中的不兼容性變化。示例包括更新依賴項、適應外部庫的更改或與新的編碼標準對齊代碼。它的復雜性在于通常需要在許多代碼文件和依賴項之間進行一致的更新。為了選擇這些存儲庫,我們搜索包含與各種遷移(API、框架等)相關的提交和拉取請求的存儲庫。我們篩選了至少包含50個文件的存儲庫。
**時間編輯任務。** 這個任務涉及在給定一些初始代碼更改的情況下編排一系列代碼更改。許多代碼更改都可以被描述為時間編輯,包括重構或添加/刪除功能。一個時間編輯任務由一組初始編輯(通常由用戶進行)以及由種子編輯引發(fā)的派生編輯組成。工具的任務是從初始編輯集中推斷出派生編輯。一個示例的時間編輯任務可以是,初始編輯是向方法添加一個參數,派生編輯是更改調用此方法的所有地方。我們從GitHub上的公共存儲庫中的提交中識別時間編輯任務。我們考慮具有寬松許可證的Python存儲庫,按星級排序,過濾掉與文檔/教程相關的存儲庫,并從候選存儲庫中選擇至少有10,000個星星的存儲庫,在2021年11月1日之后進行多個相關更改的提交。
**源/目標/預測存儲庫。** 為了收集遷移和時間編輯任務的代碼更改,我們獲取了GitHub上提交之前和之后的文件。我們將這些稱為源存儲庫(提交前)和目標存儲庫(提交后)。分析這些更改允許我們通過手動檢查識別種子更改。例如,在從NUnit遷移到XUnit時,種子編輯之一涉及將Console.WriteLine替換為寫入ITestOutputHelper對象。有了源存儲庫和種子更改說明,CodePlan的任務是對源存儲庫進行必要的更改,從而產生我們稱之為預測存儲庫的結果。如果CodePlan成功執(zhí)行了所有更改,那么預測存儲庫應該與目標存儲庫匹配,為我們對其在這些不同代碼庫中的性能與實際情況進行了充分的評估提供了堅實的基礎。
**預處理源和目標存儲庫。** 在處理大型存儲庫時,常常會有多個開發(fā)人員貢獻代碼,導致個人偏好驅動的各種編碼風格。例如,一個開發(fā)人員可能會始終使用this限定符來引用類成員,而另一個則可能不會。當CodePlan通過LLM提示執(zhí)行更改時,它往往會在整個代碼庫中建立統(tǒng)一的風格,這可能包括強制使用this限定符的一致性,等等。雖然這些更改確保了功能等效性,但在將預測存儲庫與目標存儲庫進行比較時,它們可能會影響評估指標。為了解決這些問題,我們在源和目標存儲庫上進行了手動預處理步驟。這個預處理旨在在存儲
表2?總結了遷移(C#)和時間編輯(Python)存儲庫的數據集統(tǒng)計信息,包括存儲庫的名稱(內部和外部),以及各種關鍵統(tǒng)計信息,包括它們的大小、代碼更改和其他相關指標:
- 文件數目:每個存儲庫中的文件總數。
- 代碼行數:所有文件中的代碼行數之和。
- 已更改文件數:在源存儲庫和目標存儲庫之間發(fā)生更改的文件數。
- 種子更改數:初始編輯的數量,通常被認為是代碼更改的起點。
- 派生更改數:跟隨初始種子更改的后續(xù)編輯的數量。
- 差異大小:源存儲庫和目標存儲庫之間不同的行數。
- 種子編輯的大小:當種子編輯直接在代碼上進行時,表示初始編輯的行數。當種子編輯通過LLM指令進行時,它表示指令文本的大小。
- 提示模板大小:這個數字表示CodePlan使用的LLM提示模板的大小。相同的模板適用于所有遷移存儲庫任務,另一個類似的模板用于所有時間編輯存儲庫任務。
這些指標不僅提供了數據集特征的全面概述,而且突出了使用CodePlan相對于手動過程的顯著優(yōu)勢,特別是對于大型存儲庫。在手動情景中,需要人工費力地識別依賴更改并實現每個修改。值得注意的是,“差異大小”和“種子編輯的大小”等指標提供了有關所需開發(fā)工作的見解。另外,值得注意的是,為CodePlan制定LLM指令所需的工作量明顯少于手動進行所有代碼更改所需的大量工作。這些指標共同展示了CodePlan在不同代碼庫上的高效性和有效性,強調了它在簡化開發(fā)工作流程并節(jié)省寶貴開發(fā)人員時間方面的潛力。
4.2 神諭和基線
神諭?;叵胍幌?#xff0c;我們對存儲庫級編碼任務的定義是圍繞滿足可以確定解決方案有效性的神諭。在我們的實驗中,我們考慮了兩個特定的神諭實例。對于C#遷移任務,我們將神諭定義為通過C#構建工具而沒有任何錯誤。對于時間編輯方案,我們使用Pyright [6],這是Python的靜態(tài)檢查器,作為神諭。
神諭引導修復。這兩個神諭都以代碼庫作為輸入,并可以輸出代碼庫中的錯誤列表。這自然地導致了對我們任務的基線方法的制定,我們將其稱為神諭引導修復。這些都是簡單的反應性方法,在每個步驟中,我們嘗試糾正神諭標記的錯誤。對于C#遷移場景,基線是構建修復,對于時間編輯,基線是Pyright修復,根據使用的神諭而定。
神諭引導修復包括以下步驟:
- 初始編輯:該過程從應用初始種子編輯到代碼庫開始。
- 構建和錯誤檢測:在種子編輯之后,我們調用神諭,以檢測由于種子編輯而在代碼庫中產生的錯誤。
- 錯誤消息分析:然后,解析神諭生成的錯誤消息,以精確定位錯誤在代碼中的位置。
- LLM修補:隨后,將錯誤消息以及標記位置的代碼片段傳遞給LLM。LLM利用其代碼生成能力為確定的錯誤生成修補程序或修復。為了與CodePlan進行公平比較,我們在神諭引導修復中使用了我們的實現,用于空間和時間上下文的提取。也就是說,CodePlan和神諭引導修復之間的主要區(qū)別在于,CodePlan使用自適應規(guī)劃,而神諭引導修復使用神諭生成的診斷信息。需要注意的是,神諭引導修復方法是一種反應性方法,缺乏全面的“更改可能影響分析”。這意味著它們可能不會徹底評估所提議的代碼更改對代碼庫的其他部分可能產生的影響。因此,在處理復雜的編碼任務時,此類方法生成的修復可能不完整或不正確。
替代編輯模型:Coeditor [76]。CodePlan默認利用LLM的文本和代碼處理能力,以在提供適當上下文的情況下對代碼片段進行本地編輯。然而,從理論上講,CodePlan的增量依賴分析、更
改可能影響分析和自適應規(guī)劃組件可以與任何能夠根據提供的意圖進行局部編輯的工具或模型結合使用。Coeditor [76]是一個經過微調的基于變壓器的模型,可以編輯代碼片段,同時考慮到在存儲庫中之前進行的編輯。這樣的模型非常適合時間編輯任務,其中我們需要從一組種子編輯中進行一系列編輯,其中每個編輯依賴于前面一組編輯的某個子集。實際上,Coeditor在[76]中對時間編輯任務進行了評估。為了展示我們的分析和規(guī)劃的通用性,我們評估了我們的方法在時間編輯情境中的性能,將gpt-4-32k替換為Coeditor作為編輯模型。4.3 評估指標
我們研究中采用的評估指標旨在評估CodePlan(或基線)如何有效地在整個代碼存儲庫中傳播更改以及每個更改的正確性。為了實現這一目標,我們依賴于兩個關鍵指標:塊指標和編輯指標。
塊指標。塊指標幫助我們了解CodePlan準確識別需要修改的代碼塊的能力。這些指標包括:
- 匹配塊:這些是存在于源存儲庫中、已在目標存儲庫中進行了編輯并且還在預測存儲庫中進行了編輯的代碼塊?;旧?#xff0c;這些是CodePlan成功識別需要更改的塊。?
- 未命中塊:未命中塊是指存在于源存儲庫中、已在目標存儲庫中進行了編輯但在預測存儲庫中未進行編輯的代碼塊。換句話說,這些是CodePlan在應該進行修改的情況下未能修改的塊。?
- 虛假塊:虛假塊是指在源存儲庫中找到的代碼塊,在目標存儲庫中未進行編輯,但在預測存儲庫中被CodePlan錯誤地進行了編輯。這代表了CodePlan不必要地進行的編輯。?
理想情況下,匹配塊數量高,未命中塊和虛假塊數量低。
編輯指標。雖然塊指標評估代碼塊的識別,但編輯指標深入探討了CodePlan所做修改的正確性。這些指標包括:
- Levenshtein距離:Levenshtein距離度量了預測存儲庫和目標存儲庫之間文件級別的編輯距離。它計算了將一個文件轉換為另一個文件所需的更改次數。較高的Levenshtein距離表示CodePlan未正確地對存儲庫進行了更改。?
- Diff BLEU:通常,我們使用BLEU [58],這是自然語言處理中常見的指標,來衡量文本相似性。然而,在應用于我們的任務時,BLEU可能會產生過高的相似性分數,因為特定任務的代碼編輯通常只涉及文件的一小部分。為解決這個問題,我們計算Diff BLEU:一種修改后的BLEU分數,表示為BLUE(DIFF(源存儲庫文件,目標存儲庫文件),DIFF(源存儲庫文件,預測存儲庫文件))。在這里,DIFF計算兩個文件之間的差異(diff hunks)。Diff BLEU的獨特之處在于它專注于比較預測和目標存儲庫之間代碼的修改部分,同時忽略常見的代碼。當預測和目標存儲庫中的修改精確匹配時,Diff BLEU得分為1.0,表示在處理代碼修改方面具有高度的正確性和一致性。?
總之,這些評估指標全面評估了CodePlan的性能,無論是在識別需要修改的代碼塊方面,還是在確保所做的修改是正確的方面。
5 結果與分析
在本節(jié)中,我們提供實證結果以回答以下研究問題:
RQ1:CodePlan能夠多么有效地定位并進行所需更改以自動化存儲庫級別的編碼任務?
RQ2:時間和空間上下文對于CodePlan的性能有多重要?
RQ3:使CodePlan在解決復雜的編碼任務中勝過基線的關鍵區(qū)別是什么?
5.1 RQ1:CodePlan能夠多么有效地定位并進行所需更改以自動化存儲庫級別的編碼任務?
動機。在現代軟件工程背景下,研究問題涉及到CodePlan框架在自動化存儲庫級別編碼任務中的有效性,這是至關重要的。有幾個關鍵動機驅使這一問題的重要性:
- 存儲庫級別任務的復雜性:軟件工程活動,如包遷移和時間代碼編輯,通常超越了局部代碼更改的范圍。存儲庫級別編碼任務涉及對整個代碼庫的廣泛修改。這種復雜性需要新的方法來確保效率和正確性。
- 現實世界的相關性:在實踐中,軟件存儲庫經常需要進行大規(guī)模的更改。例如,包遷移涉及跨多個文件和依賴項更新依賴項,而時間代碼編輯需要跟蹤和管理不斷演化的代碼庫。這些任務不僅耗時,而且在手動操作時容易出錯。
- 與基線的比較:評估CodePlan與基線方法的比較至關重要。基線方法,如“Oracle-Guided Repair”,在軟件開發(fā)中很常見,但在處理存儲庫級別任務時可能缺乏效率。將CodePlan與基線進行評估提供了衡量其有效性的基準,并突出了其表現出色的領域。我們還研究了在執(zhí)行不同編輯模型時我們的系統(tǒng)的行為,通過在時間編輯任務上評估Coeditor和CodePlan的組合。
- 大規(guī)模存儲庫:研究不僅涉及孤立的編碼問題,還評估了CodePlan在大型內部和外部存儲庫上的性能。這個廣泛的范圍確保了框架在各種復雜的實際場景下的有效性得到了測試。
實驗設置。
為了研究CodePlan能夠多么有效地定位并進行所需更改以自動化存儲庫級別任務,我們在4.1中描述的任務的上下文中對其進行評估。對于C#遷移和時間編輯任務,我們從沒有進行任何編輯的源狀態(tài)的存儲庫開始。我們在源狀態(tài)的基礎上應用種子編輯,此時CodePlan(或正在評估的基線)接管整個存儲庫的編輯。在C#遷移的情況下,使用合適的提示自行執(zhí)行種子編輯,而對于時間編輯,我們?yōu)槊總€存儲庫任務存儲種子編輯并將其應用為補丁。CodePlan在種子編輯后對存儲庫進行增量依賴分析,識別可能受其影響的代碼,并計劃下一步要進行的編輯,使用合適的提示查詢LLM并將結果合并到存儲庫中。CodePlan會不斷進行迭代,直到發(fā)現沒有更多的站點需要進行編輯。
有時,由于大型語言模型(LLM)響應的固有變異性,可能需要多次迭代。我們啟動第一次迭代,稱為“Iter 1”,以使用LLM啟動代碼編輯過程。然而,LLM響應的偶爾不準確可能會引入錯誤的代碼更改和后
續(xù)的構建錯誤。為了解決這些挑戰(zhàn),第二次迭代,稱為“Iter 2”,變得重要。在此階段,CodePlan積極識別并確認前一次迭代中的任何構建錯誤,并重新與LLM交互,以獲取更精確的響應以糾正初始錯誤。
與CodePlan一起,我們還在相同的存儲庫上評估了一系列基線。對于C#遷移,我們評估了Build-Repair,對于時間編輯,我們評估了Pyright-Repair,其設置如4.2中所述。Pyright-Strict-Repair是一個變種,其中我們使用啟用了嚴格模式的Pyright工具。在所有修復基線中,我們提供與CodePlan中相同的上下文(時間和空間),唯一的區(qū)別是本地化的編輯位置是使用oracle進行的。我們還評估了在時間編輯任務中使用Coeditor而不是gpt-4-32k作為編輯模型的情況,如4.2中所述。在所有Coeditor基線中,上下文化是根據[76]中的進行的,下一個編輯站點的本地化是通過CodePlan或使用oracle進行的。
我們評估所有這些方法在多大程度上能夠通過匹配、未命中和虛假塊指標定位要編輯的站點以及整體修改的正確性,如4.3中所述。我們還確定了方法執(zhí)行結束后存儲庫的狀態(tài)是否通過了有效性檢查,即是否滿足了oracle并根據基本事實進行了正確的編輯。
結果討論。
表3中的實驗結果展示了CodePlan框架在自動化存儲庫級別編碼任務方面的有效性。
在內部(專有)存儲庫的C#遷移任務的背景下,結果表提供了兩種方法的性能全面視圖:CodePlan和Build-Repair。值得注意的是,CodePlan在幾個關鍵方面表現出色。它在“匹配塊”方面表現出色,為“Int-1(日志)”和“Int-2(日志)”數據集均實現了151個匹配塊和438個匹配塊的完美結果。這表明CodePlan在準確識別和處理預期代碼更改方面具有卓越的精度。此外,CodePlan令人印象深刻地展現了零“未命中塊”,確保沒有忽略任何關鍵代碼修改,從而最小化了功能問題的風險。同樣值得注意的是“虛假塊”的缺失。
**CodePlan vs. Build-Repair**
比較分析顯示了為什么Build-Repair落后于CodePlan。導致其性能差距的一個關鍵因素是它依賴于“構建錯誤位置”作為代碼更正的指示器。構建錯誤通常會標出錯誤檢測到的位置,但它們不一定與實際所需的修復位置相符。例如,錯誤可能表現為派生類的重寫函數簽名不匹配,但修復是在基類的虛函數簽名中需要的,這會導致Build-Repair錯誤地解釋為校正位置。此外,在編譯器優(yōu)化的上下文中,構建過程可能會遮蓋后續(xù)的錯誤,僅在特定時間顯示選定的錯誤。這可能導致不正確的校正位置的識別,阻礙正確更改的傳播,進一步加劇了CodePlan和Build-Repair之間性能差距。這些限制突顯了Build-Repair在準確定位和處理復雜遷移任務中的代碼修改時所面臨的挑戰(zhàn)。
**多次迭代**
正如在CodePlan的“Iter 1”后的“Int-1”數據集中所示,處理大型語言模型(LLM)響應固有變異性的需求,從而需要多次迭代來處理。為了糾正這一點,“Iter 2”發(fā)揮著重要作用。在此階段,CodePlan識別并承認了前一次迭代中的構建錯誤,并重新與LLM合作,以獲取更準確的響應以糾正初始錯誤。值得注意的是,在兩次迭代之間的塊度量沒有發(fā)生變化,因為CodePlan正確地識別了需要糾正的塊并與LLM進行了修改。然而,“Iter 1”中的LLM校正是錯誤的,導致Levenshtein距離度量較低。在代碼編輯階段,這種迭代的改進過程顯著有助于減輕LLM輸出偶爾不準確的影響。
**在Ext-1上的性能**
在“Ext-1”數據集上,CodePlan與基線方法Build-Repair的比較中,我們觀察到它們性能上的顯著差異。CodePlan成功地識別并更新了58個代碼塊,實現了1.00的完美DiffBLEU分數,表明它進行了與目標存儲庫相同的更改。相比之下,基線方法Build-Repair則未能識別六個塊并生成了八個構建錯誤。這種差異突顯了Build-Repair的一個關鍵局限性——缺乏全面的“更改可能影響分析”功能。具體來說,Build-Repair未能更新用于初始化新添加的ITestOutputHelper _output類成員的構造函數塊。這個遺漏沒有被注意到,因為缺少初始化沒有觸發(fā)構建錯誤,從而對調用者產生了級聯(lián)影響。相比之下,CodePlan成功處理了新添加的ITestOutputHelper _output類成員的初始化。這一成就要歸功于其強大的更改可能影響分析,它準確識別了在添加新字段時對構造函數塊的必要修改。因此,CodePlan無縫地更新了構造函數和所有其調用者,避免了任何遺漏的塊或構建錯誤。這一發(fā)現突顯了CodePlan的高級規(guī)劃能力的重要性,這確保了對存儲庫級別的代碼編輯采用更全面和準確的方法。
**CodePlan vs Pyright-Repair**
在時間編輯任務的背景下,我們可以看到CodePlan能夠成功地識別所有的派生編輯位置,其中兩個存儲庫(T-2,T-3)中的效果幾乎完美,第三個存儲庫(T-1)中也幾乎成功。與此形成對比的是基線Pyright-Repair方法,它未能識別兩個存儲庫(T-2,T-3)中的任何派生編輯。我們發(fā)現,在嚴格模式下使用Pyright可以提供更好的結果,但僅在一個存儲庫(T-1)中表現得與CodePlan一樣好。總體而言,我們觀察到Pyright-Repair基線在識別編輯位置方面不足。這也反映在DiffBLEU得分一直較高和Levenshtein距離(L.D.)一直較低的情況下。請注意,由于LLM不一定執(zhí)行與基本事實相同的確切編輯,因此DiffBLEU和L.D.可能不會具有完美的1.0和0值。
對于T-2和T-3,我們看到Pyright-Repair基線無法識別任何派生編輯。在這兩個存儲庫中,一旦進行了種子編輯,Pyright就不會標記代碼庫中的任何錯誤。在T-2的情況下,種子編輯涉及向方法添加參數,如圖6所示。然而,此新參數還被分配了默認值。由于此參數存在默認值,Pyright不會標記此方法的所有調用站點為錯誤。但是在基本事實中,開發(fā)人員會跟進并編輯調用站點。CodePlan的更改可能影響分析將這些調用站點標識為“可能需要”編輯,因此我們將它們傳遞給LLM以進行編輯。
在T-3的情況下,種子編輯涉及修改函數的主體,如圖6所示。在種子編輯之后,該方法現在期望傳遞給它
的字典包含一個新的鍵“api_endpoint”。Pyright不會在此階段標記代碼庫中的任何錯誤,但很明顯,這可能需要對send_request的調用者進行修改。CodePlan確實檢測到了這一點,并能夠在基本事實中的10個派生站點中識別和進行編輯。
在這兩種情況下,我們事先不知道是否需要編輯調用站點。因此,CodePlan將此決策留給LLM根據上下文來決定。相比之下,Oracle-Guided基線甚至沒有檢測到可能需要更改。這可以歸因于像Pyright或Build之類的Oracle的事實,它們旨在檢測錯誤,因此只會標記違反某些規(guī)則的代碼。這與在整個存儲庫中傳播更改的任務不一致,因為在許多情況下,可能需要傳播更改,但具有更改的存儲庫不會違反Oracle的任何規(guī)則。
**Coeditor評估**
在比較CodePlan和Coeditor-CodePlan時,我們可以看到它在T-1上表現得相當好,但在T-2和T-3上略遜一籌,每個存儲庫中都錯過了一個編輯站點。盡管這兩種方法都使用相同的分析和規(guī)劃,但CodePlan中的本地編輯與基本事實更一致,與Coeditor-CodePlan在T-2和T-3中展示的更低的DiffBLEU分數和更高的L.D.反映出這一點。這可能是由于gpt-4-32k和Coeditor之間的上下文理解能力差異所致。例如,選擇將方法的參數實例化而不是向調用者添加參數可能意味著錯過了由于調用者簽名更改而產生的編輯。作為一個更強大的模型,gpt-4-32k更擅長理解時間編輯的上下文,因此它所做的編輯與基本事實更加一致,與Coeditor相比。這也在T-2上比較Pyright-Strict-Repair和Coeditor-Pyright-Strict-Repair時觀察到,Coeditor的錯誤本地編輯導致了編輯站點的丟失以及更差的DiffBLEU和L.D。
總之,實驗結果證明了CodePlan的基于規(guī)劃的方法在自動化存儲庫級別編碼任務方面非常有效,相比傳統(tǒng)的基線方法,它具有更好的匹配度、完整性和精度。其處理大規(guī)模存儲庫內復雜編碼任務的能力,標志著自動化軟件工程活動的重大進展。
**RQ2: 時態(tài)和空間背景對于CodePlan的性能有多重要?**
**動機:** RQ2的動機在于認識到大型語言模型(LLMs)需要時態(tài)和空間背景來提供準確和與上下文相關的代碼更新建議。時態(tài)上下文對于理解代碼更改的順序和時機至關重要,使LLMs能夠提出與代碼的演變相一致并保持一致性的建議。沒有這種時態(tài)意識,LLMs可能會提供與早期或后續(xù)修改相沖突的解決方案,導致代碼錯誤。另一方面,空間上下文使LLMs能夠理解代碼庫不同部分之間的復雜關系和依賴關系。這種理解對于確定需要更新的位置以及確保以保持代碼功能的方式應用它們至關重要。因此,調查這些上下文在CodePlan的規(guī)劃中的重要性對于有效地利用LLMs來自動執(zhí)行存儲庫級別的編碼任務至關重要。
**實驗設置:** 為了評估CodePlan的規(guī)劃中時態(tài)和空間上下文的重要性(RQ2),采用了特定的實驗設置。回顧一下,CodePlan通過維護與先前相關的更改列表來跟蹤時態(tài)上下文,并將這些上下文隨后納入到大型語言模型(LLM)提示中。
為了衡量這些上下文的重要性,進行了一項受控實驗。在這個實驗中,故意停止跟蹤時態(tài)變化,不提供時態(tài)或空間上下文給LLM在代碼修改任務期間。這使得可以詳細研究當這些基本上下文被省略時,LLM響應受到了怎樣的影響。該實驗還包括了對各種指標的全面評估,包括代碼一致性(塊度量)和代碼正確性(Diff BLEU和Levenshtein距離)。通過比較帶有時態(tài)和空間上下文的CodePlan設置與沒有這些上下文的實驗設置之間的結果,研究旨在準確量化這些上下文在CodePlan的規(guī)劃過程中的重要性以及它們對自動化代碼修改質量的影響。
**結果討論:** 關于時態(tài)和空間上下文對于CodePlan的規(guī)劃的重要性的結果(RQ2)揭示了關鍵的見解。如表2所示,當不考慮時態(tài)上下文時,在代碼修改過程中錯過的塊明顯增加。這增加歸因于大型語言模型(LLM)由于缺乏時態(tài)上下文而未對某些代碼塊進行必要的更改,因為它無法理解在沒有時態(tài)上下文的情況下需要這些修改。
圖8中的一個示例說明了這個問題。在這種情況下,根據派生類的方法的簽名更改,需要在基類的虛方法中進行校正。然而,由于LLM缺乏時態(tài)上下文,它沒有關于派生類的方法的信息,導致它認為不需要對基類方法進行任何更改。這突顯了時態(tài)上下文在理解代碼依賴關系和確保準確更新方面的關鍵作用。
此外,圖9提供了另一個示例,說明了缺乏時態(tài)上下文如何影響代碼修改過程。在這種情況下,需要在“Startup()”方法內的“Create-Service()”調用中添加“Context”參數。然而,由于LLM缺乏時態(tài)上下文,它不知道“CreateService()”的簽名更改,因此無法識別需要更新所有調用者的需求。這個遺漏導致了整個代碼庫中的許多未能更新的內容。
**關于時態(tài)和空間上下文的重要性的觀察:** 還需要強調另一個重要的觀察結果,即在缺乏足夠的空間上下文時,虛假塊的數量增加。這種現象發(fā)生是因為在缺乏足夠的空間上下文的情況下,大型語言模型(LLM)可能會錯誤地感知缺失的代碼元素并嘗試創(chuàng)建它們,導致虛假代碼塊的生成。
圖10中的一個示例說明了這個問題。在這種情況下,任務是通過將日志調用從舊的日志框架遷移到新的框架來修改“AuthorizeUser()”方法。然而,由于缺乏空間上下文,無法指定“GetUserSubscription()”方法和“CurrentUser”屬性的存在,LLM試圖創(chuàng)建這些元素。因此,不僅解決了日志遷移問題,LLM還引入了不必要的代碼塊,比如創(chuàng)建“GetUserSubscription()”方法和將“CurrentUser”添加為類級對象。
這一觀察結果強調了空間上下文在引導LLM理解代碼結構和關系方面的關鍵作用。提供全面的空間上下文有助于防止生成多余的代碼塊,并確保代碼修改精確且與預期的更改一致。
總之,實驗結果強調了時態(tài)和空間上下文在CodePlan的規(guī)劃中的基本性質。由于缺乏時態(tài)和空間上下文而導致的錯過和虛假更新的增加強調了通過這些上下文向LLM提供對代碼演化和依賴關系的全面理解對于確保準確和有效的代碼修改的重要性。
**5.3 RQ3:CodePlan在解決復雜編碼任務方面的關鍵差異因素是什么?**
**動機:** RQ3旨在揭示CodePlan相對于基線方法在處理復雜編碼任務方面表現出色的關鍵不同之處。這個研究問題的動機在于需要識別和理解有助于CodePlan表現出眾的具體因素??紤]到編碼任務日益復雜,尤其是存儲庫級別的任務,因此有必要明確指出將CodePlan與傳統(tǒng)方法區(qū)分開的具體方面。通過識別這些不同之處,研究旨在揭示CodePlan的優(yōu)勢和優(yōu)點,為解決復雜編碼任務所帶來的挑戰(zhàn)提供有價值的見解。
**實驗設置:** 這里的主要關注點是定性分析。在使用CodePlan和基線方法進行代碼修改后,通過手工檢查結果進行仔細分析。這包括詳細檢查每種方法所做的更改,目的是深入了解決策過程和代碼修改的微妙差異。通過手動檢查和比較這些更改,研究旨在發(fā)現微妙但關鍵的差異,以闡明CodePlan在處理復雜編碼任務時的優(yōu)勢和基礎機制。這種定性方法提供了全面了解CodePlan在這一背景下表現出色以及它與傳統(tǒng)方法的不同之處的理解。
**結果討論:**
**CodePlan的戰(zhàn)略規(guī)劃和上下文感知:**
CodePlan在處理復雜編碼任務方面的卓越表現可以歸因于其強大的功能,尤其是其增量分析和更改可能影響分析。這些功能使其與像Build-Repair這樣的基線方法區(qū)分開來,后者主要側重于保持語法正確性,而忽略了關鍵的上下文細節(jié)。為了說明這一點,讓我們深入研究圖11所示的存儲庫Ext-1中的一個示例,在這個示例中,CodePlan的任務是將Console.WriteLine方法遷移到ITestOutputHelper.WriteLine。這個遷移涉及到一系列的更改1到4,如圖11所描述。這些級聯(lián)更改始于引入ITestOutputHelper _output作為類級成員,這是通過LLM更新完成的。
在這種情況下,CodePlan的更改可能影響分析在發(fā)揮作用方面無價。它認識到添加新字段需要修改構造函數以確保正確初始化。因此,CodePlan安排了必要的構造函數修改。因此,構造函數Subscriber(...)被正確更新為接受ITestOutputHelper作為參數并初始化類成員_output。這反過來導致了存儲庫中的一系列更改,如圖11中的步驟1到4所解釋的那樣。
這個例子說明了CodePlan如何根據上下文和上下文感知的能力,有序地對存儲庫進行更改,得益于它的更改影響分析和引入時態(tài)上下文的能力。相比之下,僅依賴于語法正確性的Build-Repair無法檢測到Subscriber構造函數中的修改需要修改。由于遵守了所有語法規(guī)則,它不會觸發(fā)構建錯誤,因此無法在圖4中所示的步驟2到4中執(zhí)行更改,而只會執(zhí)行步驟1中概述的修改,導致代碼更新不完整。
CodePlan的獨特優(yōu)勢在于它對代碼關系的全面理解和精心規(guī)劃,確保了代碼庫的完整性和功能在復雜編碼任務中得以保持。這種定性分析突顯了CodePlan在處理復雜編碼任務時的微妙方法如何勝過基線方法。
**增量分析:與依賴圖的關系維護:**
CodePlan在應對復雜編碼任務時的卓越表現歸功于其增量分析,該分析有效地將更改與底層的依賴圖關聯(lián)起來。與靜態(tài)代碼快照不同,靜態(tài)代碼快照可能導致依賴關系的不完整表示,我們的增量分析方法確保了依賴圖內部的關系保持到受影響的代碼塊被修改之前。
考慮一個調用者函數進行重命名的情況。傳統(tǒng)的靜態(tài)快照可能難以保持調用者-被調用者關系,因為在它們的視圖中,調用者已經被重命名。然而,CodePlan的增量分析介入,保持調用者
**7 相關工作**
**用于編碼任務的大型語言模型 (LLMs):** 已經訓練了大量的LLMs [10, 19, 21, 24, 28, 30, 35, 57, 73–75, 80],這些模型是基于大規(guī)模的源代碼和自然語言文本語料庫的。它們已被用于完成各種編碼任務。一些示例包括程序合成 [50, 56]、程序修復 [11, 43, 79]、漏洞修補 [60]、推斷程序不變式 [62]、測試生成 [69] 和多任務評估 [72]。然而,這些研究是在從其存儲庫中提取的策劃示例上進行的,并且旨在通過獨立調用LLM來完成。我們考慮的是一類不同的任務,這些任務是在代碼存儲庫的規(guī)模上提出的,LLM在不同的示例上被多次調用,這些示例是相互依賴的。我們在存儲庫范圍內監(jiān)視每個LLM調用的結果,以確定未來的代碼更改義務,以使存儲庫達到一致狀態(tài),例如,存儲庫不包含構建或運行時錯誤。
**自動化規(guī)劃:** 自動化規(guī)劃 [37, 67] 是人工智能領域中一個廣泛研究的主題。在線規(guī)劃 [67] 用于在不知道動作效果并且無法預先列舉狀態(tài)空間的情況下。它需要監(jiān)視動作和計劃擴展。在我們的情況下,編輯動作由LLM執(zhí)行,其結果不能在預先知道之前預測,并且狀態(tài)空間是無界的。因此,我們的自適應規(guī)劃是一種在線算法,我們通過靜態(tài)分析監(jiān)視動作并擴展計劃。在另一方面,[42] 使用LLM從自然語言意圖中推導出一個計劃,然后生成代碼以解決復雜的編碼問題,而[86]執(zhí)行前瞻規(guī)劃(樹搜索)來引導代碼LLM的標記級解碼。我們的工作中的規(guī)劃是基于分析依賴關系和LLM對代碼存儲庫進行更改的影響而進行的。
**代碼更改分析:** 靜態(tài)分析用于確保軟件質量。每當代碼發(fā)生更改時,重新計算分析結果都是昂貴的。增量程序分析領域提供了一些技術,可以僅重新計算受更改影響的分析結果。針對數據流分析 [18, 68]、指針分析 [84]、符號執(zhí)行 [63]、錯誤檢測 [52] 和類型分析 [27] 已經開發(fā)了專門的算法。程序差異分析 [16, 46, 48] 和更改影響分析 [17, 41] 確定了兩個程序版本之間的差異以及更改對程序的其余部分的影響。已經研究了更改對回歸測試 [65]、分析重構 [33] 和協(xié)助代碼審查 [13, 36] 的影響。我們分析由LLM生成的代碼,逐步更新了語法(例如,父子關系)和依賴關系(例如,調用者-被調用者關系)。我們進一步分析了這些更改對相關代碼塊的可能影響,并創(chuàng)建了由LLM執(zhí)行的更改義務。
**空間和時間上下文化:** 如介紹中所討論的,LLMs受益于從存儲庫中的其他文件和過去的編輯中提取的相關上下文。我們通過跟蹤代碼更改和依賴關系提供了這兩種信息給LLM。
**學習編輯模式:** 已經開發(fā)了許多方法,用于從過去的編輯或提交中學習編輯模式,包括重寫規(guī)則 [31]、錯誤修復 [15, 20]、類型更改 [45]、API遷移 [49, 82] 和編輯的神經表示 [83]。諸如 [53] 和 [54] 的方法從用戶提供的示例中合成上下文感知的編輯腳本,并將它們應用于新的上下文中。其他方法觀察IDE中的用戶操作,以自動化重復的編輯 [55] 和與時間相關的編輯序列 [87]。我們不打算學習編輯模式,也不假設編輯之間存在相似性。我們的重點是識別LLM進行的代碼更改的影響,并引導LLM進行必要的其他更改。
**8 結論和未來工作**
在本文中,我們介紹了CodePlan,這是一個新穎的框架,旨在解決存儲庫級別編碼任務的挑戰(zhàn),這些任務涉及到跨大規(guī)模和相互依賴的代碼庫的廣泛更改。CodePlan利用增量依賴分析、更改可能影響分析和自適應規(guī)劃,通過Large Language Models引導多步編輯。我們在不同復雜性和大小的各種代碼存儲庫上評估了CodePlan,包括內部專有存儲庫和C#和Python的公共GitHub存儲庫,用于遷移和時間編輯任務。我們的結果表明,CodePlan優(yōu)于基線方法,與實際情況更加一致。總之,CodePlan提供了一種有望自動化復雜的存儲庫級別編碼任務的方法,既提高了生產率又提高了準確性。它在解決這些挑戰(zhàn)方面的成功為高效和可靠的軟件工程實踐開辟了新的可能性。
雖然CodePlan表現出了巨大的潛力,但還有許多未來研究和改進的方向。首先,我們的目標是將其適用于更廣泛的編程語言和代碼工件范圍,包括配置文件、元數據和外部依賴項,以提供更全面的存儲庫級別編輯解決方案。此外,我們計劃進一步定制CodePlan的更改可能影響分析。這可以通過通過基于規(guī)則的方法或更高級的機器學習技術將任務特定的影響分析規(guī)則納入其中,以微調其特定的編碼任務的編輯決策。此外,我們將解決處理動態(tài)依賴關系的挑戰(zhàn),例如數據流依賴關系、復雜的動態(tài)調度(通過虛擬函數和動態(tài)轉換)、算法依賴關系(例如,輸入列表預計要排序)和各種執(zhí)行依賴關系(例如,多線程和分布式處理),以使CodePlan在更廣泛的軟件工程任務中更加靈活。