去渣:讓生成的程式碼與人類仔細撰寫的無異
「渣」不是風格問題,而是信任問題。審查者一旦發現一個跡象,就會停止閱讀程式碼,開始尋找更多蛛絲馬跡。修復絕非僅止於表面:以下每一道關卡都在驗證必要性,而非僅是外觀。
從實際的 diff 開始工作,絕不依賴你對所寫內容的記憶。如果工作跨越分支,請針對每個分支與其合併基礎之間的 diff 進行審計。
第0關 — 小型撰寫,每行都有理由(預防勝於清理)
渣在產生前清除成本最低。建構時:
- 逐步建構。 從能完成一件事的最小變更開始;在添加下一環節前,先確保其正確且符合習慣用法。一次性寫成的龐大 diff 總是會累積渣;而 N 個小型增量,每個都經由此技能審查,則不會。這也會產生小型 PR,審查者通常會比大型 PR 更信任並更快核准。
- 在建立時質疑每一行:這行為何存在?為何這是個新函式,而不是呼叫現有函式?為何要有新檔案?如果答案是「生成的流程就是那樣」,請刪除或合併。每個添加都必須有一個你可以在審查中大聲說出的理由。
- 不要過度複雜化。 在第二個呼叫者出現之前,明顯的5行版本勝過抽象化的25行版本。不要有推測性參數、不要有「為未來彈性」、不要有包裝一個值的包裝型別。
- 不要重新發明輪子——這要在撰寫期間就做,而不只是在清理時:在撰寫任何工具函式前,先用 grep 搜尋儲存庫中是否有現有輔助函式,並檢查相依性樹是否有已擁有該問題所有權的套件(參見第2關)。
- 在撰寫前先比對鄰近程式碼。 先閱讀相鄰檔案;採用它們的命名、錯誤處理和測試框架,讓新程式碼讀起來像本來就在那裡一樣。
如果任務無法逐步建構(例如大型移植、廠商合約),請說明情況,並在其他人看到之前,以更嚴格的第1至5關審計來補償。
第1關 — 註解
最大的單一跡象。應用一條規則:註解必須陳述程式碼無法呈現的約束條件。 其他所有內容都刪除。
見則刪除:
- 敘述:
// 走訪條目、// 呼叫輔助函式、// 回傳結果 - 在方法主體中重複程式碼或其自身文件標題
- 審查者對話:
// 這很安全因為我們在上面檢查過了、// 請注意...—— 這是你為 diff 辯護,而不是告知下一位讀者 - 對 PR、審查、對話或「最近更改」的引用
- 解釋為何變更是正確的註解(應放在 PR 主體中)
- 在少於約300行的檔案中的章節標題(
// ---- 輔助函式 ----)
保留(這些是必要的):
- 有線格式或協定約束,尤其是逆向工程且沒有公開規格的:即使為空也必須存在的欄位 X;如果 Z 遺失,客戶端 Y 會拒絕整個酬載;引用上游來源檔案
- 為什麼不的註解:為何拒絕了顯而易見的方法、什麼會出錯
- 讀者會卡住的平台事實:此環境變數是 unix 慣例;此 API 在 Windows 上會靜默無操作
- 安全性/資料安全性不變量:絕不快取原始記錄,它內嵌了秘密
- 有意違反上游預設的政策釘選,並附上啟用條件
密度啟發法,非規則:對於膠水/CRUD 程式碼,每15行添加行多於約1行註解行就值得再看一眼。對於無文件協定程式碼,高密度可能是正確的——這些註解本身就是規格。根據保留/刪除列表來判斷每個註解,絕不只看數量。
文件標題:4到8行的純約束。如果標題是逐步敘述模組功能的段落,請刪減到檔案名稱和匯出項目無法表達的程度。
第2關 — 依賴項或標準庫已提供的手寫程式碼
第二大跡象:重新實作 uuid、雜湊、semver 解析、glob 匹配、日期運算、深層合併。
程序:
- 檢查套件已依賴的項目(讀取 package.json,而非記憶)。優先使用現有依賴項或執行時期標準庫,而非添加新項目。
- 如果確實需要新依賴項,請選擇無聊且經過實戰測試的那個。
- 在替換現有手寫實作前,透過經驗證明其等價性——對相同的輸入同時執行兩者並比較輸出。特別是確定性 ID 和雜湊程式碼:一個會改變輸出的替換會靜默地破壞現有使用者的等冪性。
- 如果保留手寫版本確實是正確的(避免10行程式碼的依賴項),請預期審查者的問題,並判斷這個論點是否比依賴項更有價值。通常不是:無聊的解決方案在審查中勝出。
- 例外:已經是標準庫組合(createHash + pipeline)的程式碼不算手寫。如果被問到,就這麼說;不要再進一步包裝它。
第3關 — 內聚性與必要性
渣是累積的:每次編輯都增加而不整合。尋找:
- 重複的條件表達式 → 提取一次,用意涵命名(
noConfigChanges,而非check1) - N 個檔案中的相同字面值 → 在擁有該概念的模組中定義一個共享常數;採用者匯入它
- 儲存庫輔助函式的區域實作 → 在保留私有副本前,先用 grep 搜尋現有輔助函式(
pathExists、輸出/格式工具、錯誤輔助函式)。刪除區域副本,匯入共享的那個。 - 死程式碼:對於 diff 中的每個輔助函式/型別/匯出項目,找到一個使用者。沒有使用者 → 立即刪除,而非「以後再用」。
- 呼叫端手動實作輔助函式已擁有的功能(間距、引用、ID 生成)→ 將行為移入輔助函式,精簡呼叫端
- 同一事實陳述兩次(文件標題 + 行內註解)→ 保留一個
- 回傳鬆散相關值的元組的函式→ 通常是兩個職責被融合的跡象;拆分或記錄合約
然後檢查儲存庫適配性:閱讀相鄰檔案並比對它們的慣用語法、命名、錯誤風格和測試模式。忽略專案風格規範的程式碼即使正確,看起來也像生成的。
第4關 — 測試
- 測試必須鎖定行為,而非錯誤。如果一個斷言編碼了錯誤的值「因為程式碼就是那樣做」,兩者都要修正。
- 鏡像測試套件的現有框架和輔助函式;不要在同一個檔案中發明一個並行的 mock 風格。
- 每個平台特定的跳過都帶有一行理由。
- 測試名稱描述情境,而非實作。
- 當你變更輸出形狀時,為被移除的形式添加一個否定斷言。
第5關 — 工具鏈對齊,然後驗證
- 使用儲存庫固定的格式化程式/林特版本(讀取 package.json——在本機執行不同主要版本會讓僅在 CI 上發生的失敗溜過)。
- 在堆疊式變更的每一層級執行 diff 所觸及的測試,而不僅僅是頂端。
- 在所有編輯完成後,再次重新審計最終 diff:
git diff並根據第1關大聲朗讀每個剩餘的註解行。密度檢查:git diff | grep -cE '^\+\s*(//|\*)'與總添加行數比較。 - 提交訊息:用散文說明原因,像個人一樣。不要為3行的提交寫項目符號變更日誌,不要使用「增強/改善/強化」。
報告
當被問到「這個去渣了嗎」:審計即時 diff 並用證據回答(密度數字、刪除了什麼、刻意保留了什麼及其原因),絕不憑記憶說「是」。如果保留了一些審查者可能會標記的內容——例如一個高密度的協定檔案——主動指出並用一句話給出辯護。
當審查者稱某些東西為渣時:在為任何實例辯護之前,先承認模式。在所有地方修復該類別,包括他們沒有標記的檔案(以及他們讚賞的檔案)。將標準作為一門紀律來應用,而不是作為對被標記行的損害控制,這才是重建信任的方式。