Skip to main content

程式設計原則:提升可讀性、可維護性與可擴展性

可讀性高、可維護、可擴展的程式碼是我們追求的目標,但是網路上沒有統整的資源都分成很多篇文章,學的時候總是在想我學完了嗎?這個絕對的嗎?於是這裡整理常見原則讓你能一次學完。和前言說的一樣,這裡不會深入細節,因為這些東西網路上有超級多資源我沒必要重寫一次,只會統整讓你知道有哪些東西。

要強調的是很多文章都把這些原則介紹的像是至高無上的真理一樣,這絕對是錯誤的,正確的是透過這些原則幫助我們寫出「可讀性高、可維護、可擴展」的程式碼而不是追求原則,更不是盲目追求 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,強調程式碼應該清晰、直觀而不是用隱式表達讓人還要推敲才能理解。

撰寫有意義的註釋

核心在於解釋「為什麼」(意圖)而非「是什麼」(表面行為),因為後者通常已由程式碼本身表達。

  • 適當註解可以幫助解釋程式目的和邏輯,但避免過度註釋
  • 甚至於程式改了註釋記得刪掉也是,別讓註解變成騙人的東西
  • 好的程式碼光是用變數和常見邏輯就可讀懂意義,過多的註釋就是廢話或程式碼太複雜!

程式碼風格一致性

  • 遵循一致的程式碼風格指南 (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) 很多人會這樣解釋:同樣的程式碼重複超過三次就抽象成函數,雖然這個說法不算錯一開始學也可以這樣用,但是詳情請見別寫乾淨的程式

  • 避免重複程式碼,提高可重用性和可維護性
  • 將重複邏輯提取到函式或類別中

關注點分離

關注點分離 (Separation of Concerns) 建議將程式分為不同部分,每部分解決單獨問題,提高模組化,降低耦合度。

老實說我覺得維基百科寫的解釋甚至優於大部分網路文章,所以請直接看維基百科

可擴展性

這裡我掰不出來了,因為自己不夠強到能解釋這個問題,所以偷偷在這裡總結前面的原則,在可讀性章節提到的原則可以視為法則,無時無刻都該遵守,在可維護性章節列出的原則就不見得是法則了,個人認為那裡面列出的原則算是建議。再次提醒,這些原則沒有哪個原則一定負責什麼什麼性,只是排版覺得這樣比較好讀。

其他重要原則

  • 單一職責原則 (很重要就再寫一次)
    • 每個類別或模組應該只有一個改變的理由
  • SOLID 原則
    • 複雜,五個原則可以寫成五篇文章,把握好基本原則再看 SOLID 原則
  • 最小驚訝原則 (Principle of Least Astonishment)
    • 設計應符合直覺預期,提高可用性
  • Fail-fast
    • 儘早暴露錯誤,以便及時發現和解決問題
    • 使用斷言、異常處理和日誌記錄等技術
  • 錯誤處理
    • 預期和處理可能發生的錯誤
    • 提供有意義的錯誤訊息
  • 避免過早優化
    • 先寫出可讀性高的程式碼,再進行效能優化
    • 只有在確認效能瓶頸後,才進行程式碼優化
  • 平衡 spaghetti code 和 ravioli code
    • 避免過度複雜 (spaghetti) 或過度分割 (ravioli) 的程式碼結構
  • 版本控制 (Git)
  • 單元測試和整合測試
  • 自動化測試 (Github Actions CI, commit hook)
  • 補充,Python 身為動態語言也可以進行靜態檢查

常見誤區

  1. 「組合優於繼承」和「迪米特法則」並非絕對,然而很多文章都把他說的像是絕對原則,這是大誤會。
  • 組合優於繼承
    • 優先使用組合而非繼承來實現程式碼重用
  • 迪米特法則 (Law of Demeter / Principle of Least Knowledge)
    • 一個物件應對其他物件有最少的了解
    • 減少耦合,提高模組化
  1. 濫用 DRY 會造成更大問題,請看別寫乾淨的程式
  2. 可讀性章節提到的原則可以視為法則,無時無刻都該遵守,在可維護性章節列出的原則就不見得是法則了,個人認為那裡面列出的原則算是建議。再次提醒,這些原則沒有哪個原則一定負責什麼什麼性,只是排版覺得這樣比較好讀。

相關資源

相關資源,大部分是影片。


這些是不完全相關的資源,也可以看一下。