探索生成式 AI

生成式 AI,尤其是 LLM(大型語言模型),已在公眾意識中爆炸開來。與許多軟體開發人員一樣,我對可能性感到好奇,但對於從長遠來看它對我們的職業意味著什麼並不確定。我現在已在 Thoughtworks 擔任一個角色,協調我們的工作,了解這項技術將如何影響軟體交付實務。我將在此處發布各種備忘錄,描述我和我的同事正在學習和思考的內容。

較早的備忘錄

工具鏈(2023 年 7 月 26 日) 

工具鏈

讓我們從工具鏈開始。每當有一個仍在演進中的模式和技術的新領域時,我都會嘗試建立一個心智模型,說明事物如何組合在一起。這有助於應對迎面而來的資訊浪潮。在這個領域中解決了哪些類型的問題?解決這些問題需要哪些常見的拼圖類型?事物如何組合在一起?

如何分類工具

以下是我目前使用 LLM(大型語言模型)支援編碼工具的心智模型的維度。

輔助任務

  • 更快地在上下文中找到資訊
  • 產生程式碼
  • 「推論」程式碼(解釋程式碼或程式碼中的問題)
  • 將程式碼轉換成其他東西(例如文件文字或圖表)

這些是我在編碼協助方面最常看到的任務類型,儘管如果我將範圍擴展到軟體交付生命週期中的其他任務,還有很多其他任務。

互動模式

我見過三種主要的互動模式

  • 聊天介面
  • 內嵌協助,即在程式碼編輯器中輸入
  • CLI

提示組成

提示的品質顯然會對工具的實用性產生重大影響,並結合後端中使用的 LLM 的適用性。提示工程不必完全留給使用者,許多工具會在後端為您套用提示技巧。

  • 使用者從頭開始建立提示
  • 工具從使用者輸入和額外內容(例如開啟檔案、一組可重複使用的內容片段或對使用者的額外問題)組成提示

模型的屬性

  • 模型訓練的內容
    • 它是否經過特定代碼和編碼任務的訓練?哪些語言?
    • 它是在什麼時候接受訓練的,也就是說資訊有多新
  • 模型的大小(儘管對於這一點的重要性以及對於特定任務(例如編碼)來說什麼是「好」的大小,仍有很大的爭議)
  • 模型支援的內容視窗大小,基本上就是它可以作為提示接受的代碼數
  • 已新增到模型或其託管後端的過濾器

來源和託管

  • 商業產品,其 LLM API 由產品公司託管
  • 開放原始碼工具,連接到 LLM API 服務
  • 自建工具,連接到 LLM API 服務
  • 自建工具連接到微調、自託管的 LLM API

範例

以下是這個領域中一些常見的工具範例,以及它們如何符合這個模型。(此清單並非對這些工具的認可或對其他工具的否定,它只是為了幫助說明這些面向。)

工具 任務 互動 提示組成 模型 來源/託管
GitHub Copilot 產生代碼 內聯協助 由 IDE 擴充功能組成 使用代碼、漏洞過濾器進行訓練 商業
GitHub Copilot Chat 所有 聊天 由使用者聊天 + 開啟檔案組成 使用代碼進行訓練 商業
ChatGPT 所有 聊天 全部由使用者完成 使用代碼進行訓練 商業
GPT Engineer 產生代碼 CLI 提示根據使用者輸入組成 選擇 OpenAI 模型 開放原始碼,連接到 OpenAI API
「團隊 AI」 所有 網頁 UI 提示根據使用者輸入和使用案例組成 最常使用 OpenAI 的 GPT 模型 由團隊維護,以供其使用案例使用,連接到 OpenAI API
Meta 的 CodeCompose 產生代碼 內聯協助 由編輯器擴充功能組成 模型針對內部使用案例和程式碼庫進行微調 自託管

人們現在使用什麼,接下來會是什麼?

現在,人們最常使用直接聊天互動(例如透過 ChatGPT 或 Copilot Chat)與程式碼編輯器中的編碼協助(例如透過 GitHub Copilot 或 Tabnine)的組合。與其他方法相比,編輯器環境中的內聯協助可能是目前使用 LLM 進行編碼協助最成熟且有效的方式。它以小步驟支援開發人員的自然工作流程。較小的步驟可以更容易地追蹤和更仔細地檢閱品質,而且在無法運作的情況下,可以很容易地繼續進行。

開放原始碼世界中正在進行許多實驗,這些工具提供提示組成以產生較大的程式碼片段(例如 GPT Engineer、Aider)。我看到團隊針對其特定使用案例調整類似的微小提示組成應用程式的使用,例如透過將可重複使用的架構和技術堆疊定義與使用者故事結合,來產生任務計畫或測試程式碼,類似於我的同事徐浩在 這裡 的描述。像這樣的提示組成應用程式現在最常與 OpenAI 的模型一起使用,因為它們最容易取得且相對強大。然而,隨著人們尋求對其資料有更多控制權,實驗正越來越朝向開放原始碼模型和大型超大規模託管模型邁進。

作為進一步的步驟,除了進階提示組成之外,人們將許多對未來改進的希望寄託於模型組成部分。較大的模型或較小但經過更具體訓練的模型是否更適合於編碼協助?具有較大內容視窗的模型是否能讓我們提供更多程式碼,以推論我們程式碼庫中較大部份的品質和架構?在什麼規模下微調模型以符合您組織的程式碼是有回報的?開放原始碼模型的空間中會發生什麼事?未來備忘錄的問題。

感謝 Kiran Prakash 的意見回饋


中位數 - 三個函式的寓言 (2023 年 7 月 27 日) 

中位數 - 三個函式的寓言

這是產生中位數函式的一個小故事,以及它如何說明 LLM 輔助編碼的有用性和限制。

我需要計算 Typescript 程式碼庫中數字清單的中位數。我沒有做我通常會做的事,也就是開始在網路上搜尋「中位數函式 javascript」,而是嘗試讓 GitHub Copilot 協助我。

// calculate the median of a list of numbers

Copilot 首先為我產生一個適當的方法簽章

public static median(values:number[]): number {

然後建議三個不同的函式實作。

建議 1

public static median(values:number[]): number {
    if(values.length === 0) return 0;

    values.sort((a,b) => a-b);

    var half = Math.floor(values.length / 2);

    if (values.length % 2)
    return values[half];

    return (values[half - 1] + values[half]) / 2.0;
}

此實作運作良好。然而,輸入參數 values 會隨著 sort 函式而改變。當向一些同事展示此範例時,我得到不同的回應 - 一位說「這很糟糕,這是我們通常會忘記撰寫測試的部分,然後它可能會變成一個很難除錯的錯誤」。另一位說「這不一定是壞事,這取決於情況」。我在想這是否會是 IDE 中的靜態程式碼分析工具會標記為問題的部分,但 ESLint 和 Sonar 都沒有標記它。

建議 2

function median2(values: number[]) {
    const sorted = values.slice().sort((a, b) => a - b);
    const middle = Math.floor(sorted.length / 2);
    if (sorted.length % 2 === 0) {
        return (sorted[middle - 1] + sorted[middle]) / 2;
    } else {
        return sorted[middle];
    }
}

這個運作良好,而且沒有改變輸入參數的問題,因為 slice() 會傳回輸入陣列的副本。

建議 3

function median3(values: number[]) {
    const sorted = values.slice().sort((a, b) => a - b);
    const middle = Math.floor(sorted.length / 2);
    return sorted[middle];
}

乍看之下,這個看起來比其他兩個好 - 它較短,而且循環複雜度為零,我們傾向於認為那是「更優雅」。我為它撰寫的測試也運作良好,太棒了!

但後來我意識到,使用偶數和奇數值進行測試非常重要 - 我編寫的測試使用了奇數長度的輸入,但此實作在偶數長度的情況下會失敗。這個範例很好地說明了,了解我們在做什麼仍然很重要,才能為所產生的程式碼撰寫合理的測試。我們需要知道中位數的運作方式,才能撰寫涵蓋完整功能的測試。

這不就等同於從網路上複製嗎?

像這樣的使用案例,正是人們在這種「類固醇自動完成」中看到的好處之一的完美範例:您不必離開您的流程和工具鏈,就能獲得此類問題的解答。否則,我們無論如何都會從某個地方複製並貼上它,然後必須徹底檢閱該程式碼,並撰寫測試。所以風險是一樣的 - 對吧?

唯一的差別在於,使用 Copilot,我們不知道程式碼的來源。例如在 StackOverflow 的情況中,我們有關於片段品質的額外資料點:按讚數。

順帶一提,「建議 1」幾乎就是由 StackOverflow 上關於此主題的問題 中,按讚數最高的回應所建議的程式碼,儘管有小瑕疵。不過,一位使用者在留言中指出了輸入參數的變異。

產生測試,還是程式碼?或者兩者?

那麼反過來呢,如果我要求 Copilot 先為我產生測試會怎樣?我使用 Copilot Chat 嘗試了這個方法,它給了我一套非常好的測試,包括一個針對「建議 3」偶數長度失敗的測試。

it("should return the median of an array of odd length", () => { ... }

it("should return the median of an array of even length", () => { ... }

it("should return the median of an array with negative numbers", () => { ... }

it("should return the median of an array with duplicate values", () => { ... }

在中位數這種非常常見且小的函式的特殊情況中,我甚至會考慮同時為測試和函式使用產生的程式碼。這些測試相當易讀,我很容易就能推論出它們的涵蓋範圍,而且它們會幫助我記住,我需要同時查看輸入的偶數和奇數長度。但是,對於其他具有更多自訂程式碼的更複雜函式,我會考慮自己撰寫測試,作為品質控制的手段。特別是對於較大的函式,我想要從頭開始以結構化的方式思考我的測試案例,而不是從工具中取得部分場景,然後再填寫遺失的部分。

這個工具本身可以幫助我修正產生的程式碼中的瑕疵嗎?

我要求 Copilot Chat 以不變更輸入參數的方式重構「建議 1」,它給了我一個合理的修正。不過,這個問題暗示我已經知道我想改善程式碼中的哪些部分。

我也更廣泛地詢問了 ChatGPT,「建議 3」有什麼問題或可以改善的地方。它確實告訴我,它不適用於偶數長度的輸入。

結論

  • 您必須知道自己在做什麼,才能判斷產生的建議。在這種情況下,我需要了解中位數計算運作的方式,才能為產生的程式碼撰寫合理的測試。
  • 工具本身可能知道產生的程式碼有什麼問題或可以改善的地方 - 這是未來讓它變得更好的途徑,還是我們註定要與我們的 AI 工具進行循環對話?
  • 由於品質控管的原因,我對產生測試和實作一直持懷疑態度。但是,產生測試可以給我一些我錯過的測試情境的想法,即使我之後捨棄了程式碼。而且,根據函式的複雜性,如果可以輕鬆地推論情境,我可能會考慮使用產生的測試。

感謝 Aleksei Bekh-Ivanov 和 Erik Doernenburg 的見解


內嵌協助 - 何時更有用?(2023 年 8 月 1 日) 

內嵌協助 - 何時更有用?

Thoughtworks 目前使用最廣泛的編碼協助形式是 IDE 中的內嵌程式碼產生,其中 IDE 擴充功能會在開發人員在 IDE 中輸入時產生建議。

對於「這是否有用?」這個問題的簡短回答是:「有時有用,有時沒有用。」 ¯_(ツ)_/¯ 您會在網路上找到各種開發人員的意見,從「這讓我快很多」到「我把它關掉了,它沒用」。這是因為這些工具的用處取決於情況。而有用的判斷取決於您的期望有多高。

我所謂的「有用」是什麼意思?

為了這份備忘錄的目的,我將「有用」定義為「產生的建議有助於我以更快的速度與沒有工具時相當的品質解決問題」。這不僅包括撰寫程式碼,還包括檢閱和調整產生的建議,以及在之後處理返工,如果出現品質問題的話。

影響建議有用的因素

注意:這主要是基於 GitHub Copilot 的經驗。

更普遍的技術堆疊

較安全的領域:技術堆疊越普遍,在模型的訓練資料中就會有越多的討論和程式碼範例。這表示產生的建議更有可能對 Java 或 Javascript 等語言有用,而不是對 Lua 等較新的或討論較少的語言。

然而:我的同事 Erik Doernenburg 撰寫了他使用 Rust 進行「“將 Copilot 帶到困難領域”」的經驗。他的結論:「總體而言,即使對於像 Rust 這種不太常見的程式語言,在使用較複雜資料結構的程式碼庫中,我發現 Copilot 很有幫助。」

更簡單且更常見的問題

更安全的領域:這一點有點難以定義。「更簡單」是什麼意思,「更常見」是什麼意思?我會使用一些範例來說明。

  • 常見問題:在先前的 備忘錄 中,我討論了一個產生中位數函式的範例。我會認為這是一個非常常見的問題,因此很適合用於產生。
  • 應用於我們情境的常見解決模式:例如,我已成功使用它來實作需要清單處理的問題,例如清單的對應、分組和排序鏈。
  • 樣板:建立樣板設定,例如 ExpressJS 伺服器、React 元件,或資料庫連線和查詢執行。
  • 重複模式:它有助於加速輸入具有非常常見且重複模式的內容,例如建立新的建構函式或資料結構,或在測試套件中重複測試設定。我傳統上會大量使用複製和貼上來處理這些事情,而 Copilot 可以加速這個過程。

當一位使用 Copilot 超過 2 個月的同事與一位尚未取得授權的人配對時,他「發現必須手動撰寫重複的程式碼令人痛苦」。然而,對於已經非常擅長使用 IDE 功能、捷徑和多重游標模式等功能的開發人員來說,這種類固醇自動完成功能可能不太有用。而且要注意,當編碼助理減少重複程式碼的痛苦時,我們可能會降低重構的動機。

然而:即使你之後會捨棄建議,你也可以在開始處理更複雜的問題時使用編碼助理來探索一些想法。

建議的較小大小

更安全的領域:產生的建議越小,所需的檢閱工作就越少,開發人員就越容易遵循建議的內容。

建議越大,你將花費更多時間來理解它,而且你必須修改它以符合你的情境也越有可能。較大的片段也會誘惑我們採取較大的步驟,這會增加遺漏測試涵蓋範圍或引入不必要的內容的風險。

然而:我懷疑這個因素與其他因素有很大的交互作用。小步驟特別有助於你已經有了解決問題的方法。因此,當你因為經驗較少或問題較複雜而還沒有計畫時,較大的片段可能有助於你開始制定計畫。

經驗較豐富的開發人員

較安全的領域:經驗仍然很重要。開發人員越有經驗,他們越有可能判斷建議的品質,並有效地使用它們。正如 GitHub 本身所言:「它擅長於你忘記的事情。」這項研究甚至發現,「在某些情況下,初階開發人員使用工具所需的時間比不使用工具多 7% 到 10%」。

然而:到目前為止我收集到的大多數觀察都是由經驗較豐富的開發人員所做的。因此,這是目前我對權衡取捨最不確定的部分。我的假設是,從上述其他因素來看,水域越安全,工具越不可能讓經驗較少的開發人員走錯路,而且更有可能讓他們獲得優勢。結對程式設計和其他形式的程式碼檢閱進一步降低了風險。

較高的錯誤容忍度

我已經提到判斷建議的品質和正確性的重要性。正如廣泛報導的那樣,大型語言模型可以「虛構」資訊,或在這種情況下,程式碼。當你處理問題或使用案例時,如果你做錯了,影響會更大,你需要特別注意審查建議。例如,當我最近在網際網路應用程式中保護 cookie 時,Copilot 建議了 Content-Security-Policy HTTP 標頭的值。由於我在這個領域的經驗不足,而且這是一個與安全性相關的使用案例,我不想只接受 Copilot 的建議,而是去了一個值得信賴的線上來源進行研究。

結論

對於程式設計協助來說,有較安全的領域,但從這個討論中你可以看到,有多個因素和交互作用決定了其有用性。有效使用程式設計協助工具是一項技能,並非僅從訓練課程或部落格文章中學到。重要的是在一段時間內使用它們,在安全領域內外進行實驗,並建立一種感覺,了解這些工具何時對你有用,以及何時繼續並自己執行。

感謝 James Emmott、Joern Dinkla、Marco Pierobon、Paolo Carrasco、Paul Sobocinski 和 Serj Krasnov 提供他們的見解和回饋


內嵌式協助 - 它如何造成阻礙?(2023 年 8 月 3 日) 

內嵌式協助 - 它如何造成阻礙?

在上一份備忘錄中,我談到了編碼協助在何種情況下會很有用。這份備忘錄是一份二合一備忘錄:以下是我們注意到這些工具會造成阻礙的兩種方式。

擴大不良或過時的做法

編碼助理在 IDE 中的一大優點是,它們可以使用周圍程式碼庫的片段來使用額外的內容來增強提示。我們發現,在編輯器中開啟正確的檔案來增強提示是改善建議實用性的一個相當重要的因素。

但是,這些工具無法區分好的程式碼和壞的程式碼。它們會將任何內容注入到看似相關的內容中。(根據這份逆向工程工作,GitHub Copilot 會尋找具有相同程式語言的開啟檔案,並使用一些啟發法來尋找類似的片段以新增到提示中。)因此,編碼助理可能會成為團隊中不斷從程式碼庫中的不良範例複製程式碼的那位開發人員。

我們還發現,在重構介面或在程式碼庫中引入新模式後,助理可能會陷入舊模式中。例如,團隊可能想要引入「開始使用工廠模式進行依賴注入」之類的新模式,但該工具會不斷建議目前依賴注入的方式,因為這種方式仍然普遍存在於整個程式碼庫和開啟的檔案中。我們稱之為中毒內容,而且我們目前還沒有很好的方法來減輕這種情況。

結論

AI 急於使用我們的程式碼庫來改善提示內容,這既是一種祝福,也是一種詛咒。這就是為什麼開發人員不要過度信任產生的程式碼,而仍然要自己審查並思考如此重要的原因之一。

審查疲勞和自滿

使用編碼助理意味著必須反覆進行小型程式碼審查。通常,當我們編碼時,我們的流程更多的是積極撰寫程式碼,並在腦海中實作解決方案計畫。現在,這會穿插著閱讀和審查程式碼,這在認知上是不同的,而且也是我們大多數人比積極產生程式碼更不喜歡的事情。這可能會導致審查疲勞,並感覺助理破壞了流程,而不是增強了流程。一些開發人員可能會暫時關閉該工具以休息一下。或者,如果我們不處理疲勞,我們可能會在程式碼審查中變得馬虎和大意。

審查自滿也可能是許多認知偏誤的結果

  • 自動化偏誤是我們「偏好自動化系統的建議,並忽略沒有自動化提供的矛盾資訊,即使它是正確的。」一旦我們對 GenAI 助理有良好的經驗和成功,我們可能會開始過度信任它們。
  • 當我使用 AI 編碼助理時,我也經常會在工作中感受到沉沒成本謬誤的扭曲版本。沉沒成本謬誤定義為「一旦投資金錢、精力或時間後,繼續努力的傾向更大」。在這種情況下,我們並沒有真正投資時間,相反,我們正在節省時間。但是,一旦我們從工具中獲得了多行程式碼建議,與其在看到建議不太正確後花費 5 分鐘自己撰寫程式碼,不如花費 20 分鐘讓建議發揮作用,這感覺起來更合理。
  • 一旦我們看到程式碼建議,就很难忘記它,而且我們很難想到其他解決方案。這是因為錨定效應,當「個人的決定受到特定參考點或『錨點』的影響」時就會發生。因此,雖然在我們還不知道如何解決問題時,編碼助理的建議對於集思廣益非常有用,但當集思廣益沒有成果時,了解錨定效應非常重要,我們需要重新整理大腦以重新開始。

結論

有時暫時不使用助理是可以的。我們必須小心,不要成為因為導航系統告訴他們這樣做,就開車衝進湖裡的那個人。

感謝 Thoughtworks Canada 的 Paul Sobocinski 周圍的「與 Copilot 結合」小組,他們向我描述了「內容中毒」效應和審查疲勞:Eren、Geet、Nenad、Om、Rishi、Janice、Vivian、Yada 和 Zack

感謝 Bruno、Chris、Gabriel、Javier 和 Roselma 對這份備忘錄的審查意見


編碼助理並未取代結對程式設計(2023 年 8 月 10 日) 

編碼助理並未取代結對程式設計

正如之前的備忘錄有望展示的那樣,我發現由 GenAI 驅動的編碼助理是開發人員工具鏈非常有用的新增功能。在某些情況下,它們可以明顯加快程式碼的撰寫速度,它們可以幫助我們擺脫困境,並更快地記住和查找事物。到目前為止,所有備忘錄主要都是關於 IDE 中的內嵌協助,但如果我們向其中新增聊天機器人介面,就有更多潛力提供有用的協助。特別強大的是整合到 IDE 中的聊天介面,它增強了我們不必在提示中說明的程式碼庫的額外內容。

然而,儘管我看到了潛力,但當人們談論編碼助理作為結對程式設計的替代品時,我確實會感到相當沮喪(GitHub 甚至稱他們的 Copilot 產品為「您的 AI 結對程式設計師」)。在 Thoughtworks,我們長期以來一直大力倡導結對程式設計和結對,以提高團隊的效率。這是我們用作專案起點的「明智的預設實務」的一部分。

將編碼助理定位為結對程式設計師對這種實務來說是一種損害,並強化了對結對好處的廣泛簡化理解和誤解。我回顧了一組我用來討論結對的投影片,以及在此網站上發表的綜合 文章,並將我在其中提到的所有好處塞進一張投影片中

Title: Benefits and purposes of pair programming, in 5 categories: 1. “one plus one is greater than two”, for things like knowledge exchange or onboarding; 2. Flow, for things like keeping focus and limiting work in process; 3. Avoid waste, referencing the 7 wastes of software development; 4. Continuous Integration, as in integrating multiple times a day, mentioning shorter code review loops; and 5., Practice skills needed on highly effective teams, like task organisation, empathy, communication, feedback

編碼助理可以發揮最明顯影響的領域是第一個,「1 加 1 大於 2」。它們可以幫助我們擺脫困境,它們可以讓入門變得更好,它們可以幫助更快速地進行戰術性工作,因此我們可以更多地專注於策略性工作,也就是整體解決方案的設計。它們也有助於在「這項技術如何運作?」的意義上進行知識分享。

然而,結對程式設計也與創造集體程式碼所有權和共享程式碼庫歷史知識的知識分享類型有關。這是關於分享未寫在任何地方的默會知識,因此大型語言模型也無法取得。結對也與改善團隊流程、避免浪費和讓 持續整合 變得更容易有關。它幫助我們練習協作技能,例如溝通、同理心,以及給予和接收回饋。它還提供了寶貴的機會,讓遠距優先團隊彼此建立聯繫。

結論

編碼助理只能涵蓋結對程式設計目標和好處的一小部分。這是因為結對是一種有助於改善整體團隊的實務,而不仅仅是單獨的程式設計師。如果執行得當,溝通和協作水準的提升將改善流程和集體程式碼所有權。我甚至會主張,使用這些工具進行結對可以最好地減輕 LLM 輔助編碼的風險(請參閱 先前備忘錄 中的「它如何妨礙」)。

使用編碼助理來改善結對,而不是取代結對。


TDD 與 GitHub Copilot(2023 年 8 月 17 日) 

TDD 與 GitHub Copilot

作者 Paul Sobocinski

GitHub Copilot 等 AI 編碼助理的出現是否意味著我們不再需要測試?TDD 是否會過時?為了回答這個問題,讓我們探討 TDD 協助軟體開發的兩種方式:提供良好的回饋,以及在解決問題時「分而治之」的方法。

TDD 以獲得良好的回饋

良好的回饋快速且準確。在這兩方面,沒有什麼能比得上從撰寫良好的單元測試開始。不是手動測試、不是文件、不是程式碼檢閱,是的,甚至不是生成式 AI。事實上,LLM 提供不相關的資訊,甚至會產生幻覺。在使用 AI 編碼助理時,特別需要 TDD。由於我們需要對自己撰寫的程式碼獲得快速且準確的回饋,因此我們也需要對 AI 編碼助理撰寫的程式碼獲得快速且準確的回饋。

TDD 以分而治之解決問題

透過分而治之解決問題表示可以比解決較大的問題更早解決較小的問題。這能實現持續整合、主幹開發,並最終實現持續交付。但如果 AI 助理為我們編寫程式碼,我們真的需要所有這些嗎?

是的。LLM 在單一提示後很少提供我們需要的確切功能。因此,反覆開發尚未消失。此外,LLM 在透過思考鏈提示逐步解決問題時,似乎會「引發推理」(請參閱連結的研究)。基於 LLM 的 AI 編碼助理在分而治之解決問題時表現最佳,而 TDD 就是我們在軟體開發中這樣做的方式。

針對 GitHub Copilot 的 TDD 訣竅

在 Thoughtworks,我們從今年年初開始將 GitHub Copilot 與 TDD 搭配使用。我們的目標是針對此工具的使用,實驗、評估和演進一系列有效實務。

0. 開始使用

TDD represented as a three-part wheel with 'Getting Started' highlighted in the center

從空白測試檔案開始並不表示從空白內容開始。我們通常從包含一些粗略筆記的使用者故事開始。我們也會與配對夥伴討論起點。

這些都是 Copilot 在我們將其放入開啟的檔案(例如測試檔案的頂端)之前「看不到」的內容。Copilot 可以處理錯字、項目符號、不佳的文法,您說得出來的所有問題。但它無法處理空白檔案。

一些對我們有用的開始內容範例

  • ASCII 藝術模型
  • 驗收標準
  • 指導性假設,例如
    • 「無需 GUI」
    • 「使用物件導向程式設計」(相較於函式式程式設計)

Copilot 會使用開啟的檔案作為背景,因此同時開啟測試和實作檔案(例如並排開啟)能大幅提升 Copilot 的程式碼完成能力。

1. 紅色

TDD represented as a three-part wheel with the 'Red' portion highlighted on the top left third

我們從撰寫一個具描述性的測試範例名稱開始。名稱越具描述性,Copilot 的程式碼完成效能越好。

我們發現 Given-When-Then 結構在三個方面有所幫助。首先,它提醒我們提供商業背景。其次,它允許 Copilot 為測試範例提供豐富且具表現力的命名建議。第三,它從檔案頂端的背景中揭示 Copilot 對問題的「理解」(如前一節所述)。

例如,如果我們正在處理後端程式碼,而 Copilot 將我們的測試範例名稱程式碼完成為「假設使用者…按一下購買按鈕」,這表示我們應該更新檔案頂端的背景,以指定「假設無 GUI」或「此測試套件與 Python Flask 應用程式的 API 端點介接」。

更多需要注意的「陷阱」

  • Copilot 可能一次程式碼完成多個測試。這些測試通常是無用的(我們會刪除它們)。
  • 隨著我們新增更多測試,Copilot 會程式碼完成多行,而不是一次一行。它通常會從測試名稱推斷出正確的「安排」和「動作」步驟。
    • 陷阱就在這裡:它推斷出正確的「斷言」步驟的頻率較低,因此我們在這裡特別小心,確保新的測試在進入「綠色」步驟之前正確失敗

2. 綠色

TDD represented as a three-part wheel with the 'Green' portion highlighted on the top right third

現在我們準備好讓 Copilot 協助實作。一個現有的、具表現力和可讀性的測試套件,能最大化 Copilot 在此步驟的潛力。

話雖如此,Copilot 常常無法「逐步進行」。例如,在新增一個新的方法時,「逐步進行」表示回傳一個硬編碼值以通過測試。到目前為止,我們還無法說服 Copilot 採用此方法。

回填測試

Copilot 沒有「逐步進行」,而是跳到前面提供功能,儘管這些功能通常相關,但尚未經過測試。作為解決方法,我們「回填」遺漏的測試。雖然這偏離了標準的 TDD 流程,但我們尚未看到我們的解決方法有任何嚴重問題。

刪除並重新產生

對於需要更新的實作程式碼,讓 Copilot 參與的最有效方式是刪除實作,並讓它從頭開始重新產生程式碼。如果這樣做失敗了,刪除方法內容並使用程式碼註解寫出逐步方法可能會有所幫助。如果這樣做還是失敗了,最好的方法可能是暫時關閉 Copilot,並手動編寫出解決方案。

3. 重構

TDD represented as a three-part wheel with the 'Refactor' portion highlighted on the bottom third

在 TDD 中重構是指進行逐步變更,以改善程式碼庫的可維護性和可擴充性,所有這些都是在保留行為(和可運作的程式碼庫)的同時進行的。

對於這一點,我們發現 Copilot 的能力有限。考慮以下兩個場景

  1. 「我知道我想嘗試的重構動作」:IDE 重構捷徑和多游標選取等功能讓我們比 Copilot 更快地到達我們想去的地方。
  2. 「我不知道要採取哪個重構動作」:Copilot 程式碼完成無法引導我們完成重構。但是,Copilot Chat 可以直接在 IDE 中提出程式碼改進建議。我們已經開始探索該功能,並看到在小範圍的局部範圍內提出有用建議的承諾。但是,對於更大規模的重構建議(即超出單一方法/函式),我們還沒有取得太多成功。

有時候我們知道重構動作,但不知道執行它所需的語法。例如,建立一個測試模擬,讓我們可以注入依賴關係。對於這些情況,當透過程式碼註解提示時,Copilot 可以幫助提供內嵌答案。這讓我們免於切換到文件或網路搜尋的背景。

結論

俗話說,「垃圾進,垃圾出」,這適用於資料工程以及生成式 AI 和 LLM。換句話說:品質較高的輸入讓 LLM 的能力可以得到更好的利用。在我們的案例中,TDD 維持高水準的程式碼品質。這種高品質的輸入會產生比其他方式更好的 Copilot 效能。

因此,我們建議將 Copilot 與 TDD 一起使用,我們希望您發現上述提示對此有所幫助。

感謝 Thoughtworks Canada 開始的「與 Copilot 整合」團隊;他們是本備忘錄中涵蓋的發現的主要來源:Om、Vivian、Nenad、Rishi、Zack、Eren、Janice、Yada、Geet 和 Matthew。


GenAI 與其他程式碼產生器有何不同?(2023 年 9 月 19 日) 

GenAI 與其他程式碼產生器有何不同?

在我職業生涯的初期,我在模型驅動開發 (MDD) 領域投入許多心力。我們會想出一個建模語言來代表我們的領域或應用程式,然後使用該語言以圖形或文字的方式描述我們的需求(自訂 UML 或 DSL)。接著,我們會建構程式碼產生器,將這些模型轉換為程式碼,並在程式碼中留下由開發人員實作和自訂的指定區域。

不過,這種程式碼產生方式從未真正普及,除了嵌入式開發的某些領域。我想那是因為它處於一個尷尬的抽象層級,在多數情況下,它提供的成本效益比不如其他抽象層級,例如架構或平台。

GenAI 的程式碼產生方式有何不同?

在我們的軟體工程工作中,我們持續做出的關鍵決策之一,就是選擇適當的抽象層級,以在實作工作量與我們使用案例所需的客製化和控制層級之間取得良好的平衡。作為一個產業,我們不斷嘗試提高抽象層級,以減少實作工作量並提升效率。但對於此,有一種看不見的力場,受到我們所需的控制層級所限制。以低程式碼平台為例:它們提高了抽象層級並減少了開發工作量,但結果是最適合某些類型簡單明瞭的應用程式。一旦我們需要執行更客製化和更複雜的工作,我們就會撞上力場,必須再次降低抽象層級。

An illustration of typical abstraction levels in software, starting from 0s and 1s at the bottom, going up via programming languages, frameworks, platforms, and low code applications. A squiggly line at the top marks the "Invisible force field for customizability and extensibility".

GenAI 開啟了一個全新的潛力領域,因為它並非另一種嘗試打破力場的嘗試。相反地,它可以讓我們人類在所有抽象層級上更有效率,而無需正式定義結構化語言和翻譯器,例如編譯器或程式碼產生器。

An illustration of typical abstraction levels in software, starting from 0s and 1s at the bottom, going up via programming languages, frameworks, platforms, and low code applications. A vertical box on the right marked "Natural Language" has arrows going into all of the abstraction levels, indicating that GenAI can help create all of those levels.

我們在應用 GenAI 時,抽象層級越高,建置軟體所需的整體工作量就越低。回到低程式碼範例,在該領域中有一些令人印象深刻的範例,展示了如何僅使用幾個提示就能建置完整的應用程式。然而,這也伴隨著低程式碼抽象層級的相同限制,在於你能涵蓋的用例。如果你的用例觸及該力場,而且你需要更多控制權,你必須回到較低的抽象層級,同時也回到較小的可提示單元。

我們需要重新思考我們的抽象層級嗎?

當我推測 GenAI 在軟體工程方面的潛力時,我採取的一種方法是思考我們的自然語言提示與我們的目標抽象層級之間的抽象距離。我上面連結的 Google AppSheet 示範使用非常高層級的提示(「我需要建立一個應用程式,以協助我的團隊追蹤旅遊請求 […] 填寫表單 […] 請求應傳送給管理者 […]」)來建立一個運作中的低程式碼應用程式。使用這樣的提示,我們可以將多少個目標層級向下推動以獲得相同的結果,例如使用 Spring 和 React 框架程式碼?或者,提示必須詳細到什麼程度(抽象程度較低)才能在 Spring 和 React 中達成相同的結果?

如果我們想要更好地利用 GenAI 在軟體工程方面的潛力,也許我們需要重新思考我們的傳統抽象層級,為 GenAI 搭建更多「可提示」的距離。

感謝 John Hearn、John King、Kevin Bralten、Mike Mason 和 Paul Sobocinski 對這份備忘錄提供有見地的審查意見


最新備忘錄:如何解決編碼助理的不可靠性

2023 年 11 月 29 日

編碼助理實用性的其中一個折衷方案是其不可靠性。基礎模型相當通用,而且基於大量的訓練資料,與手邊的任務相關或不相關。此外,大型語言模型會編造事情,它們會「產生幻覺」,這是常見的說法。(旁注:關於「產生幻覺」一詞有很多討論,關於它實際上並非用來描述此現象的正確心理學隱喻,但也關於一開始使用心理學術語,因為它將模型擬人化。)

這種不可靠性會產生兩個主要的風險:它可能會對我程式碼的品質產生負面影響,而且可能會浪費我的時間。考量到這些風險,快速有效地評估我對編碼助理輸入的信心至關重要。

我如何判斷對助理輸入的信心

以下是我嘗試評估建議的可靠性和使用風險時,通常會浮現在腦海中的一些問題。這適用於輸入程式碼時的「自動完成」建議,以及聊天機器人的回答。

我是否有快速的回饋迴路?

我越快能找出答案或產生的資訊是否有效,助理浪費我時間的風險就越低。

  • 我的 IDE 能否協助我進行回饋迴路?我是否有語法突顯、編譯器或轉譯器整合、程式碼檢查外掛程式?
  • 我是否有測試,或快速手動執行建議程式碼的方法?在一個案例中,我使用程式碼助理聊天機器人來協助我研究如何在 HTML 頁面中最佳顯示可折疊 JSON 資料結構。聊天機器人告訴我一個我從未聽過的 HTML 元素,所以我不知道它是否存在。但將它放入 HTML 檔案並在瀏覽器中載入,就能輕鬆確認。舉一個反例,驗證我從未聽過的基礎架構程式碼片段的回饋迴路通常會長很多。

我是否有可靠的回饋迴路?

除了 AI 輸入的回饋迴路速度外,我也會思考該回饋迴路的可靠性。

  • 如果我有一個測試,我對該測試有多有信心?
  • 我是自己撰寫測試,還是也使用 AI 助理產生測試?
  • 如果 AI 產生測試,我對自己檢視這些測試效能的能力有多有信心?如果我正在撰寫的功能相對簡單且例行,而且使用我熟悉的語言,那麼我當然比撰寫更複雜或更大的功能有信心。
  • 我在使用助理時是否與他人配對?他們將提供額外的輸入並檢視 AI 輸入,並增加我的信心。
  • 如果我不確定測試涵蓋範圍,我甚至可以使用助理本身來提高我的信心,並要求它提供更多邊界案例進行測試。這就是我如何找到關鍵的遺漏測試場景 針對我在先前備忘錄中描述的中位數函數

誤差範圍是多少?

我也會思考我正在做的事情的誤差範圍。誤差範圍越低,我對 AI 輸入的批評就會越嚴格。

  • 當我引入新的模式時,我會考慮這對程式碼庫整體設計的爆破半徑較大。團隊中的其他開發人員會採用該模式,而編碼助理一旦將其納入程式碼中,也會在整個團隊中複製該模式。例如,我注意到在 CSS 中,GitHub Copilot 經常向我建議彈性盒狀配置。選擇配置方法是一個重大的決定,因此在使用此方法之前,我會想要諮詢前端專家和團隊中的其他成員。
  • 當然,任何與安全性相關的事項都具有較低的誤差範圍。例如,我正在開發一個網路應用程式,需要設定「Content-Security-Policy」標頭。我對這個特定標頭一無所知,因此我首先詢問 Copilot 聊天。但由於主題的關係,我不想依賴它的答案,而是轉向網路上值得信賴的安全資訊來源。
  • 這段程式碼的存續期有多長?如果我正在開發原型或一次性程式碼,我比在製作系統時更可能毫不質疑地使用 AI 輸入。

我是否需要非常新的資訊?

我需要的答案越新、越具體(例如,針對架構版本),它錯誤的風險就越高,因為我正在尋找的資訊不可用或 AI 無法區分的機率較高。對於此評估,了解手邊的 AI 工具是否僅能存取訓練資料以外的資訊也很有幫助。如果我正在使用聊天,我希望知道它是否有能力考量線上搜尋,或是否僅限於訓練資料。

給助理一個時間限制

為了減輕浪費時間的風險,我採取的一種方法是給它一種最後通牒。如果建議沒有在付出少量額外努力的情況下為我帶來價值,我就會繼續前進。如果輸入沒有足夠快地幫助我,我總是假設助理最糟糕的情況,而不是給它好處懷疑,並花費額外 20 分鐘讓它發揮作用。

想到一個例子,當時我正在使用 AI 聊天機器人來協助我產生 mermaid.js 類別圖表。我不太熟悉 mermaid.js 語法,我持續嘗試讓建議發揮作用,並認為我可能以錯誤的方式將其包含在我的標記檔案中。結果證明,語法完全錯誤,我在大約 10 分鐘後終於前往線上文件時才發現。

為助理建立角色

在準備這份備忘錄時,我開始思考為助理建立角色是否有助於負責任地使用它,並在最短的時間內完成。也許在這種情況下,將 AI 人格化實際上會有幫助?

思考各種不可靠性,我想像具有以下特質的 AI 角色

  • 熱心助人
  • 固執
  • 見多識廣,但經驗不足(對於龍與地下城愛好者:高智力,低智慧)
  • 不承認自己「不知道」某事

我嘗試使用圖片產生器進行一些提示,要求它提供熱心海狸和固執驢子的變體。以下是其中我最喜歡的一個(Midjourney 中的「熱心固執的驢子快樂書籍電腦;卡通、基於向量的平面色塊」)

a cartoonish picture of a very excited donkey among a stack of books

你甚至可以為你的角色想一個有趣的名字,並在團隊中討論它。「Dusty 在那次會議中是一個令人討厭的萬事通,我們不得不暫時關閉它們」,或「我很高興 Dusty 在那裡,我在午餐前完成了那項任務」。但你永遠不應該說的一件事是「Dusty 導致了那個事件!」,因為 Dusty 基本上未成年,他們沒有犯罪執照。我們有點像是最終對提交負責的父母,而「父母對其子女負有責任」。

結論

每次使用編碼助理時,情境評估清單似乎有很多要套用。但我相信我們使用這些工具的次數越多,我們就會做得越好。在編碼時,我們總是根據經驗對像這樣的多個維度進行快速評估。我發現,當我遇到上述情況時,我已經更能決定何時使用和信任助理 - 換句話說,我越觸摸熱爐。

你也可能會想,「如果 AI 助理不可靠,那麼我一開始為什麼要使用它們?」。在一般使用生成式 AI 工具時,我們必須改變思維方式。 我們不能對它們抱持與「常規」軟體相同的期望。GitHub Copilot 傳統的程式碼產生器,無法提供給你 100% 所需的內容。但在 40-60% 的情況下,它可以讓你完成 40-80% 的工作,這仍然很有用。當你調整這些期望,並給自己一些時間來了解熱心驢子的行為和怪癖時,你將從 AI 編碼助理中獲得更多好處。

感謝 Brandon Cook、Jörn Dinkla、Paul Sobocinski 和 Ryder Dain 提供的回饋和意見。

此備忘錄是使用 GitHub Copilot 編寫的,採用 markdown 檔案。它有助於提供想法和措辭,有時當我卡住時,但建議很少會出現在最終文字中。我使用 ChatGPT 作為同義詞詞典,並為驢子找一個好名字。