• 猶豫 AI 生成的程式碼應該審查到什麼程度的人
  • 感覺 AI 讓實作變快了,但審查負擔也增加的人
  • 想思考 AI 時代學習設計與測試的意義的人

這篇文章整理以下幾個觀點:

  • 透過契約先決定「正確」的條件
  • 透過副作用與抽象化,限制需要閱讀內部實作的範圍
  • 將契約轉換為測試,用來驗證 AI 的實作

核心想法是:

不要用相同的密度去閱讀所有 AI 生成的程式碼,而是先由人類設計出「應該遵守的規格」,再建立能從外部驗證它的狀態

這不是說 AI 寫的程式碼就不用看了,而是要透過設計與測試,讓人類的審查能集中在重要的判斷上。

※ 這是我在參加外部讀書會時學到的內容整理而成。


使用 AI 之後,可以比以前更短時間產生更多程式碼。

但是,如果這些程式碼都必須由人類逐行確認才能判斷是否正確,那麼人類端就會成為瓶頸,如下所示:

AI 大量撰寫程式碼
        ↓
人類大量閱讀程式碼
        ↓
審查跟不上

為了減少「必須把所有內部實作都讀完,才能理解行為」這種結構,本文會思考以下四件事:

  1. 以契約進行設計
  2. 隔離副作用
  3. 抽象化
  4. 以契約為基準的測試

全體圖


1. 透過契約進行設計——先先決定「正確」的條件

契約式設計(Design by Contract)通常會用以下三種方式來整理處理流程的規格:

前置條件
→ 呼叫之前必須滿足的條件

後置條件
→ 在滿足前置條件並正常結束時成立的條件

不變條件
→ 必須持續維持的狀態

例如,假設有一個會變更數量的處理。

如果只是說「變更數量」,就無法知道是否允許負數、最大值是多少、超過上限時要怎麼處理。

因此,可以像下面這樣定義契約:

前置條件
→ 增加量必須是正整數
→ 變更前的數量 + 增加量 必須小於等於 20

後置條件
→ 正常結束後的數量 = 變更前的數量 + 增加量

不變條件
→ 數量永遠是 1~20 的整數

至於超過上限時,是要丟出例外,還是回傳代表失敗的結果,也需要先決定。

這樣一來,就不是只說:

請正確實作

而是先明確指出:

要滿足什麼條件才算正確

如果只是對 AI 說「請用契約式設計來實作」,契約本身也可能會變成由 AI 自行推測。

人類必須具體化哪些輸入是有效的、正常結束後狀態應該如何、哪些東西可以變更、哪些條件必須維持。

契約不一定只能寫在註解裡,也可以表現在型別、驗證、資料庫約束、測試等多個地方。

重點是:

要決定契約應該在哪裡表達、在哪裡強制


2. 隔離副作用——回傳值以外還有什麼改變?

例如,假設有下面這個函式:

function calculateTotal(
  prices: number[]
): number {
  prices.sort((a, b) => a - b);

  return prices.reduce(
    (total, price) => total + price,
    0
  );
}

從呼叫端來看,這看起來像是一個會計算並回傳總和的函式。

但其內部其實透過 sort() 改變了傳入陣列的順序。

也就是說,這個函式實際上會做兩件事:

回傳總和
+
將傳入的陣列重新排序

如果這個副作用無法從外部得知,就必須讀到內部實作才能安全使用它。

因此,我們要問的是:

呼叫這個函式之後,除了回傳值之外,還有什麼會改變?

如果不想修改引數,就應該先複製再操作。甚至也可以重新檢討:計算總和真的需要排序嗎?

副作用本身並不是壞事。資料庫寫入、HTTP 通訊、記錄日誌,這些都是應用程式必要的副作用。

重點在於要讓副作用的位置清楚可見。

外層:取得輸入、驗證身分、DB、HTTP
              ↓
內層:業務規則計算
              ↓
外層:儲存、通知、記錄日誌

不是要把所有東西都變成純函式,而是:

先隔離副作用,再把能純化的部分盡量純化


3. 抽象化——拉出能夠不用每次都讀內部實作就能使用的界線

例如,假設有以下程式碼:

const charge = CallCharge.create(
  durationMinutes
);

只要使用端理解「根據通話時間建立通話費用」這個外部契約,就不必每次都追著內部的計算公式看。

好的抽象化,不只是把程式碼搬到另一個類別,而是:

建立一個能以外部契約為基準來使用的界線

不過,做了抽象化並不代表就能自動信任它。

在導入或變更時,仍然要審查內部實作,確認契約有被型別、約束、測試等機制所強制執行。正因為那個界線已被驗證且穩定,使用端才不需要每次都重看內部實作。

另外,也不是類別切得越細越好。應該抽象化的是那些具有獨特規則或不變條件,且值得為其命名的概念。


一旦決定了契約,就可以把它當成測試的依據。

例如,如果 Quantity 的不變條件是「1~20 的整數」,那就應該確認以下情況:

輸入期望結果1成功20成功0失敗21失敗1.5失敗重點不在於測試程式碼怎麼寫,而是要把契約和測試對應起來。

人類決定契約
        ↓
把契約轉成測試
        ↓
AI 實作
        ↓
驗證是否符合契約

不是先叫 AI「寫出正確的程式碼」,而是由人類先決定什麼才算正確。


即使測試通過了,也不代表整個系統的正確性已經被證明。

例如,仍然可能存在以下問題:

  • 契約本身就漏掉了規格
  • 權限驗證條件缺失
  • 沒有考慮並行處理或交易
  • 對外部 I/O 失敗時的規格沒有定義
  • 沒有驗證效能等非功能性需求

測試不可能自動證明所有輸入與狀態。

極端來說:

如果把錯誤的規格精準地測試出來,AI 也可能產生完全符合那個錯誤規格的程式碼

因此,在 AI 時代的審查中,必須設計出:

哪些部分可以機械式驗證,哪些部分應該集中人類判斷


在實作之前,可以先用以下問題來思考:

  1. 哪些是有效輸入,哪些是無效輸入
  2. 成功之後應該成立什麼
  3. 必須始終維持的不變條件是什麼
  4. 除了回傳值之外,還有哪些副作用
  5. 應該在哪裡用型別、約束、測試來驗證

其中最重要的是:

要讓這段程式碼即使不用每次全部讀完,也能使用,那麼從外部可以確認哪些事情?

透過設計,減少「如果不把全部看完,就不知道會發生什麼事」的狀態。


當 AI 讓寫程式的速度變快時,

AI 實作
      ↓
人類用相同密度把全部都讀過一遍

這種方式很可能讓審查跟不上。

因此,我們要建立以下流程:

人類先決定應該守住的規格
          ↓
整理成前置條件、後置條件、不變條件
          ↓
隔離副作用
          ↓
抽象成有意義的單位
          ↓
以型別、資料庫約束、測試來表達
          ↓
交由 AI 實作
          ↓
驗證是否符合契約
          ↓
把人類審查集中在高風險部分

重點不是:

讓 AI 思考「正確的程式碼」

而是:

由人類先決定「滿足什麼條件才算正確」

這並不代表完全不用讀程式碼,而是要降低每次都用相同密度閱讀全部內容的必要性,讓人類的審查聚焦在真正需要判斷的地方。

契約、隔離副作用、抽象化、測試,都是 AI 出現之前就存在的基本設計原則。
正因為 AI 能產生的程式碼變多了,這些原則的重要性也更明顯地提高了。


原文出處:https://qiita.com/masashige0904/items/43beaaabc2c0ef2dcb1c


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝56   💬2  
736
🥈
NewsData
2
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登