# Clean Code ## 為什麼需要學習 Clean Code? * 現今藉由 AI 生成的程式碼越來越多,越來越多工程師直接將可以工作的程式碼拼貼進專案裡面。 * 短期內並不會有任何問題,程式 **"寫"** 的特別快,而且覺得原來寫程式這麼簡單。 * 等到專案大到一個程度,開始發現很多疑難雜症的時候已經來不及了。 * 就像 [Chapter 1](https://hackmd.io/@stanley890314/B1ot5l_7le) 提到的案例,需要開始進行重構。 * 即使退一百步來說「未來的工程師都不再需要實際動手寫程式」,但還是需要有 **「辨別程式碼優劣」** 以及 **「交付易維護程式碼」** 的能力。 * 閱讀 Clean Code 正是讓你能夠擁有這些能力的途徑之一。 ## Keynote * **Clean Code 並不是要程式碼很短、過度簡潔。而是希望程式碼能夠好維護,節省工程師額外的處理時間!** * **沒有人第一次就寫出完美的 function,「先寫完,再修整,直到它變好。」** ## 目錄 * [Clean Code Foreword](https://hackmd.io/@stanley890314/B1QByeuXxl) ::: spoiler 大綱 * 維護是軟體開發的核心工作 超過 80% 的軟體工作是維護,而非開發。 * 5S 原則是整潔程式碼的靈感來源 整理(Seiri)、整頓(Seiton)、清掃(Seiso)、標準化(Seiketsu)、自律(Shitsuke)——這些工業管理準則也適用於程式碼品質。 * 好的命名是整潔的起點 * 命名變數如同命名孩子,需謹慎考慮。 * 乾淨的程式碼需要紀律與自省 * 經常檢查與調整自己的程式風格與寫作習慣。 * 程式碼是永遠未完成的作品 * 如詩如設計,應持續修正、改善,不可放棄。 * 整潔的程式碼是高效協作的基礎 * 雖然耗時,但能減少後續維護成本,提升團隊效率。 * 從零開始有時是必要的重構 * 定期重建軟體系統,以清除累積的技術債。 ::: * [Clean Code Introduction](https://hackmd.io/@stanley890314/SJeROldQel) ::: spoiler 大綱 * 小事誠實,才能寫出值得信賴的程式碼 * 乾淨的程式碼從細節做起,命名、格式、排列都是誠實的體現。 * 寫好程式不只是知識,更是工藝 * 理解原則與模式只是開始,真正的功夫來自大量練習與反覆失敗中的累積。 * 維護佔據軟體開發的大多數時間 * 整潔的程式碼能大幅減少除錯與維護的負擔,是對未來的投資。 * 5S 精神指引我們組織、清理與自律 * 來自製造業的整理與標準化原則,正好對應程式碼的品質與可讀性。 * 學會辨識與改善程式碼異味,是持續進步的關鍵 * 案例研究與實戰清理,幫助我們培養「看見問題」與「正確改進」的直覺。 ::: * [Clean Code Chapter 1 - Clean Code](https://hackmd.io/@stanley890314/B1ot5l_7le) ::: spoiler 大綱 * 程式碼不會消失,它是需求的最終表達 * 無論技術如何進步,我們永遠需要精確、清楚、可執行的程式碼來實現* 壞程式碼會拖垮專案甚至整個公司 * 趕時間、不整理的程式碼最終會導致生產力衰退、進度遲滯,甚至專案死亡。 * 乾淨的程式碼是快速完成工作的唯一方法 * 越髒亂,越慢;只有保持整潔,才能持續交付與維護。 * 專業態度意味著即使在壓力下也堅持整潔 * 就像醫生不能不洗手,程式設計師也不能為了趕工妥協品質。 * 整潔程式碼是藝術,也是紀律與直覺的結晶 * 它需要不斷練習與培養「code-sense」,才能在混亂中看見秩序、做出正確轉換。 ::: * [Clean Code Chapter 2 - Meaningful Names](https://hackmd.io/@stanley890314/HkerQWuQlx) ::: spoiler 大綱 * 名稱應該直接表達意圖 * 好的名稱應明確傳達其用途與含義,讓讀者不用猜測即可理解它的目的與使用方式。 * 避免誤導與假資訊 * 不要使用模糊、誤導或與既有術語衝突的名稱(如 list、l、O 等),否則容易造成混淆。 * 讓名稱有意義,而不是無用的序列 * 命名應反映資料的實際角色,例如將 a1, a2 改成 source, destination,讓意圖一目了然。 * 使用容易發音的名稱 * 命名應符合語言可交流性,方便團隊溝通與討論,程式設計是社交活動的一部分。 * 好名稱需要勇氣與持續改善的態度 * 不要害怕重命名,只要能提升清晰度,團隊會感激你的用心。 ::: * [Clean Code Chapter 3 - Funtions](https://hackmd.io/@stanley890314/H1QdxH-Vlx) :::spoiler 大綱 * 函式應該小,還要更小 幾乎所有函式都應限制在 2–20 行內,越短越清楚。長函式難以維護與理解。 * 每個函式只做一件事 如果你能將函式進一步拆解成更小的函式,就代表它不只做一件事;應確保單一責任與清晰焦點。 * 保持一致的抽象層級 一個函式內部不應同時處理高層流程與低層細節,應將不同層級的邏輯分離成不同函式。 * 少參數,零或一個最好 函式參數越少越容易理解、測試與使用;三個以上幾乎都應重構或封裝為物件。 * 避免副作用與命令查詢混用 函式要麼執行行動(command),要麼回答問題(query),避免同時做兩件事;保持行為可預測、可測試。 ::: * [Clean Code Chapter 4 - Comments](https://hackmd.io/@stanley890314/HkrdzTo4xg) :::spoiler 大綱 * 註解不是美德,是無奈之舉 * 如果程式能清楚表達意圖,就不需要註解;每一行註解都是對表達失敗的補救。 * 能用好命名與結構說明的,就別用註解 * 函式與變數命名清楚,小函式結構良好,比任何註解都清楚且可靠。 * 註解容易過時,甚至誤導 * 程式碼會變動,註解若未同步更新,反而會傳遞錯誤資訊,製造地雷。 * 好的註解聚焦於設計意圖與不可避免的說明 * 像是法規需求、使用第三方黑盒 API、警告效能/副作用等情境才需要註解。 * 壞註解比沒註解更糟,能刪就刪、能改寫就重構 * 模糊的、重複的、誤導的、過時的註解都應果斷移除,並以乾淨程式碼取代。 ::: * [Clean Code Chapter 5 - Formatting](https://hackmd.io/@stanley890314/HJ82Cpj4xx) :::spoiler 大綱 * 排版是團隊的語言,不只是美學 * 清楚、統一的排版讓程式碼在 30 秒內被理解,是良好協作的基礎。 * 垂直排版:高層在上、細節在下,像報紙一樣好讀 * 把重要邏輯放上面、細節往下放,呼叫者在上、被呼叫在下,幫助閱讀流程自然展開。 * 水平排版:簡潔有序,避免過長與過度對齊 * 行寬盡量 < 120 字,適當留白提升可讀性,不要為了對齊犧牲維護性。 * 結構層次清楚,縮排不混亂 * 每層邏輯明確分層,每層縮排 4 空格,避免巢狀過深與濫用單行控制結構。 * 全團統一格式 > 個人喜好,靠工具自動化 * 使用 formatter 工具,寫好規則一次套用,讓整體風格乾淨、一致、省爭議。 ::: * [Clean Code Chapter 6 - Objects and Data Structures](https://hackmd.io/@stanley890314/rypTRzpNgg) :::spoiler 大綱 * 資料應抽象化,物件應隱藏實作細節 * 不應直接暴露欄位,而應提供有意義的操作介面,讓使用者無需關心內部資料格式(如 x,y vs r,θ)。 * 避免只包裝欄位的 Getter/Setter * Getter/Setter 若無封裝邏輯,等同暴露內部結構,是假封裝、真洩漏。 * 資料導向 vs 物件導向有不同優劣,依據變化方向選擇 * 程序式易於「新增行為」,OO(Object-Oriented) 易於「新增型別」;先看你系統哪邊比較常變動。 * Demeter 法則提醒你減少耦合與鏈式依賴 * 跟朋友說話,不跟朋友的朋友說話;不要有太多「.接龍」,應該將責任往物件封裝。 * 保持資料純粹:DTO 與 Active Record 不要混入商業邏輯 * DTO 僅作資料傳遞、Active Record 只處理資料存取,規則與邏輯應交由 Service 處理,維持關注點分離。 ::: * [Clean Code Chapter 7 - Error Handling](https://hackmd.io/@stanley890314/B1tmre6Hgx) :::spoiler 大綱 * 用拋例外取代回傳錯誤碼,讓失敗自動中斷流程 * 把錯誤交給 try/except 處理,讓業務邏輯不被 if 淹沒,並強制處理失敗狀況。 * 先寫 try/except/finally 區塊,再填內容 * 思考「失敗怎麼辦」再寫邏輯,能保持一致性,也讓 stub 更容易測試與擴充。 * 定義自己的錯誤類別,避免高耦合外部 SDK * 包裝第三方例外為統一錯誤,讓呼叫端乾淨、測試簡單、替換無痛。 * 讓主要流程線性易讀,錯誤邏輯抽出另處理 * 使用 Special Case 或預設物件避免主流程塞滿例外情況,讓程式像講故事一樣清楚。 * 避免回傳或傳遞 None,改用預設值或直接拋例外 * None 是 bug 的溫床,應以明確結構或例外代替,提升可預測性與穩定性。 ::: * [Clean Code Chapter 8 - Boundaries](https://hackmd.io/@stanley890314/Hyd7vl6Sgl) :::spoiler 大綱 * 不要把第三方介面亂丟,請包成自己的介面 * 不要讓整個程式直接使用 dict / Map / SDK API,把它們包在 Registry、Wrapper 或 Adapter 裡,只讓外界用你的 API。 * 先寫 Learning Test 探索陌生套件,還能做為升級驗證用 * 把「摸索 API」過程寫成測試,未來升級跑一次就知道有沒有踩雷,學習與保險一舉兩得。 * 遇到還沒實作的系統,先寫出你希望它有的介面 * 先寫 Protocol 或抽象介面開發上層,日後再實作或接上 Adapter;這讓你能提早動工、不受卡關。 * 保持邊界乾淨、集中處理依賴點 * 只有少數幾個地方知道外部世界,其他部分只依賴你自家封裝的物件與方法,升級或替換更輕鬆。 * 依賴你能控制的東西,否則你會被反過來牽著走 * 外部世界會變,你無法阻止;但你可以控制自己的介面與邊界,讓自己更有彈性、少踩坑。 ::: * [Clean Code Chapter 9 - Unit Tests](https://hackmd.io/@stanley890314/B1t1aGU8gl) :::spoiler 大綱 * 測試品質 = 程式品質,測試也要「乾淨」。 * 測試碼跟 production code 一樣重要。髒測試會讓團隊放棄測試,進而讓整個系統腐敗。 * TDD 三定律:先寫測試,先讓測試失敗,只寫剛好能通過的 code。 * 用快速迭代建立防護網,讓程式可重構、可放心演進。 * 善用測試 DSL,讓測試「像故事一樣」好讀。 * 抽出 _make_pages()、self.server.request() 等幫助理解意圖,而不是堆疊細節。 * 測試可以在效能上偷懶,但「可讀性」不能妥協。 * 效能是 production code 的事,測試應追求直觀、清晰,讓「失敗時知道哪裡壞了」。 * 遵守 F.I.R.S.T.:Fast、Independent、Repeatable、Self-validating、Timely。 * 測試要快、獨立、可重跑、能自動驗證,並且盡可能在寫 code 前完成。 ::: * [Clean Code Chapter 10 - Classes](https://hackmd.io/@stanley890314/HJ6DD78Ill) :::spoiler 大綱 * 類別要小,職責要單一(SRP)。 * 一個類別只應有「一個改變的理由」。別讓類別同時處理 GUI、版本、資料邏輯等不同責任。 * 高內聚,低耦合。 * 類別內的方法應操作同一批成員變數,保持內聚。當變數和方法只屬於某一功能時,應拆成獨立類別。 * 類別設計為「變更而組織」,遵守開放封閉原則(OCP)。 * 當需求變動(如新增 SQL 語法),用繼承或策略模式擴充,不要改舊有類別,降低風險。 * 善用封裝,畫好邊界。 * 將實作細節封裝起來,不讓外界直接碰內部欄位。只有在測試或必要時,才稍微放寬存取限制。 * 依賴抽象,隔離變動(DIP)。 * 高層模組依賴介面,不依賴實作。透過 Protocol 或抽象類別,減少外部變動對系統的衝擊。 ::: * [Clean Code Chapter 11 - Systems](https://hackmd.io/@stanley890314/HJ6DD78Ill) * [Clean Code Chapter 12 - Emergence](https://hackmd.io/@stanley890314/HJ6DD78Ill) # Clean Code 寫的都是 Bullshxt!!! 如果對於 Clean Code 的內容有疑問,個人強烈推薦去看 [A Philosophy of Software Design vs Clean Code](https://github.com/johnousterhout/aposd-vs-clean-code)。 裡面有 [A Philosophy of Software Design](https://www.amazon.com/Philosophy-Software-Design-John-Ousterhout/dp/1732102201) 作者 John 與 UB(Uncle Bob)的討論。 針對 [Comments](https://hackmd.io/@stanley890314/HkrdzTo4xg)、[Do One Thing](https://hackmd.io/@stanley890314/H1QdxH-Vlx#Do-One-Thing-%E2%80%94-%E4%B8%80%E6%AC%A1%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E4%BA%8B) 和 [TDD](https://hackmd.io/@stanley890314/B1tmre6Hgx#%E5%85%88%E5%AF%AB-trynbspnbspexceptnbspnbspfinally-%E5%8D%80%E5%A1%8A%E5%86%8D%E5%A1%AB%E5%85%A7%E5%AE%B9---Write-Your-Try-Catch-Finally-Statement-First) 等理念有所衝突的部分進行深度的討論。 [It's probably time to stop recommending Clean Code](https://www.reddit.com/r/programming/comments/qs8j0z/its_probably_time_to_stop_recommending_clean_code/),這是他的[觀點](https://qntm.org/clean) 另外 Reddit 上也有人對於 Clean Code 有強烈的反感,我覺得都是很有趣的觀點,提供大家討論。 我自己是贊同 Clean Code 部分觀點的,但他提出的許多看法也很有道理。 * 尤其是 Clean Code 中有許多自相矛盾的"建議"。 * 例如許多規範與第三章末端的範例有矛盾。(作者也承認在 Clean Code 中有很多現在來看很多能改進的地方,[未來可能會有 Clean Code 2nd Edition](https://www.reddit.com/r/programming/comments/1eo2lo5/uncle_bob_martin_i_am_in_the_midst_of_writing_the/)) # 暫時結論 * Clean Code 講的不是聖經,裡面有許多規範都與實務上有很多衝突。 * 適當的規範能夠增加團隊效率,但誤解"Clean Code"的定義則會嚴重影響效率。 * Clean Code 最大的問題是將 **"建議"** 升格為 **"標準"**。 * 事實上這些很多建議都是好的,但絕對不是無時無刻都該遵守的規則。 * 註解需要認真看,如果有不正確或是過時的註解需要修改,不能進 Merge。 * 部分 API 如果合適可以考慮分開,盡量避免出現過於強大的 API 包山包海。 * 只要這個 API 掛了整個系統就完了。 # 需要釐清的點 ## 函式太多會不好維護嗎? |問題點|何時「取多函式」有益|何時真的會變負擔|如何折衷| |---|---|---|---| |認知負荷|每個函式都單一責任+好名字時,你一次只要理解一層抽象;IDE/搜尋也能精準跳轉。|名稱模糊 (process_data, handle_user) 或動機不明(為了湊篇幅把 2 行抽出去)時,讀者要不停點進去才知道在做什麼。|確保函式名稱=業務語意;若只是瑣碎工具,放在同一區域 (nested function、私有方法) 減少跳轉。| |變動範圍|修改查詢欄位、演算法時,只動那個函式;測試可聚焦、影響面小。|抽象層次混亂──高階函式又呼叫另一堆「薄包裝」,改一行得追呼叫鍊,debug 困難。|每層只對齊一種語言: UI、Service、Repository… 不要在同一檔案混用。| |程式碼組織|函式多但依領域拆檔(例如 user_repository.py、order_service.py),導航直觀。|全塞在 `utils.py` 或 `helpers.py`,導致「萬能工具箱」,難找又易衝突。|依bounded context 拆模組;工具函式若僅服務單一檔,放在檔尾 _helper() 即可。| ## 如何判斷「要不要抽函式」 1. 意圖清晰度優先 * 只要函式命名能把「為什麼存在」說清楚,就算只包住 5 行也值得。 * 若抽完名字只剩 do_something_specific 這種無資訊詞,就先保留行內。 2. 變更頻率與範圍 * 會改動的點(SQL 欄位、第三方 API)→ 抽函式/類別以局部化影響。 * 高度穩定且只用一次 → 保留原地,加上 WHY 註解即可。 3. 抽象層次不要交錯 * 在完整 Workflow (Main Function) 中只呼叫「商業函式」,不要直接塞細節邏輯。 * 讓每層都可用一句話描述它做的事:「Service 組合 Repository、Repository 包 SQL…」。 # 什麼是 [KISS](https://www.interaction-design.org/literature/topics/keep-it-simple-stupid?srsltid=AfmBOoqD_hv5sZX4gdXO1nobKg-wdZnPewPiuaTvqEvON3VWbp5u_0Ow)? KISS 是 “Keep It Simple, Stupid” 的縮寫,意思是「保持簡單,傻孩子」。這是一個設計和工程領域的經典原則,強調: * 簡單易懂的設計比複雜設計更好 * 不要為了炫技或過度抽象而犧牲可讀性與維護性 > KISS 強調的核心思想是:簡單就是美,避免讓設計或程式碼過於複雜、難以理解,這樣更有助於後續的維護與團隊協作。 ## KISS vs Clean Code 某部分人的觀點與 Clean Code 並不完全相符,並且會拿 KISS 來反駁 Clean Code 的一些觀點: * 程式碼應該足夠簡單且容易理解,不應該將程式碼寫的文謅謅,某些時候應該 **適當的冗贅** 會讓程式碼更容易閱讀。 * 實際上 Clean Code 也提到多次 **清楚表達** 比 **精簡 Code** 還要重要。 * 按照 Clean Code 說的避免重複自己、Small! 等規範來精簡程式碼會造成程式碼難以閱讀。 * 其實 Clean Code 說的規範是環環相扣的,不是只是一味的細化或是減少重複的 Code。 * 更重要的是同時應該 ***[清楚命名](https://hackmd.io/@stanley890314/H1QdxH-Vlx#Use-Descriptive-Names-%E2%80%94-%E7%94%A8%E8%83%BD%E8%A1%A8%E9%81%94%E6%84%8F%E5%9C%96%E7%9A%84%E5%A5%BD%E5%90%8D%E5%AD%97)*** 、 ***[避免過度嵌套](https://hackmd.io/@stanley890314/H1QdxH-Vlx#Switch-Statements-%E2%80%94-%E6%8A%8A-switchif-else-%E8%97%8F%E8%B5%B7%E4%BE%86%EF%BC%81)*** 和 ***[混用不同層級函式](https://hackmd.io/@stanley890314/H1QdxH-Vlx#One-Level-of-Abstraction-per-Function-%E2%80%94-%E4%B8%80%E8%87%B4%E7%9A%84%E6%8A%BD%E8%B1%A1%E5%B1%A4%E7%B4%9A)*** 讓程式碼更清晰易懂。 實際上 Clean Code 與 KISS 追求的目標是相同的。 * 程式碼應該足夠愚笨,讓小孩都看的懂。(KISS) * 程式碼應該精簡、細化、重用重複的功能、命名精確。(Clean Code) > **當你真正按照 Clean Code 的精神來撰寫程式時,你也同時實現了 KISS 的理念,使你的程式碼更容易讓人一目了然。**
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.