什麼是 Clean Code?

作者 쉬었음.com

Clean Code 不只是能夠編譯和執行的程式碼,更是讓其他開發者能理解其意圖,並能在日後安全地修改、擴充和驗證的程式碼。它不是由某項嚴格的國際標準或分數所定義的概念,而是涵蓋可讀性、可理解性、可維護性、一致性與變更安全性等品質目標的實務用語。google.github.io

一開始很容易把它想成只是「看起來好看的程式碼」。但在實務上,真正重要的時刻是在程式碼首次寫完之後:修正功能、找出 bug、新增需求,或檢閱同事的工作時。Clean Code 更著重於降低這些時刻所需的時間與出錯機率。因此,關鍵不在於背誦特定的語法技巧,而是思考讀者需要知道什麼,以及某項變更會影響哪些地方。

Clean Code 究竟是什麼意思?

軟體不是寫完一次就完成的文件。新增訂單狀態、變更定價規則或調查錯誤時,都會再次閱讀既有程式碼。讀者可能是原始開發者,但更多時候是其他團隊成員或未來的自己。Clean Code 是指讀者能在相對短的時間內理解程式碼的角色、輸入與輸出、重要條件,以及可能需要變更的地方的狀態。

這裡的「乾淨」不只是美觀上的判斷。例如,即使程式碼格式良好,若命名含糊、數項職責混在同一個函式中,且沒有方法可驗證,它仍然難以安全修改。反過來說,即使沒有採用特別醒目的風格,只要角色明確、符合團隊慣例,且有能確認變更的測試,從維護角度而言可能反而更好。程式碼審查不只檢視風格,也會檢視設計、功能正確性、複雜度、測試與文件。google.github.io

Clean Code 這個詞因 Robert C. Martin 於 2008 年出版的 Clean Code 一書而廣為人知。不過,書中的建議是置於特定程式語言與物件導向開發實務的脈絡中。與其把一本書或知名規則原封不動套用到所有語言和程式規模,不如判斷它是否能解決目前程式碼庫和團隊面臨的問題。www.informit.com

為什麼能執行的程式碼還不夠?

對目前輸入產生預期結果,是程式最基本的要求。但即使功能正確,若下一次變更時很容易壞掉,長期仍難以管理。例如,一個很長的函式可能同時包含折扣計算、權限檢查、顯示渲染與資料儲存。它現在或許能正常運作,但想只變更折扣政策的人,也更可能同時影響權限處理或儲存順序。

難以閱讀的程式碼不只是需要更長的閱讀時間。當開發者無法確信程式的意圖時,可能會複製相似邏輯、修改比必要範圍更廣的區域,或重新實作已經存在的規則。審查者也會難以判斷變更的影響範圍。可維護性是指不阻礙未來變更的特性,而 Clean Code 著重於提升這種可維護性。

不過,沒有人能預先消除所有未來的變更成本。當需求本身很複雜,或外部系統施加很強的限制時,程式碼在某種程度上也會變得複雜。更好的目標不是假裝現實很簡單,而是區分可避免與不可避免的複雜度。若複雜度確有必要,應透過結構、命名、測試與文件讓其原因清楚可見。

好的命名如何揭露程式碼意圖?

命名是讀者剛開始理解程式碼時最常接觸到的資訊。像 xdataprocessflag 這類廣泛的名稱,對撰寫者可能很熟悉,但無法告訴其他人它們代表什麼。相較之下,expiredCouponCountisEligibleForRefundcalculateShippingFee 等名稱,能相對直接地傳達值或操作的目的。有意義的名稱也能把原本需要透過註解說明的資訊,轉移到程式碼本身。google.github.io

好的命名重點在於具體性,而不是長度。在狹小範圍內已獲廣泛共識的概念可以使用短名稱;在較大範圍使用的值,則可能需要更多上下文。例如,迴圈索引 i 在非常短的迴圈中可以理解。但若函式回傳值或物件欄位只命名為 result,就很難知道它代表成功狀態、金額,還是查詢結果。

區分動詞與名詞也很有幫助。當函式採用動詞式名稱來揭露它做什麼,而值和物件採用名詞式名稱來揭露它們是什麼時,閱讀通常會更自然。sendReceipt() 是一項動作,而 receiptEmail 是資料。不過,加長名稱不會自動消除模糊性。handleUserData 雖然較長,仍不清楚它處理的是什麼。

// Example with unclear intent
if (a) {
  doIt(b);
}

// Example where the purpose of the condition and action is visible
if (isPaymentApproved) {
  sendOrderConfirmation(order);
}

第二個範例中的名稱仍應依實際情境調整。重點是讓讀者不必跑到很遠的地方查找 ab 的定義,就能理解重要的判斷。與註解重複名稱已說明內容的結構相比,讓名稱和程式碼組成自行說明,可降低變更後說明內容過時的風險。

函式和結構應該拆分到什麼程度?

當一個函式或模組做了太多事情,讀者就必須同時在腦中保留多項規則。若輸入驗證、計算、外部呼叫、錯誤處理和結果格式化混在同一個區塊中,變更其中一部分時可能必須理解整個流程。將相關步驟分成具名單位,能讓高層次流程更容易閱讀。

例如,訂單確認流程可以表達為 validateOrdercalculateTotalreserveInventorycreatePayment 等步驟,這些名稱展現了業務流程。拆分的目的不是增加函式數量,而是讓每個步驟的職責和順序更容易閱讀。若抽出的函式只有一行,且它的名稱不如原始運算式清楚,就很難說抽取改善了理解性。

過度拆分會造成相反的問題。讀者可能必須在許多檔案與單薄函式之間不斷跳轉,才能理解一項動作。介面或型別等抽象化具有隱藏實作細節的優點,但也可能隱藏所需的上下文。應在抽象化帶來明確效益時使用它,而不是基於「抽象化越多,設計就一定越好」的假設套用。google.github.io

因此,是否拆分可以用以下問題來判斷:

  • 這個部分是否有可獨立說明的角色?
  • 它的名稱是否比閱讀內部程式碼更能說明意圖?
  • 相同規則是否在多個地方重複出現,因而有理由集中在一處?
  • 它是否建立了邊界,使變更時只需檢查這個部分?
  • 拆分後,追蹤呼叫是否反而讓整體流程更不清楚?

這些問題不會自動產生答案,但它們將焦點放在讀者理解程式碼的實際成本,而不是「函式要短」這類表面規則。

簡單是否等於功能較少?

在 Clean Code 中,簡單不代表放棄必要功能。它更接近於避免目前需求不需要的不必要結構、未使用的擴充點與難以理解的繞路。若只根據對未來需求的猜測進行泛化,現在的讀者就必須理解尚不存在的情況。

例如,對於只有一種付款方式的小型功能,事先建立多層外掛系統可能為未來擴充預留空間,但也會增加當下必須測試的程式路徑、設定與組合。反之,若新增付款方式已經確認,且其規則有重大差異,建立共同邊界可能減少未來變更。沒有任何一種選擇能事先永遠更好。

簡單也不代表「程式碼行數最少」。把多個條件與轉換壓縮成一行,對撰寫者來說可能很巧妙,但修改者必須解讀優先順序與例外情況。相反地,使用命名恰當的中間值並分開條件,雖然增加行數,卻能簡化推理過程。程式碼審查指引也強調,未來的開發者應能夠閱讀、理解與修改程式碼。google.github.io

實務上,適合同時考量兩種簡單性。第一種是實作本身的簡單性:是否有很少不必要的狀態、分支、依賴和重複。第二種是使用與變更的簡單性:呼叫端是否容易正確使用,以及規則變更時修改位置是否清楚。即使內部稍微更複雜,讓外部使用變簡單的選擇有時可能更好。

為什麼需要一致的風格,而它又為什麼還不夠?

當縮排、換行、檔案組織和命名慣例都各不相同時,讀者每次都必須解讀格式。持續採用團隊共同約定的風格,可以減少花在程式碼表面差異上的注意力。自動格式化工具和 linter 等可機械式檢查規則的工具,對這類重複性工作尤其有用。

不過,只遵循風格並不會讓程式碼自動變乾淨。即使每個名稱都遵循相同慣例,角色仍可能含糊;即使行長度正確,設計仍可能過度糾結。程式碼品質審查認為,除了風格外,還應考量設計、功能、複雜度、測試與文件。google.github.io

套用風格規則時,尊重團隊既有慣例通常較為實際。只在一個新檔案中嘗試偏好的記法看似微不足道,卻可能削弱整個專案的一致性。反過來說,若改善能顯著提高清晰度,也可以討論並變更既有慣例。重要的不是競爭哪條規則更優雅,而是團隊能否一致地閱讀與修改程式碼。

程式碼審查也必須區分細微的偏好差異,以及影響可維護性的問題。要求每次變更都完美,可能讓改善本身變慢。若某項變更整體提升可維護性、可讀性和可理解性,漸進式接受它可能更務實。google.github.io

測試和 Clean Code 有什麼關係?

測試是驗證程式碼所承諾行為的可執行手段。這裡的承諾是指可觀察到的行為,例如「只有有效訂單會付款」、「已取消的訂單不會再次取消」,或「符合折扣條件時會扣除指定金額」。測試提供了檢查變更後關鍵行為是否遭破壞的依據。

若只把 Clean Code 視為看起來好看的程式碼,測試似乎會與它分離。但在包含安全修改的定義下,測試是核心。在結構整理期間,需要能確認外部行為是否被保留;新增規則時,則需要檢查舊規則是否意外遭到破壞。可維護的程式碼應有能驗證核心邏輯與承諾行為,並協助識別失敗原因的測試。google.github.io

僅有很多測試並不能保證品質。與微小內部順序耦合過緊的測試,可能讓合理的結構改善也難以進行。反之,即使測試數量很多,若漏掉重要的邊界條件和業務規則,也可能無法充分提升變更安全性。測試名稱和 arrange-act-assert 結構也應清楚撰寫,讓讀者知道哪些內容獲得保證。

例如,若邏輯計算退款資格期間,與其只檢查一般日期,更有意義的是測試實際規則的邊界,例如截止日當天、截止日剛過,以及缺少輸入時的情況。要測試哪些案例取決於產品需求和風險。關鍵在於讓測試傳達的不只是「程式碼存在」,而是「哪些行為必須持續被保留」。

何時需要註解和文件?

註解並不壞。當需要傳達程式碼難以表達的背景時,註解特別有價值。例如,外部服務異常行為的因應方式、法律或合約限制、根據效能量測做出的選擇,或是在特定日期後會移除的暫時性相容程式碼,其原因都可能無法僅靠名稱充分傳達。這些資訊能幫助未來維護者理解,為何不應將它替換成看似較簡單的做法。google.github.io

反過來說,只是翻譯程式碼已表達內容的註解,會隨著時間逐漸與程式碼脫節。在 count = count + 1 旁寫著「將 count 加 1」的註解,不會增加新資訊。這種情況下,更好的名稱或更直接的結構應優先處理。註解越長,就越值得檢查它是否代表程式碼意圖不清。

文件的適當位置也可能不同。函式內的局部原因適合寫成附近的註解。多個模組共用的使用規則、設定方法與相容條件,可能更容易在獨立文件或介面說明中找到。不論放在哪裡,重點都是提供讀者作決策所需的上下文,並在程式碼變更時一併更新。

Clean Code、重構與程式碼風格有何不同?

這三個詞經常一起被提到,但它們扮演不同角色。Clean Code 是以容易理解和變更的程式碼為目標的品質狀態或觀點。重構是在保留外部可觀察行為的同時改善內部結構的活動。程式碼風格則是程式碼表達的慣例,例如縮排、命名記法和空白。

類別關鍵問題範圍
Clean Code這段程式碼能否被理解並安全地修改?命名、結構、複雜度、測試、文件、一致性
重構如何在保留行為的同時改善結構?改善結構的活動
程式碼風格團隊以何種格式表達程式碼?記法和格式的慣例

重構是建立或維持 Clean Code 的一種方法。例如,可以將重複的價格計算集中到一處、修改模糊的名稱,並將條件整理成更容易理解的單位。但若沒有確認行為是否被保留就進行結構變更,可能帶來風險,因此測試和審查很重要。

風格可減少協作摩擦,但不會自動解決設計問題。反過來說,結構清楚且能正常運作的程式碼,也不會只因風格稍有不同就自動變差。理解這項差異,能降低在審查中將格式問題和真正維護風險賦予同等權重的錯誤。google.github.io

有效能與安全性限制時,應優先考量什麼?

Clean Code 對簡單與清晰的重視,不代表要犧牲效能、安全性、相容性或營運可靠性。例如,效能所需的快取、安全所需的驗證步驟,或舊外部系統所需的相容性處理,都可能讓程式碼更複雜。若這些複雜度基於真實需求和量測結果,就可能比只是看起來較簡單的替代方案更恰當。

在這種情況下,重要的態度不是隱藏複雜度。可透過命名、結構、測試和必要註解,讓限制、必須保證的行為,以及不採用慣用實作的理由清楚可見。在這些決策中,應優先考量技術事實與資料,而不是個人偏好。google.github.io

例如,若容易閱讀的實作無法滿足真實正式環境中的回應需求,就有理由選擇更複雜的實作。但也不應只因認為「是為了效能」就讓所有程式碼變得複雜。應在量測問題並確認需求後,同時比較複雜度的成本與效益。

安全性也是如此。輸入驗證、授權檢查和錯誤處理等步驟,可能讓程式碼流程變長。這不代表為了讓程式碼變短就可以省略它們。良好的結構會將這些必要步驟放在容易辨識的位置,並協助避免敏感規則任意散落在整個程式碼庫中。

對 Clean Code 常見的誤解有哪些?

第一個誤解是「越短越好」。短函式和簡潔運算式可能有所幫助,但行數不是判準。過度拆分和抽象化可能拉長呼叫路徑並隱藏上下文。與其問程式碼是否變短,不如問讀者是否更容易理解主要流程及其理由。google.github.io

第二個誤解是「註解越少越好」。透過名稱和結構來表達程式碼能自行說明的內容,並不代表移除有用的背景資訊。特別是選擇的理由與外部限制,可能需要保留在註解或文件中。好的註解不會重複程式碼,而是提供僅憑程式碼難以得知的上下文。google.github.io

第三個誤解是「只有遵循每條規則的程式碼才是好的」。建議是判斷工具,不是適用於所有情況的法典。優先順序會依程式語言特性、既有專案慣例、效能與安全需求,以及團隊經驗而不同。更重要的是確認套用規則是否真的讓程式碼更清楚。

第四個誤解是「設計必須從一開始就完美」。需求會改變,而且有些資訊一開始無法得知。與其只為追求完美而延後變更,不如持續進行小幅改善,讓目前系統整體更容易閱讀和維護,這樣更實際。google.github.io

實務上如何判斷 Clean Code?

很難只靠絕對的檢查清單判斷,但面對變更時可以提出幾個問題。首先,思考第一次看到程式碼的人能否說明它的主要目的。接著,變更一項規則時,確認修改位置是否相對清楚,或是否也必須變更無關區域。最後,確認變更後是否有測試或審查方法可以驗證核心行為。

撰寫或審查功能時,可使用以下實務問題:

  • 是否只從名稱就能大致理解某個值、函式或模組的角色?
  • 一個函式是否不必要地混合不同的業務規則或外部操作?
  • 相同的重要規則是否被複製在多個地方?
  • 它是否自然符合團隊對命名、格式和檔案組織的慣例?
  • 程式碼無法表達的選擇理由或限制,是否已視需要記錄?
  • 是否有方法能驗證核心行為與高風險邊界條件?
  • 簡化是否忽略了效能、安全性或相容性需求?
  • 抽象化或拆分是否確實降低理解成本,還是只拉長讀者必須追蹤的路徑?

不需要立即回答所有問題。在小型變更中試圖解決每個設計問題,可能使審查停滯。較實際的作法是先修正影響較大的問題,再於後續變更中讓其朝更好的方向發展。程式碼審查的目標,也可以是持續改善系統的可維護性、可讀性和可理解性,而不是產出完美的程式碼。google.github.io

結論:Clean Code 是為變更而生的品質,而非固定格式

Clean Code 不只是特定書籍中的規則清單或整齊的格式。它是一種品質觀點:透過名稱和結構讓程式碼意圖清楚可見、減少不必要的複雜度、讓團隊能一致地閱讀,並能在變更後驗證行為。註解用於傳達背景,測試支援變更安全性,而抽象化則應在確實讓理解和變更更容易時使用。

好的程式碼樣貌會因專案而異。重要的不是它看起來是否簡短,或是否遵循知名規則,而是在目前需求與限制下,下一位開發者能否正確理解並變更它。持續從這個觀點改善細小的命名、條件、測試與結構,就是 Clean Code 的實務起點。google.github.iogoogle.github.io

常見問題

Clean Code 可以用固定公式或分數來評估嗎?

不行。Clean Code 並非單一的國際標準或量測公式,而是一種實務上的品質觀點,目標是提升可理解性、可維護性、一致性與變更安全性。正確的選擇會因專案使用的語言、團隊和營運限制而有所不同。

程式碼只要短就一定是 Clean Code 嗎?

不一定。短程式碼有時能讓意圖更清楚,但過度壓縮、拆分或抽象化可能隱藏上下文與執行流程,反而使程式碼更難閱讀。重要的標準不是行數,而是讀者能否理解其意圖並安全地修改程式碼。

註解很多是否代表程式碼品質很高?

不一定。能透過命名與結構表達的行為,通常最好由程式碼本身說明。不過,對於難以從程式碼單獨推知的背景資訊,例如決策理由、外部限制或無法避免的例外情況,註解仍然很有價值。

Clean Code 和重構是一樣的事嗎?

兩者並不相同。Clean Code 是指程式碼容易理解與維護的狀態;重構則是在保留外部可觀察行為的前提下改善內部結構的活動。因此,重構可以是朝向更乾淨程式碼前進的一種方式。

如果為了效能需要複雜的程式碼,就必須放棄 Clean Code 原則嗎?

不必。效能、安全性、相容性或營運條件真正需要的複雜度,可能是必要的。與其因為較簡單的做法看起來更乾淨而忽略需求,不如根據量測結果與技術證據選擇複雜度,並讓其理由清楚可見。