# 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
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up