第 1 章說過:軟體開發是迭代且增量的。系統的設計不斷在演化——成熟系統的設計,多半由「演化過程中的變更」決定,而非當初的構思。

前面章節談的是怎麼在初始設計階段擠出複雜性;本章談修改既有程式時,如何阻止複雜性悄悄爬回來

保持戰略型思維#

第 3 章區分過:

  • 戰術型:只求東西能動
  • 戰略型:以好設計為主要目標

戰術型很快讓系統設計變糟。

想要好維護、好擴充的系統,「能跑」不是夠高的標準——必須優先考慮設計、思考戰略。

修改既有程式時的常見錯誤#

開發者改既有程式時的典型心態:

「能讓我達成目的的最小修改是什麼?」

理由可能是:對程式碼不熟,怕大改引入 bug。但結果是:

  • 每次最小修改都引入幾個特殊情況、依賴或其他複雜性
  • 系統設計每改一次就壞一點,問題逐步累積

戰略型修改的目標#

修改完之後,系統的結構應該與「一開始就帶著這個變更去設計」時的樣子一樣。

要做到這一點:

  • 抗拒「快速修補」的誘惑
  • 思考:在這個新需求下,目前的設計仍然是最佳的嗎
  • 不是 → 重構至最佳設計
  • 這樣每次修改後,系統設計都變得更好

這也是投資心態:投入一點時間重構 → 系統更乾淨 → 開發提速 → 收回重構投入。

即使這次不需重構#

即使你的這次變更不需要重構,仍要主動尋找可以順手改善的設計缺陷

任何時候改程式,都試著至少讓設計好一點——若沒讓設計變好,多半就是讓它變糟。

現實中的取捨#

商業開發中投資心態與現實有時衝突:

  • 「正確」重構要 3 個月,「快補」只需 2 小時 → 截止日臨頭時可能不得不快補
  • 重構會破壞與其他人 / 其他團隊的相容性 → 可能不可行

盡量抗拒這些妥協。問自己:

「在現有限制下,我能做到的最乾淨的設計是什麼?」

  • 也許有個「介於 3 個月和 2 小時之間」、幾天就能完成、且效果接近 3 個月版本的方案
  • 真的當下來不及,請主管在這個截止日後排時間回頭做

每個開發組織都應該規劃花一小部分總工時做清理與重構——它長期一定回本。

維護註解:放近程式碼#

確保註解被更新的最佳方式:把註解放在它所描述的程式碼旁邊——這樣修程式時自然會看到。

距離越遠 → 越容易遺漏更新。

方法的介面註解該放哪?#

  • 最佳位置:放在 .c / .cpp 等實作檔案中、緊鄰方法主體上方
  • 任何修改都會經過這段,註解就在眼前

C / C++ 的另一選擇:放在 .h#

可以放在 header 的方法宣告旁邊,但:

  • 與實作距離遠 → 改方法時看不到 → 易過時
  • 開啟另一個檔案找註解又要多步驟

有人主張介面註解該放 header 讓使用者不用看實作就能學 API。但其實使用者不該靠讀程式碼或 header——他們應該透過 Doxygen / Javadoc 等工具產生的文件、或 IDE 自動顯示的 tooltip 來學 API。

既然如此,註解應該放在「對改程式的開發者最方便」的地方。

實作註解的擺放#

不要把整個方法的註解全堆在方法頂端。

把每段註解推到「包住所有它描述的程式碼的最小範圍」內:

  • 例如方法有三個主要階段 → 別在頂端寫一大段
  • 在每個階段第一行程式碼上方各放一段註解

但頂端可以放一段「整體策略」說明:

// We proceed in three phases:
// Phase 1: Find feasible candidates
// Phase 2: Assign each candidate a score
// Phase 3: Choose the best, and remove it

一般原則:註解離程式碼越遠,就應該越抽象——這樣它較不會被局部修改使其失效。

註解屬於程式碼,不屬於 commit log#

常見錯誤:把變更的詳細資訊寫進 commit message,但程式碼裡沒記錄

問題:

  • commit log 雖然能搜尋,但需要它的開發者通常不會想到去搜
  • 即使搜了,要找到對的 commit 也很費力

寫 commit message 時問自己:

未來的開發者會需要這份資訊嗎?

會 → 把它寫進程式碼

例如:commit 訊息描述了一個微妙問題(promoted 此次變更的原因)。如果沒寫進程式碼,後人可能不知不覺把改動還原,把 bug 重新引入。

commit message 中也可以放一份副本,但最重要的是寫進程式碼裡——因為這是開發者最可能看到的地方。

維護註解:避免重複#

若文件被重複,就更難找出與更新所有相關副本。

每個設計決定,恰好記錄一次。

自然中央位置#

例如:與某變數相關的 tricky 行為影響多處 → 把行為描述寫在該變數的宣告旁註解——這是讀者搜尋的自然位置。

沒有自然位置時#

  • 建立 designNotes 檔(13.7 節)
  • 或在最佳位置放主註解,其他相關位置寫短註解指向那裡:
// See the comment in xyz for an explanation of the code below.

引用過時時 → 症狀自我顯現(讀者去查找不到,可用 revision history 追溯)。

而若是文件被重複、且部分副本未更新,沒有任何訊號告訴開發者他正在用舊資訊。

不要在 A 模組重複描述 B 模組的設計#

例如:呼叫某方法之前,不要在呼叫旁註解「該方法做了什麼」。讀者要知道,去看那個方法的介面註解就好;好工具會自動顯示。

已存在於外部的文件,不要重複#

  • 寫 HTTP 協定的類別 → 不要在程式碼內重述 HTTP 協定,加一行 URL 即可
  • 已寫在 user manual 的指令 → 在介面註解寫:
// Implements the Foo command; see the user manual for details.

重點:讓讀者容易找到所有需要的文件——但不代表你得親手寫所有文件

維護註解:審 diff#

提交前花幾分鐘掃一遍所有 diff,確認每個變更都已對應在文件中

順便也能抓到:忘了移除的 debug 程式碼、未處理的 TODO 等。

高層註解最容易維護#

越高層、越抽象的註解,越容易維護

它們不反映程式碼細節,所以小修改不會讓它們失效——只有整體行為變才會牽動它們。

當然,第 13 章提過:有些註解必須詳盡精確。但最有用的註解(不只是重複程式碼)通常也最易維護