clean-code

clean-code

熱門

透過嚴謹的命名、小型函式與乾淨的錯誤處理,寫出可讀且易於維護的程式碼。當使用者提到「清理這段程式碼」、「這個函式太長」、「程式碼壞味道」、「命名慣例」、「童子軍規則」、「單一職責」或「單元測試品質」時觸發。亦可在審查 pull request 的可讀性、解開混亂的函式、討論註解風格或改善錯誤處理模式時啟用。涵蓋 SRP、註解紀律、格式化與單元測試。重構技術請見 refactoring-patterns;架構與依賴規則請見 clean-architecture。

1698星標
171分支
更新於 2026/7/16
SKILL.md
唯讀
名稱
clean-code
描述

透過嚴謹的命名、小型函式與乾淨的錯誤處理,寫出可讀且易於維護的程式碼。當使用者提到「清理這段程式碼」、「這個函式太長」、「程式碼壞味道」、「命名慣例」、「童子軍規則」、「單一職責」或「單元測試品質」時觸發。亦可在審查 pull request 的可讀性、解開混亂的函式、討論註解風格或改善錯誤處理模式時啟用。涵蓋 SRP、註解紀律、格式化與單元測試。重構技術請見 refactoring-patterns;架構與依賴規則請見 clean-architecture。

Clean Code Framework

一套嚴謹的程式碼撰寫方法,讓程式碼能傳達意圖、減少意外,並樂於接受變更。在撰寫新程式碼、審查 pull request、重構舊系統或提供程式碼品質建議時,應用這些原則。

核心原則

程式碼被閱讀的次數遠多於被撰寫的次數——為讀者最佳化。 讀寫比遠超過 10:1,因此每個命名選擇、函式邊界與格式化決策,不是增加清晰度就是增加成本。乾淨的程式碼讀起來像流暢的散文:名稱揭示意圖,函式一步步說故事,並遵守童子軍規則——永遠讓程式碼比你發現時更乾淨。

評分

目標:10/10。 根據以下原則對任何程式碼評分 0-10。報告當前分數以及達到 10/10 所需的具體改進。

  • 9-10: 名稱揭示意圖,函式小而專注,錯誤處理一致,測試乾淨且全面
  • 7-8: 大致乾淨,但有輕微命名模糊或少數長函式;測試可能缺少邊界案例
  • 5-6: 好壞參半——好的模式伴隨不明確的名稱、重複邏輯或不一致的錯誤處理
  • 3-4: 長且多功能函式、誤導的名稱、測試不佳或缺失
  • 1-2: 幾乎無法閱讀——魔術數字、晦澀的縮寫、沒有結構、沒有測試

Clean Code Framework

六個紀律,撰寫能清楚溝通並適應變更的程式碼:

1. 有意義的名稱

核心概念: 名稱應揭示意圖,避免誤導資訊,並讓程式碼讀起來像散文。如果名稱需要註解來說明,那就是名稱錯了。

為何有效: 名稱是最普遍的文件形式——一個好的名稱消除了閱讀實作的需求;一個差的名稱迫使每位讀者逆向工程意圖。

關鍵見解:

  • 名稱應回答為何存在、做什麼以及如何使用
  • 無編碼、前綴或型別資訊(無匈牙利命名法);僅在極小範圍的迴圈計數器使用單字母
  • 類別是名詞;方法是動詞
  • 每個概念一個詞:不要混用 fetchretrieveget
  • 範圍越大,名稱越長且越具描述性
  • 自由重新命名——IDE 讓它變得簡單

程式碼應用:

情境 模式 範例
變數 揭示意圖 elapsedTimeInDays 而非 d
布林值 謂語表述 isActivehasPermissioncanEdit
函式 動詞 + 名詞 calculateMonthlyRevenue() 而非 calc()
類別 命名職責的名詞 InvoiceGenerator 而非 InvoiceManager

重新命名或審查名稱時,請參閱 references/naming-conventions.md——各語言慣例、可發音/可搜尋的表格,以及前後對比範例。

2. 函式

核心概念: 函式應小、做一件事且做好——理想 4-6 行,零到兩個參數,一個抽象層級。

為何有效: 小型單一用途函式易於命名、理解、測試和重用;長函式隱藏錯誤、抗拒測試並累積職責。

關鍵見解:

  • Step-Down 規則:程式碼由上而下閱讀,每個函式呼叫下一個抽象層級
  • 參數數量:零最好,一個可接受,兩個尚可,三個以上需要理由
  • 旗標參數是壞味道——函式做了兩件事;拆分它
  • Command-Query 分離:改變狀態或回傳值,絕不同時做
  • 提取到不能再提取:如果可以抽出一個命名函式,就做
  • 無隱藏副作用——名稱必須說出全部真相

程式碼應用:

情境 模式 範例
長函式 提取命名步驟 validateInput(); transformData(); saveRecord();
旗標參數 拆分為兩個函式 renderForPrint() / renderForScreen() 而非 render(isPrint)
錯誤情況 頂層守護子句 錯誤提前回傳,單一快樂路徑
多參數 引入參數物件 new DateRange(start, end) 而非 report(start, end, format, locale)
副作用 明確化效果 checkPassword() 啟動 session → 重新命名或分離

拆分長函式時,請參閱 references/functions-and-methods.md——參數數量規則、command-query 分離與 step-down 實作範例。

3. 註解與格式化

核心概念: 註解是無法用程式碼表達自己的失敗。當註解必要時,它們解釋為什麼,絕不解釋什麼。格式化創造視覺結構,讓程式碼易於掃讀。

為何有效: 註解會腐敗——程式碼變更但註解通常不變,產生比沒有更糟的文件。乾淨的格式化讓開發者像看報紙一樣掃讀程式碼:先看標題,細節按需閱讀。

關鍵見解:

  • 最好的註解是命名良好的提取函式
  • 可接受:法律標頭、TODO、公開 API 文件、真正的「為什麼」解釋
  • 註解掉的程式碼與日記式註解:刪除——版本控制會記住
  • 概念之間垂直開放;概念內部垂直密集;變數在靠近使用處宣告
  • 報紙隱喻:高層級函式在檔案頂部,細節在下方

程式碼應用:

情境 模式 範例
解釋「什麼」 用更好的名稱取代 // check if eligibleisEligible()
解釋「為什麼」 保留為註解 // RFC 7231 requires this header for proxies
註解掉的程式碼 刪除它 信任版本控制
團隊格式化 決定一次,自動化 Prettier、Black、gofmt

決定註解是否值得保留時,請參閱 references/comments-formatting.md——好/壞註解目錄與垂直格式化規則。

4. 錯誤處理

核心概念: 錯誤處理是與商業邏輯分離的關注點。使用例外而非回傳碼,為每個例外提供上下文,絕不回傳或傳遞 null。

為何有效: 回傳碼用檢查弄亂快樂路徑;例外乾淨地分離兩者。回傳 null 迫使每個呼叫者進行 null 檢查,一個遺漏的檢查會在遠離源頭處崩潰。

關鍵見解:

  • 先寫 try-catch——它定義了交易邊界
  • 偏好非受檢例外——受檢例外違反開放/封閉原則
  • 根據呼叫者的需求定義例外類別,而非失敗類型
  • 不回傳 null(使用空集合、Optional 或拋出);也不傳遞 null
  • Special Case / Null Object 模式:回傳具有預設行為的物件而非 null

程式碼應用:

情境 模式 範例
回傳 null 空集合或 Optional return Collections.emptyList() 而非 return null
錯誤碼 用例外取代 throw new InsufficientFundsException(balance, amount)
第三方 API 用轉接器包裝 PortfolioService 包裝廠商 API,轉譯其例外
特殊情況 Null Object 模式 GuestUser 具有預設行為而非 null 檢查
錯誤上下文 包含操作 + 狀態 "Failed to save invoice #1234 for customer 'Acme'"

設計例外或 null 策略時,請參閱 references/error-handling.md——Special Case 模式與第三方 API 包裝範例。

5. 單元測試

核心概念: 測試是一級程式碼,以與生產程式碼相同的紀律保持乾淨。骯髒的測試比沒有測試更糟——它們成為拖慢每次變更的負債。

為何有效: 乾淨的測試是可執行的文件與重構的安全網;骯髒的測試讓每次修改都變成與難以理解的測試程式碼搏鬥。

關鍵見解:

  • TDD 三定律:先寫一個失敗的測試;只寫足以失敗的測試;只寫足以通過的程式碼
  • 每個測試一個概念——一個邏輯斷言,不一定是單一 assert
  • F.I.R.S.T.:快速、獨立、可重複、自我驗證、及時
  • 建立領域特定測試語言:讀起來像 DSL 的輔助函式
  • 像重構生產程式碼一樣重構測試程式碼

程式碼應用:

情境 模式 範例
測試結構 Arrange-Act-Assert 設定、執行、驗證——清楚分離
測試命名 情境 + 預期行為 shouldRejectExpiredToken 而非 test1
共享設定 Builder/工廠輔助 aUser().withRole(ADMIN).build()
不穩定測試 移除外部依賴 模擬時間、網路、檔案系統

撰寫或清理測試時,請參閱 references/testing-principles.md——TDD 定律、F.I.R.S.T. 擴展與乾淨測試模式。

6. 程式碼壞味道與啟發式

核心概念: 壞味道是更深層設計問題的表面指標——學會快速辨識它們,並應用有針對性的重構,而非模糊的「清理」。

為何有效: 壞味道是啟發式,無需深入分析即可指向可能的問題,將程式碼審查直覺轉化為具體、可重複的動作。

關鍵見解:

  • 函式壞味道:太多參數、輸出參數、旗標參數、無用函式
  • 一般壞味道:重複、錯誤的抽象層級、特徵依戀、魔術數字
  • 測試壞味道:覆蓋率不足、跳過測試、未測試的邊界條件與失敗路徑
  • 以小而經過測試的步驟重構——絕不同時重構與新增功能
  • 童子軍規則:讓程式碼比你發現時更乾淨

程式碼應用:

情境 模式 範例
重複 提取共享邏輯 共同驗證 → validateEmail() 輔助函式
特徵依戀 將方法移到資料所屬類別 order.calculateTotal() 而非 calculator.total(order)
無用程式碼 刪除它 移除未使用的函式、不可達分支
魔術數字 命名常數 MAX_LOGIN_ATTEMPTS = 5 而非裸 5
散彈式修改 合併相關變更 將分散的邏輯分組到單一模組

當壞味道難以命名時,請參閱 references/code-smells.md——按類別分類的完整目錄,每個配對其有針對性的重構。

常見錯誤

錯誤 為何失敗 修正
縮寫名稱 節省幾秒鐘的撰寫時間,花費數小時的閱讀時間 完整描述性名稱;IDE 自動完成
「聰明」的一行式 寫起來令人印象深刻,除錯時不可能 展開為可讀的命名步驟
用註解代替重構 註解會腐敗;程式碼才是真相 改為提取命名良好的函式
捕獲通用例外 連同預期錯誤一起吞掉錯誤 捕獲特定例外;讓其餘傳播
沒有錯誤路徑的測試 快樂路徑正常,邊界案例崩潰 測試每個分支、邊界與失敗模式
過早最佳化 為了微小收益模糊意圖 先清理;最佳化測量到的瓶頸
上帝類別 一個 2000 行的類別做所有事 應用 SRP——按職責拆分
沒有測試的重構 沒有回歸的安全網 先寫特徵測試
不一致的慣例 每個檔案感覺像不同的程式碼庫 同意風格;用 linter 和 formatter 強制執行
到處回傳 null null 檢查像病毒一樣擴散 Optional、空集合或 Null Object

快速診斷

問題 如果否 行動
你能在不閱讀函式主體的情況下理解每個函式嗎? 名稱未揭示意圖 重新命名以描述其功能
所有函式都在 20 行以下嗎? 函式做太多事 將子操作提取為命名輔助函式
零個註解掉的程式碼區塊? 無用程式碼造成混亂 刪除——版本控制有歷史
錯誤處理是否與商業邏輯分離? try-catch 弄亂主要流程 提取處理器;例外優於回傳碼
每個類別是否有單一職責? 類別累積不相關的職責 拆分為專注且命名良好的類別
每個公開方法是否有測試? 沒有變更的安全網 在進一步變更前新增測試
測試名稱是否描述行為? 失敗難以解讀 重新命名為 shouldDoXWhenY
重複是否低於 3 次? 複製貼上散播錯誤 提取共享邏輯(§6)
魔術數字是否為命名常數? 意圖隱藏在原始值後 命名常數(§6)
所有測試是否在 10 秒內執行? 慢測試不會被執行 模擬外部依賴;拆分整合測試

延伸閱讀

基於 Robert C. Martin 的軟體工藝經典指南:

關於作者

Robert C. Martin ("Uncle Bob") 自 1970 年開始寫程式,共同撰寫敏捷宣言,並創立 Uncle Bob Consulting 與 Clean Coders。他的著作——《Clean Code》、《The Clean Coder》、《Clean Architecture》與《Clean Agile》——塑造了一整個世代開發者對程式碼品質的看法,他的核心立場是:要快,唯一的方法就是做好。