核心概念#

身為程式設計師,我們收集、組織、維護和駕馭知識。
我們把知識記錄在規格書中,讓它在運行的程式碼中活起來,用它來提供測試時需要的檢查。
不幸的是,知識並不穩定——它經常快速變化。

大多數人認為維護是從應用程式發布後才開始,但作者認為這是錯的。
程式設計師一直在維護模式。我們的理解每天都變化,新需求到來,現有需求隨開發進行而演進。

Tip 15 - DRY–Don’t Repeat Yourself(不要重複你自己)

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system. (系統中的每一項知識都必須有單一、明確、權威的表述。)

替代方案是在兩個或多個地方表達同件事。如果你改了一個,你必須記得改其他的。
這不是會不會記得的問題,而是什麼時候會忘記的問題。

DRY 不只是程式碼#

很多人認為 DRY 只是指程式碼——「不要複製貼上程式碼」。
DRY 的一部分,但只是微不足道的一小部分。

DRY 是關於知識的重複、意圖的重複
它是關於在兩個不同地方、可能以兩種不同方式表達同件事。

測試標準#

當某個單一面向的程式碼需要改變時,你是否發現自己要在多個地方、以多種不同格式進行修改?
如果是,你的程式碼就不是 DRY 的。

程式碼中的重複#

程式碼重複可能很瑣碎,但很常見。
作者透過一個 print_balance 函式例子逐步示範如何消除重複:

  1. 抽取處理負數格式的重複邏輯為 format_amount 函式
  2. 利用此函式消除欄位寬度的重複
  3. 抽取列印格式為 print_linereport_line,讓每個關注點都只有一個修改點

不是所有程式碼重複都是知識重複#

例如驗證年齡和驗證數量的函式體可能完全相同,但它們驗證的是兩件不同事情,只是恰好有相同的規則。
那是巧合,不是重複。

文件中的重複#

不知怎的,有個迷思誕生了:你應該為所有函式寫註解。
函式的意圖如果在註解中寫一次、在程式碼中又寫一次,那就是 DRY 違規。
解法是用好的命名和清晰的程式碼佈局來自我說明:

當註解與程式碼重複表達同一件事時,隨著時間推移,它們幾乎肯定會脫節。用好的命名取代冗餘的註解。

資料中的 DRY 違規#

資料結構也代表知識。
例如一個 Line 類別如果同時有 startendlength 欄位,就存在重複——因為 length 是由 startend 決定的。
最好讓 length 成為一個計算欄位。

如果出於效能原因需要快取,就把違規局部化——
只有類別內部的方法需要處理一致性,透過存取器函式隱藏實作細節。

Meyer 的統一存取原則指出:模組提供的所有服務都應該透過統一的表示法來提供,不應洩露它是透過儲存還是透過計算來實現的。

表徵性重複(Representational Duplication)#

你的程式碼透過 API、其他服務、資料來源等與外部世界介接。每次這樣做,你都引入了某種 DRY 違規——你的程式碼必須擁有外部事物的知識。

緩解策略包括:

  • 內部 API:使用中性格式的工具來定義 API,自動產生文件、mock、測試和客戶端
  • 外部 API:匯入 OpenAPI 等規格,整合更可靠
  • 資料來源:從 schema 自動產生容器,而非手動撰寫;或使用 key/value 資料結構搭配驗證層

開發者間的重複#

也許最難偵測和處理的重複類型。不同開發者之間可能無意間重複整套功能。

最佳應對方式是鼓勵開發者之間積極且頻繁的溝通:

  • 每日站會、Slack 等論壇討論共同問題
  • 指定一位團隊成員擔任「專案圖書館員」,促進知識交流
  • 在原始碼樹中有一個中央位置存放公用程式
  • 養成閱讀他人程式碼的習慣

Tip 16 - Make It Easy to Reuse(讓重用變得容易)

營造一個易找到和重用現有東西的環境。
如果不容易,人們就不會去做。
如果不重用,你就有重複知識的風險。

相關章節#

  • Topic 8,優秀設計的精髓
  • Topic 28,解耦
  • Topic 32,配置
  • Topic 38,巧合式程式設計
  • Topic 40,重構