為什麼程式碼品質重要#

高品質程式碼能產出可靠、易維護且少 Bug 的軟體;低品質程式碼則會導致系統脆弱、難以修改、充斥問題。每位工程師在撰寫程式碼時做出的小決策,累積起來就決定了軟體的好壞。

面對的情境高品質程式碼低品質程式碼
既有需求完全符合不完全符合,存在未處理的邊界情況
需求變更少量額外工作即可適應需大幅重做和重構
發生錯誤系統能正常還原或優雅降級進入未定義狀態,資料可能受損
未預期的使用場景系統仍可控系統進入未定義狀態,資料可能受損
遭受攻擊維持安全狀態,不受損害進入未定義狀態,可能被入侵
最終軟體樣貌可信賴、易維護、Bug 少不可信賴、難以維護、Bug 眾多

Figure 1.1: High-quality code maximizes the chance that the software will be reliable, maintainable, and meet its requirements. Low-quality code tends to do the opposite.

程式碼如何變成軟體#

程式碼不會在工程師寫完的瞬間就變成正式運行的軟體,通常需要經過多道流程與檢查。以下是幾個關鍵術語:

術語說明
程式碼庫(Codebase)由版本控制系統(如 Git)管理的程式碼倉庫
提交程式碼(Submitting code)將本地修改合併到主程式碼庫,也稱為 commit 或 merge pull request
程式碼審查(Code review)由其他工程師審查變更,如同「校對」般幫助發現作者遺漏的問題
預提交檢查(Pre-submit checks)在提交前自動執行測試和編譯檢查,若失敗則阻擋提交
發布(Release)從程式碼庫的快照建構軟體,通過品質保證檢查後釋出
生產環境(Production)軟體被部署到伺服器或系統上正式運行的狀態

典型的軟體開發與部署流程如下:

  1. 工程師在本地副本進行修改
  2. 提交程式碼審查
  3. 審查者檢視並可能要求修改
  4. 雙方同意後,程式碼提交到主程式碼庫
  5. 定期從程式碼庫產出發布版本
  6. 測試失敗或編譯錯誤會阻擋提交或阻擋發布
flowchart TD
A[工程師在本地修改] --> B[提交程式碼審查]
B --> C{審查者檢視}
C -->|要求修改| A
C -->|通過| D[合併到主程式碼庫]
D --> E[產出發布版本]
E --> F{品質檢查}
F -->|失敗| E
F -->|通過| G[部署到生產環境]

Figure 1.2: A simplified diagram of a typical software development and deployment process.

高品質程式碼的四個目標#

能運作#

程式碼的首要目的是解決問題。「能運作」意味著程式碼應正確完成其預期功能且無 Bug。這也包含效能、安全性、隱私等非功能性需求。

能持續運作#

程式碼今天能運作,不代表明天也能。程式碼不是孤立存在的:

  • 它依賴的其他程式碼會被修改和更新
  • 新功能需求可能需要修改現有程式碼
  • 問題本身會隨時間演變(消費者偏好、業務需求、技術條件都會改變)

重點: 確保程式碼持續運作是工程師面臨的最大挑戰之一,不應被視為事後補救的工作。

能適應變化的需求#

軟體開發往往持續數月乃至數十年,需求必然會改變。作者提出兩個極端情境的對比:

  • 過度預測(Scenario A):試圖預測所有未來可能的需求變化並提前設計,導致開發速度極慢,而且預測通常是錯的
  • 完全忽略(Scenario B):完全不考慮適應性,需求一變就得砍掉重練

技巧: 正確做法是介於兩者之間——採用通用的技術手段確保程式碼具有適應性,而不必精確預測未來會如何變化。

不重新發明輪子#

解決問題時,我們通常會將大問題拆解為多個子問題。許多子問題早已有人解決過,使用現有方案有四大好處:

好處說明
節省時間與心力不需從頭實作底層功能
降低 Bug 機率已被廣泛測試和使用的程式碼更可靠
借助既有專業知識維護者是該領域的專家,會持續更新
提高可理解性使用標準方式做事,其他工程師一看就懂

補充: 這個原則是雙向的——如果我們寫了解決某子問題的程式碼,也應該把它結構化,讓其他工程師能輕鬆重用。

程式碼品質的六大支柱#

讓程式碼可讀#

如同一份糟糕的食譜——沒有標題、步驟混在一起、用 A/B/C 代稱器具、關鍵資訊擺錯位置——難以閱讀的程式碼會讓工程師花大量時間解讀,更可能在 code review 中遺漏 Bug,或在修改時引入新的錯誤。

避免意外#

想像你打電話訂披薩,但手機 app 的「貼心功能」在忙線時自動轉撥到另一間餐廳。你以為點了 margherita(瑪格麗特披薩),實際上卻收到了 margarita(瑪格麗特調酒)。

Figure 1.3: If you think you're talking to a pizza restaurant, when you're in fact talking to a Mexican restaurant, your order may still make sense, but you'll get a surprise when it's delivered.

其他工程師使用我們的程式碼時,會根據命名、資料型別和慣例建立心理模型。如果程式碼做了超出這個模型的事情,就會產生意外,而意外往往在很久之後才以詭異的方式浮現。

讓程式碼不易被誤用#

電視背面的插孔被設計成不同形狀,讓你不可能把電源線插進 HDMI 孔。

Figure 1.4: The sockets on the back of a TV are deliberately different shapes to make it hard to plug the wrong cables into the wrong holes.

程式碼也是如此——其他程式碼會「插入」各種輸入參數或前置狀態。如果錯誤的東西被插入,系統可能崩潰、資料庫損壞、重要資料遺失。透過設計讓程式碼難以(甚至不可能)被誤用,能大幅提升軟體的可靠性。

讓程式碼模組化#

模組化(Modularity)意味著系統由可獨立替換的小元件組成。以兩個玩具為例:

  • 模組化的玩具(左):頭、手、腳可以獨立拆裝替換,元件間只有簡單的介面(一個凸榫對一個孔)
  • 非模組化的玩具(右):所有部件縫在一起,無法獨立替換,介面極度複雜(20 多條線交織)

Figure 1.5: A modular toy can be easily reconfigured. A toy that has been stitched together is extremely hard to reconfigure.

模組化的程式碼更容易適應需求變更(修改一處不會牽動全局),也更容易理解和推理。

讓程式碼可重用與可泛化#

  • 可重用性(Reusability):同一段程式碼能在不同場景下解決相同問題(如電鑽能鑽牆、鑽地板、鑽天花板)
  • 可泛化性(Generalizability):程式碼能解決概念相似但細節不同的問題(如電鑽既能鑽孔也能鎖螺絲)

程式碼的建立與維護都需要時間和心力,而且寫越多就越可能引入 Bug。讓程式碼可重用和可泛化,能減少程式碼總量,同時提高可靠性。

讓程式碼可測試且正確測試#

測試是確保程式碼能運作且持續運作的關鍵防線,在兩個關鍵時間點發揮作用:

  1. 阻擋有問題的程式碼提交到程式碼庫
  2. 阻擋有問題的版本發布到生產環境

Figure 1.6: Tests are vital for minimizing the chance that bugs and broken functionality enter the codebase.

測試分為幾個層級:

測試層級說明
單元測試(Unit tests)測試個別函式或類別,是工程師日常最常接觸的層級
整合測試(Integration tests)測試多個元件整合在一起時是否正常運作
端對端測試(E2E tests)測試從頭到尾的完整使用流程

注意:可測試性」和「測試」是兩個不同概念。即使你想寫測試,如果程式碼本身不具備可測試性(例如過度耦合),就無法有效測試。在寫程式碼的過程中,應持續問自己「這要怎麼測試?」

補充: 測試驅動開發(TDD)主張先寫測試再寫程式碼,是確保可測試性的一種有效實踐。

mindmap
root((程式碼品質))
四個目標
能運作
能持續運作
能適應變化
不重造輪子
六大支柱
可讀
無意外
不易誤用
模組化
可重用與可泛化
可測試

寫高品質程式碼會拖慢開發嗎?#

短期看來,寫高品質程式碼確實需要多花一些心思。但只要不是一次性的拋棄式工具,高品質程式碼在中長期幾乎總是能加速開發

作者用「裝層板」來比喻:

  • 正確做法:用電鑽和螺絲固定支架,花 30 分鐘。日後調整或重新裝潢都很容易
  • 偷懶做法:用膠水黏上去,花 10 分鐘。但層板很快會連同牆面石膏一起掉落,修復要花數小時甚至數天

重點:欲速則不達」(Less haste, more speed)——不要把匆忙當成效率。走捷徑省下的時間,往往會在後續的修復、重構和除錯中加倍償還。

總結#

  • 要產出好軟體,需要寫高品質的程式碼
  • 程式碼在成為正式軟體前,通常需要通過數道檢查與測試關卡
  • 高品質程式碼追求四個目標:能運作持續運作能適應變化不重造輪子
  • 實現品質的六大支柱:可讀無意外不易誤用模組化可重用可測試
  • 短期看似變慢,但中長期必然加速開發