程式設計原則:提升可讀性、可維護性與可擴展性
可讀性高、可維護、可擴展的程式碼是我們追求的目標,但是網路上沒有統整的資源都分成很多篇文章,學的時候總是在想我學完了嗎?這個絕對的嗎?於是這裡整理常見 原則讓你能一次學完。和前言說的一樣,這裡不會深入細節,因為這些東西網路上有超級多資源我沒必要重寫一次,只會統整讓你知道有哪些東西。
要強調的是很多文章都把這些原則介紹的像是至高無上的真理一樣,這絕對是錯誤的,我們的目的是「可讀性高、可維護、可擴展」的程式碼,這些原則只是方法,不應該盲目追求 clean code,這嚴重搞混方法和目的。
這些原則沒有哪個原則一定負責什麼什麼性,只是排版覺得這樣比較好讀,很多原則可以同時服務可讀、可維護、好擴展,例如 SRP 就是一個例子。
強烈建議看過這部影片裡面提到的代码是工具,最好是連下面的留言都看一看,這樣你就可以清楚的知道這些 是規則不是法則,不應該把目的和方法倒果為因。
可讀性
想像一下接手一個亂成一團的老專案,亂就算了還沒人問,要獨自理解程式邏輯會有多痛苦。可讀性的本質是降低理解和維護程式碼的認知負擔,優秀的程式碼理解起來應該要很輕鬆,邏輯流暢、意圖明確、結構簡潔。好的可讀性意味著:
- 新人可以更快加入專案
- bug 修復變得更加直觀
- 團隊協作效率提升
- 程式碼重構和擴展變得更加容易
撰寫易於除錯的程式碼,從意識到未來會忘記這些程式碼開始。
From Write code that’s easy to delete, and easy to debug too.
單一職責原則 SRP
單一職責原則 (Single Responsibility Principle, SRP) 是 SOLID 設計原則之一,但是就算不看 SOLID 在大部分的程式碼都應該遵守他,核心理念是一個類別或模組應該僅負責一個單一的 職責。什麼是職責?職責指的是某個類或模組所負責的特定功能或行為,也可以理解成該模組「改變的理由」,可以幫助程式碼易於理解、測試與替換。
講白了就是各管各的降低耦合,你不會想要一個既管理資料庫操作又處理使用者介面邏輯的物件,因為任何一個環節出問題都可能連累整個系統。
KISS
KISS (Keep It Simple, Stupid) 建議保持程式簡單明瞭,避免不必要的複雜性,簡單的程式碼易於理解、維護與擴展,降低錯誤機率,提升效率。KISS 原則不是反對複雜性,而是反對不必要的複雜性。
- 使用清晰的命名和簡潔的邏輯
- 避免多餘功能或過度抽象
- 避免炫技,不寫複雜難懂的程式碼
- 避免為了效能寫複雜難懂的程式碼,除非能有一個 order 的效能提升才改,但這種效能提升通常是架構設計層面的問題,尤其是在沒有 profiling 的情況下很有可能 80% 的努力帶來 5% 的效能提升
顯式優於隱式
來自 Zen of Python,強調程式碼應該清晰、直觀而不是用隱式表達讓人還要推敲才能理解。
- 程式碼的意圖應該明確,避免隱式表達
- 隱示代表程式語言本身任何的隱示表達方式,例如
from package import *
語法約束好於邏輯約束
字典取鍵值
顯式的寫出 if-else
就算只是補上 else 都比懶惰不寫好
撰寫有意義的註釋
核心在於解釋「為什麼」(意圖)而非「是什麼」(表面行為),因為後者通常已由程式碼本身表達。
- 適當註解可以幫助解釋程式目的和邏輯,但避免過度註釋
- 甚至於程式改了註釋記得刪掉也是,別讓註解變成騙人的東西
- 好的程式碼光是用變數和常見邏輯就可讀懂意義,過多的註釋就是廢話或程式碼太複雜!
程式碼風格一致性
- 遵循一致的程式碼風格指南 (PEP 8)
- 使用程式碼格式化工具確保一致性 (Ruff formatter)
- 避免魔術數字與硬編碼
- 明確的條件判斷
- 適當的空白與縮排
- 清晰的錯誤處理
可維護性
隨著時間推移,專案成長,我們勢必要花越來越多的時間維護程式。可維護性建構在好的可讀性之上,但是比可讀性更複 雜,是一種系統性的思考方式:
- 能被輕鬆地理解和修改
- 即使專案規模擴大仍然保持程式邏輯清晰
- 降低修改時影響其他模組的風險
開放封閉原則
開放封閉原則 (Open–Closed Principle, OCP) 是 SOLID 原則的其中一項,你一定看過這句話:對擴展開放,對修改封閉:
- 開放:能夠新增功能或改變系統行為,而不必直接更動已經穩定的程式碼。
- 封閉:對一個已經開發完成的模組應該避免直接修改其原始碼,而應透過擴展來新增功能,以減少對既有程式碼的影響,保持穩定性。
兩者都是避免修改原有程式碼,不過前者強調輕鬆增加功能,後者強調保護既有功能不被破壞。這是一個比較模糊的概念,簡單的範例就是插件系統,我們在 Chrome 或者 Vim 的插件可以隨便搞輕鬆的新增功能,但是原有的瀏覽器和文字編輯功能永遠不會被更動,就是 OCP 的體現。
介面隔離原則
介面隔離原則 (Interface-Segregation Principles, ISP) 只透出最小介面,不要給一個大父類強迫所有人實作介面,第一用不到功能,第二增加耦合性造成修改困難。
高內聚低耦合
一句話解釋,模組內部專注於單一的職責,模組間的依賴關係最小化,減少模組間的互 相影響。
我不知道為什麼一堆介紹內聚和耦合的文章第一句話甚至是標題就要說物件導向,就算不是物件導向的程式語言高內聚低耦合也存在且必要。一個簡單的概念是,修改功能 A 模組就不要改到 A 以外模組,錯誤來自於修改,改的範圍越大代表耦合性越高,越容易造成動一髮牽全身,程式越容易出錯,這個概念明顯不限於物件導向程式設計。
YAGNI
YAGNI (You Aren't Gonna Need It) 說明避免過度設計和不必要的複雜性,專注於解決當前的具體問題
- 不要過度預測需求,只實現當前需要的功能
- 保持程式碼靈活性,降低維護成本
DRY
DRY (Don't Repeat Yourself) 很多人會這樣解釋:同樣的程式碼重複超過三次就抽象成函數,DRY 可以簡單的總結成兩點:
- 避免重複程式碼,提高可重用性和可維護性
- 將重複邏輯提取到函式或類別中
雖然這個說法不算錯一開始學也可以這樣用,但是現在更多人意識到了 DRY 帶來更多問題而開始說 Repeat yourself,詳情請見別寫乾淨的程式。
關注點分離
關注點分離 (Separation of Concerns) 建議將程式分為不同部分,每部分解決單獨問題,提高模組化,降低耦合度。
老實說我覺得維基百科寫的解釋 甚至優於大部分網路文章,所以請直接看維基百科。
可擴展性
這裡我掰不出來了,因為自己不夠強到能解釋這個問題,所以偷偷在這裡總結前面的原則,在可讀性章節提到的原則可以視為法則,無時無刻都該遵守,在可維護性章節列出的原則就不見得是法則了,個人認為那裡面列出的原則算是建議。再次提醒,這些原則沒有哪個原則一定負責什麼什麼性,只是排版覺得這樣比較好讀。
其他重要原則
- 單一職責原則 (很重要就再寫一次)
- 每個類別或模組應該只有一個改變的理由
- SOLID 原則
- 複雜,五個原則可以寫成五篇文章,把握好基本原則再看 SOLID 原則
- 最小驚訝原則 (Principle of Least Astonishment)
- 設計應符合直覺預期,提高可用性
- Fail-fast
- 儘早暴露錯誤,以便及時發現和解決問題
- 使用斷言、異常處理和日誌記錄等技術
- 錯誤處理
- 預期和處理可能發生的錯誤
- 提供有意義的錯誤訊息
- 避免過早優化
- 先寫出可讀性高的程式碼,再進行效能優化
- 只有在確認效能瓶頸後,才進行程式碼優化
- 平衡 spaghetti code 和 ravioli code
- 避免過度複雜 (spaghetti) 或過度分割 (ravioli) 的程式碼結構
- 版本控制 (Git)
- 單元測試和整合測試
- 自動化測試 (Github Actions CI, commit hook)
- 補充,Python 身為動態語言也可以進行靜態檢查
常見誤區
- 「組合優於繼承」和「迪米特法則」並非絕對,然而很多文章都把他說的像是絕對原則,這是大誤會。
- 組合優於繼承
- 優先使用組合而非繼承來實現程式碼重用
- 迪米特法則 (Law of Demeter / Principle of Least Knowledge)
- 一個物件應對其他物件有最少的了解
- 減少耦合,提高模組化
- 濫用 DRY 會造成更大問題,請看別寫乾淨的程式。
- 在可讀性章節提到的原則可以視為法則,無時無刻都該遵守,在可維護性章節列出的原則就不見得是法則了,個人認為那裡面列出的原則算是建議。再次提醒,這些原則沒有哪個原則一定負責什麼什麼性,只是排版覺得這樣比較好讀。
相關資源
相關資源,大部分是影片。
- Clean Code 實戰之 PHP 良好實踐
從初學者到進階的乾淨程式碼指南。 - 【Code Review】把 & 當 and 用可是不行!測試寫成這樣也有點離譜哦!
適合初學者的程式碼審查,展示基本的程式碼優化技巧。 - 如何優雅地避免程式碼巢狀 | 程式碼嵌套 | 狀態模式 | 表驅動法 |
介紹減少巢狀結構的方法,包括表驅動法、提前返回、斷言、多型,以及內建函數(filter、sort、group、map、any、all),並探討更好的空指標處理方式。 - 【Python】原來我可以少寫這麼多 for 迴圈!學會之後程式碼都更 Pythonic 了!
介紹 Python 中實用的內建函數,並與手寫 for 迴圈進行效能比較。 - 我與微軟的程式碼規範之爭——局部變數竟然不讓初始化?
討論 C 語言中是否應初始化局部變數,並分析相關錯誤的權衡。 - 【Code Review】傳參的時候有這麼多細節要考慮?冗餘的迴圈變數你也寫過嗎?
- 【Code Review】十行迴圈變兩行?argparse 注意事項?不易察覺的異常處理?
- 【Code Review】格式、異常處理與多執行緒風險
- 30 YEAR OLD C++ Code Base of Command & Conquer
這些是不完全相關的資源,也可以看一下。
- Win 系統舊程式碼導致 CPU 過熱?Google 工程師背鍋 | Google | 微軟 | Chrome | 負優化 | 記憶體 | 系統 | Windows | 程式設計 | CPU
過時且不清晰的 Windows API 導致分頁錯誤開銷過大。 - Kotlin 顏值為何遙遙領先 | 不可變變數 | lambda | 語法糖 | 建構函式 | 教程 | 中綴表達式 | val var
良好的設計讓唯讀變數比例從 2.9% 提升至 86.3%。