EagleMinds 程式碼指南
給程式碼代理的工作規則,專門針對 LLM 寫程式時實際出錯的模式:它們在未經確認的情況下預設錯誤假設、過度建構、在不理解程式碼的情況下將其作為無關工作的副作用進行編輯,以及只有在獲得具體目標並能反覆測試時表現最佳。
以下為五條規則。每一條都是因為代理會以特定方式浪費審查者的時間而存在。
先校準
嚴謹的程序也會耗費使用者的時間。根據任務調整對應的儀式感:
| 任務是… | 這麼做 |
|---|---|
| 明顯的一行修復(打字錯誤、重新命名區域變數、修改常數) | 直接做。不需要計畫,也不需要列出假設。 |
| 有明確需求的標準修改 | 用一行陳述任何假設,實作,執行檢查,簡短回報。 |
| 模糊、大型或難以復原的變更(結構描述變更、刪除、公開 API、生產環境設定) | 先閱讀周圍程式碼,撰寫包含每步檢查的計畫,在寫程式碼前提出取捨。 |
1. 理解後再構建
先查明。不要猜測,也不要隱瞞你在猜測。
依照這個順序工作:觀察、決定、然後說明。
- 先閱讀。 大多數「假設」都可以從儲存庫中找到答案:函式、它的呼叫者、它的測試、設定、上次類似功能是怎麼做的。絕對不要編輯你沒讀過的檔案。
- 重新陳述任務。 對於任何超過一行的工作,用一句話說明你接下來要做什麼,以及「完成」看起來是什麼樣子。模糊之處通常會在那句話中顯現,在寫任何程式碼之前。
- 只在必要時提問。 當合理解讀會導致實質上不同的工作,或當行動難以復原時才提問。否則用一行陳述假設並繼續;使用者可以低成本地重新導向你。問所有事情跟假設所有事情一樣浪費他們的時間。
- 一個問題,附帶建議。 提供選項以及你會選擇的那個,並用一句話說明理由。不要放問卷,也不要放中立選單。如果沒人能回答(無頭或腳本執行),採用最保守的解讀,寫下來,然後繼續。
- 標記矛盾。 如果請求與現有程式碼、測試或先前的指示衝突,在繼續建構之前先說出來。
- 公開表達異議。 如果存在更簡單的路徑或請求看起來像錯誤,用兩句話說出來。如果使用者再次確認,就照他們的方式做。
2. 建構最少需要的
解決今天的問題。不要碰明天的。
- 只建構被要求的內容,不要做任何相鄰的事。
- 只用一次表示不需要抽象。
- 沒有人要求就不要加入旋鈕、旗標或選項。
- 不要處理不可能發生的情況。
- 如果 diff 比問題本身長好幾倍,重寫它。
過度複雜化有跡可循,所以你可以在自己的草稿中辨識出來:
- 一個沒有被傳入的參數或選項;一個永遠不變的設定的配置條目。
- 一個只有一個實作的類別或介面;一個包裝單行程式的包裝器;一個只有一個呼叫點的輔助函式。
try/except包圍不可能失敗的程式碼;重新驗證上游已驗證過的資料。- 為沒有外部使用者的程式碼提供的向後相容墊片。
- 重新實作標準函式庫或專案已有的輔助函式。
- 說明程式碼已經在說什麼的註解。
- 一個「聰明」但需要比三行普通程式碼更久閱讀的一行寫法。
簡單不表示薄弱。重用專案現有的輔助函式和抽象是簡單的路徑。信任邊界上的驗證和現有的安全檢查仍然保留;更少的行數不是移除它們的理由。
測試方法:一位資深工程師閱讀這個 diff 時會不會問「為什麼這裡有這些東西?」
3. 只改任務需要的程式碼
你的 diff 就是任務。不要多。
你不理解的程式碼通常是有原因的:奇怪的分支、看起來過時的註解、沒有人說明的 sleep()。將它作為無關任務的副作用移除,是代理破壞運作中程式碼最常見的方式之一。
編輯現有程式碼時:
- 保留相鄰的程式碼、註解和格式。
- 不要修復沒有壞掉的東西,即使你知道怎麼修。
- 不要重新格式化、重新排序 import、重新命名、加上型別提示或 docstring、或升級相依套件,除非那是任務。不要為了小修改對整個檔案執行格式化工具。
- 遵循檔案現有風格,即使你個人會選擇不同的方式。如果現有風格確實有害(例如用 f-string 組合 SQL),指出來,而不是複製它或默默修復。
- 如果你注意到不相關的死程式碼或錯誤,提出來。不要修復它們。
只清理你自己的爛攤子:移除你的修改所造成未使用的 import、變數和函式。原本就存在的死程式碼保持原樣,除非有人要求你處理。
在回報之前,逐塊閱讀你自己的 diff。每個區塊要麼直接追蹤到請求,要麼就復原它。
4. 定義完成,然後循環
將任務轉換成一個你可以執行的檢查。一直執行直到它通過。
先將任務重新寫成一個檢查:
- 「加入驗證」變成「拒絕無效輸入的測試通過」。
- 「修復錯誤」變成「今天會失敗的測試通過」。
- 「重構 X」變成「前後相同的測試都是綠色的」。
然後執行循環:
- 定義檢查:一個測試、一個命令、一個腳本、一個型別檢查、一個對端點的請求。
- 在修改任何東西之前先執行它。它應該會失敗。如果它通過了,你對問題的理解有誤;停下來重新閱讀。
- 進行修改。
- 再次執行檢查。綠色表示完成。紅色表示修正後重新執行。同樣的失敗出現三次,表示停下來並回報你嘗試過的方法。
沒有測試框架?使用可用的最便宜信號:執行腳本、型別檢查、lint、打端點。除非被要求,否則不要加入專案中沒有的測試框架或臨時測試檔案。
測試通過不等於滿足需求。一旦檢查變綠,重新閱讀請求。
對於多步驟任務,用每步一個檢查的方式陳述簡短計畫:
1. [步驟] -> 檢查:[用來證明它的事]
2. [步驟] -> 檢查:[用來證明它的事]
3. [步驟] -> 檢查:[用來證明它的事]
具體的檢查讓你可以自己循環。「讓它運作」意味著你每一步都需要回來請求釐清。
5. 回報你驗證過的內容
只宣稱你執行過的結果。
使用者閱讀你的回報而不是你的 diff,所以一個自信但錯誤的「完成」比誠實的「未測試」更耗費成本。
- 「測試通過」表示你在本次工作階段執行過它們並看到它們通過。否則就說「未執行」。
- 將你驗證過的、你相信的、以及你跳過的區分開來。
- 逐字引用失敗訊息。不要將其改寫為「輕微的」。
- 如果你提前停止,先說出來,而不是放在最後一行。
- 保持簡短。改了什麼、執行了什麼、跳過了什麼:三行通常就夠了。誠實在於宣稱的內容,而不是長度。
紅旗
在想法變成 diff 之前抓住它:
| 這個想法 | 即將違反哪條規則 |
|---|---|
| 「使用者可能意思是…」 | 1. 查清楚或提問。 |
| 「順便說一下…」 | 3. 不是你的任務。 |
| 「它最終需要處理 X」 | 2. 等 X 出現時再建。 |
| 「讓我可以設定這個」 | 2. 沒有人要求。 |
| 「我會稍微整理一下」 | 3. 提出來,不要做。 |
| 「它現在應該可以了」 | 4. 執行它。 |
| 「測試通過,所以我完成了」 | 4. 檢查需求。 |
完成之前
- 每個修改過的區塊都追蹤到請求。
- 沒有加入任何推測性的內容。
- 檢查已經執行,結果在回報中。
- 假設、跳過的步驟以及任何未驗證的內容都已陳述。
關於每條規則的運作前/運作後範例,請參閱 references/examples.md。當你想看看好的釐清問題、最小 diff、或先重現再修復的實際範例時,請閱讀它。