因為公司有這本書的中文版,在各種等待時間,慢慢把這本看完了,同時也一邊寫些自己認為的重點。
其實我覺得 Clean Code 適合的閱讀對象,是從來沒有接觸過任何 Coding Style 的人。
如果你跟我一樣,有和別人合作過,平常有遵守一些準則(例如 PEP8),或是使用包含 Styling 的 linter (pylint / eslint / rubocop …),這本書還是能找到能學習的地方,例如命名原則、封裝的部分、模組的拆分、測試撰寫等等,這些開發準則以及自動化的 formatter 沒辦法告訴你的部分。
也難怪第一次聽到別人推薦大家看這本書的時候,把它歸類在「選讀」,卻又說裡面的內容很重要。因為很多觀念在現在已經是日常了。
比較後面的章節,從系統、平行化開始,討論了一些比較深入的知識,Java code 也開始變難懂了,而持續精鍊、JUnit 等章節,就真的是針對 Java 的實例做說明,而非原則介紹的部分,這些對我來說讀起來就比較吃力一點。而平常會聽到大家討論的,也都是前面章節討論一些基本原則的部分。因此對於時間比較少的人或許可以先點到為止。
我看網路上的心得也很少人真的讀完後面 XD
列舉一下我覺得比較重要的章節:
- Chp. 1 ~ Chp.10
- Chp. 12 簡單的總結
- Chp. 17 一個清單,其實包含了 Chp. 14-16 的一些啟發
我認為重要順序是倒著的,完全沒時間可以先看 Chp. 17
另外 Chp. 11 其實也蠻重要的,但是裡面太多 Java 的部分了,如果以後學 Java 再來細看吧。
大概是這樣,下面就附上我紀錄的每章節重點吧。
前言
Clean code 是一門學派,不是絕對正確
減少閱讀程式碼的時間
雜亂程式會拖累開發時間
讓程式清楚明白,不要隱藏意圖,避免 anti-pattern
童子軍規則:離開營地前,讓營地比使用前更乾淨
變數命名
- 類別:名詞,方法:動詞
- 避免無意義的字:the, variable, name
- 避免跟形態衝突的字(會誤導),除非他真的是那個形態:list, array
- 或是省去:
Accounts
>AccountList
- 或是省去:
- 能搜尋
- 能唸出來
- 意義區別
- e.g.
source
,destination
取代a1
,a2
- e.g.
- 同一概念統一字詞
- 在命名內加入 context,或是用 Class 包起來
善用詞性
類別:名詞
方法:動詞
函式
- 多型 > switch case
- 參數越少越好
- 可以把參數包在類別裡,例如
point
取代x, y
- 可以把參數包在類別裡,例如
- No side effect
- 不要做名字以外的事情
- 讓物件改變自己,而不是讓函式輸入物件
- 查改分離(Commend / Query 分離)
- 用例外處理取代 if 回傳錯誤碼
- try…finally block 應該獨佔函式(就是一件事)
Single Responsibility Principle, SRP 單一職責原則
一個類別或一個模組,應該只有一個讓你去修改他的理由(一種職責)
要能有簡潔的命名,否則就該拆開成兩個類別 / 兩個模組
- 只有一個層次的抽象概念
- 不要傳 flag(true/false)進函數讓他做兩件事
Open Closed Principle, OCP 開放閉合原則
要設計得易於延展(例如:繼承),不用因新型態、新需求加入而改變原本的部分
對擴展開放,對修改封閉
註解
彌補程式碼表達意圖的失敗
註解容易隨著時間失真,常常會沒有跟著程式碼一起改到(只有程式碼是真的)
-> 透過修改程式碼來移除註解
使用 git 等版本控制系統後,就不需要的註解:版本日誌 / 程式碼修改 / 作者等資訊
註解應該最少,只留必要的註解
- 法律型註解
- 資訊型註解
- function doc(產生文件的註解)
- formatter 會挑出的格式
- 意圖的解釋
- 一段複雜 / magic code 是為了解決什麼
- 後果告誡
- e.g. 為什麼不跑這個測試
- thread unsafety
- TODO
- 放大重要性:"下面這行很重要,因為…"
編排
寫 code,像編排一頁報紙
Vertical
- 空行分隔:package / class / function
- 關聯的 code 要連續(script / function …)
- 互相呼叫 / 引用
- Concept 相近
- local variable 宣告盡可能靠近使用的地方
- Instance variable 宣告在最上方 (也可以依照 C++ 的 Scissors rule 放在最下方)
Horizontal
- 寬度 ~100-120 以下,以不要捲動畫面為主
- assignment 兩端空白
user_name = 'david'
- 用空白來強調運算子的優先序
return b*b - 4*a*c
- 不要水平對齊,會需要水平對齊代表列表太長
物件與資料結構
- OOP 容易添加新的類別(duck-typing)
- Struct 容易添加新的 function
The Law of Demeter / Principle of Least Knowledge
Loose coupling
Abstraction: 模組不該知道它所操作的物件的內部運作
火車事故 (train wreck)
一連串的連續呼叫,對於物件 get_options
內部理解太深
output_dir = obj.get_options().get_scratch_dir().get_absolute_path()
Clean Code 認為違反了 Law of Demeter,以下列方式分割:
opt = obj.get_options()
scratch_dir = opt.get_scratch_dir()
output_dir = scratch_dir.get_absolute_path()
資料傳輸物件 Data Transfer Objects (DTO)
只有 public variable
沒有 method
e.g. active record
作者還批評了一般 Model 寫法,說應該要把 Bussiness rule 和 DTO 分開 XD
錯誤處理
- 提供 error message
- 從 caller 的角度,定義 Exception 的類別
- 利用特殊類別配合 duck typing 來處理特殊情況,不用總是用
try
…catch
Return Null?
盡量避免,改成:
- empty array for iteration
- 在函式內部做 assertion 來避免會造成 null 的結果
Interface
你的 code 和 3rd-party 的 code 的 interface
最小化會依賴於 3rd-party 的部分
Adapter
- 把 3rd-party 封裝起來,不要直接在 code 內傳遞他
Unit Test
測試也要好維護
讓你的測試變得很好閱讀:封裝細節
Test-Driven Delopment
三大法則
- 寫測試 -> 寫程式
- 只寫剛好無法通過的測試
- 只寫剛好能透過當前測試的程式
Styling
- Given-When-Then
- One Assert per example (原則上)
- One concept per example
F.I.R.S.T.
- Fast: 測試程式不能跑太慢,不然你會不想跑 XD
- Independent: 區塊間不應該互相依賴,可以獨立跑某些測試,而且不能因為一個 fail 導致後續的 fail
- Repeatable: 在 Test / Development / Production 都能跑
- Self-validating: 通過 / 失敗 要明確,不要把重要結果放 log 裡面
- Timely: 即時寫測試,不要拖到寫完功能之後
Class
封裝
Keep private 除非需要開放給測試或其他 module
凝聚性
盡量減少你的 instance variables
- 當一群 Instance variables 在一些 methods 都需要用到時,也許他們該被拆出去變成一個新類別
Dependency Inversion Principle (DIP)
類別要 depends on 抽象概念,不要 depends on 具體細節
System
將所有關注的事分開
每個關注領域分開成不同模組,並以 interface 整合
- Lazy initialization 同時進行了「initialize」和「執行」,少用
- 使用一個主程式 Main 來建造物件,並讓物件各司其職
- 使用 Dependency Injection 來減少元件之間的 Coupling
- 善用 DSL (Domain-Specific Language) 來增加可讀性
註:這張多講了很多專有名詞,所以我是跳著看的(畢竟我跟 Java 不熟)。
羽化 Emergence
其實就是個小結。
四個簡單準則
重要性由上而下:
- 執行完所有測試
- 沒有重複的部分
- 表達 Programmer 的本意
- 最少的類別與方法
執行完所有測試
- 能夠撰寫測試,類別自然較為小型且單一用途
- 方便重構
平行化
將「做什麼」和「何時做」分離,讓你的 code 可以隨插即用 (跟寫輪眼一樣)
一些聲明
- 平行化會帶來額外負擔
- 正確的平行化是複雜的,即使原本的問題很簡單
- 平行化程式的錯誤不容易重複出現,容易被忽略
- 常常需要根本性的修改
能夠幫助平行化的原則
- SRP 單一職責原則
- 限制資料的視野
- 使用資料的 copy 而不要直接共享
- 讓 thread 盡可能獨立運行
當然還有了解當下語言的相關函式庫,撰寫各種順序的測試,讀一下作業系統相關章節等等
最後的清單:程式碼的氣味
一些會讓人修改 code 的原因,只寫下我覺得前面沒記錄到的:
- 多步驟才能 Build / Test
- 一個檔案多種語言(例如說不要再
.py
裡面硬寫 HTML) - Boundary Condition 壞掉(想起那個二分搜尋法…)
- 封裝他們
- 無視安全規範(例如:關掉失敗的測試、警告等)
- Magic Number: 出現在 Code 裡面的意義不明的數字(給個名字吧)
- Configuration 要放在高的抽象層
- Scope 大,變數名稱拉長
延伸閱讀
- Clean Code JavaScript,如果是寫 JS 的很推薦閱讀這個 repo
- Clean Code Python,Python 版本的 repo,受到上面那個 repo 啟發的