為什麼程式開發一定需要重構(Refactoring)?

 從維護地獄走回可長期演進的系統

在實務開發中,我們很常聽到一句話:

「這段先這樣寫,之後再重構。」

但現實往往是——
「之後」永遠不會來,直到系統已經變成維護地獄。


一、為什麼程式開發一定需要重構?

1️⃣ 軟體不是一次性產品,而是「長期演進的系統」

大多數系統的生命週期是:

  • 第 1 年:快速開發、需求不明確

  • 第 2 年:開始加功能、補例外

  • 第 3 年:修 bug 比加功能還慢

  • 第 4 年:沒人敢動核心程式

如果沒有重構,程式會逐漸變成:

  • 條件判斷滿天飛

  • 方法越寫越長

  • 一改 A 壞 B

  • 新人完全看不懂

👉 重構的本質,是讓系統「活得久」


2️⃣ 重構不是為了優雅,而是為了降低風險

很多人誤會重構是:

「程式寫得不夠漂亮,所以想改寫」

實務上真正的原因是:

  • 修改需求時風險太高

  • Bug 定位困難

  • 重複邏輯無法控管

  • 技術債開始拖慢整個團隊

👉 重構是在「降低未來改動的成本與風險」


二、什麼狀況下需要重構?

以下是實務上非常明確的「重構警訊」。

✅ 1. 同一段邏輯出現超過 2~3 次

if (user != null && user.IsActive && user.Role == "Admin") { ... }

當你開始 複製貼上,而不是抽成方法,
這通常是第一個警訊。


✅ 2. 一個方法超過「一個責任」

void ProcessOrder() { // 驗證 // 計算金額 // 寫入資料庫 // 發送 Email // 記錄 Log }

這種方法通常:

  • 很難測試

  • 很難修改

  • 一動就全壞

👉 違反 Single Responsibility Principle


✅ 3. 新需求來了,你第一個反應是「完蛋了」

如果你看到需求變更時:

  • 不知道該改哪

  • 害怕影響既有功能

  • 需要大量人工測試

👉 這通常不是需求問題,而是 設計已經失控


✅ 4. 大量 if / switch 判斷業務邏輯

if (type == 1) { ... } else if (type == 2) { ... } else if (type == 3) { ... }

這類程式:

  • 不可擴充

  • 新增需求只能一直加條件

  • 很容易改壞舊邏輯


三、重構前的必要前提(非常重要)

❗ 1️⃣ 重構 ≠ 改需求

重構的定義是:

在不改變外部行為的前提下,改善內部結構

如果同時:

  • 改邏輯

  • 改需求

  • 改流程

👉 風險會「指數級上升」


❗ 2️⃣ 一定要有「保護網」

實務上至少要具備其中一種:

  • 單元測試(Unit Test)

  • 穩定的 QA 測試流程

  • 可快速回滾的部署機制

沒有保護網的重構,本質是「豪賭」。


❗ 3️⃣ 先理解,不要急著改

最常見的錯誤是:

「這段好醜,我重寫一份比較乾淨的」

結果通常是:

  • 隱藏邏輯被刪掉

  • 邊界條件遺失

  • 舊 bug 變成新 bug

👉 重構前一定要先「完全理解原意圖」


四、重構時真正要考量的重點

🔹 1. 邊界與責任切割

問自己三個問題:

  • 這個方法「到底在做什麼」

  • 它有沒有在做「不屬於它的事」

  • 能不能拆成更小、可命名的單元


🔹 2. 可讀性 > 聰明技巧

重構的目標不是炫技,而是:

  • 新人看得懂

  • 半年後的你看得懂

  • Debug 時能快速定位


🔹 3. 為「變化點」設計,而不是現在的需求

好的重構會思考:

  • 未來最可能變的是什麼?

  • 哪裡需要被替換?

  • 哪裡應該被隔離?


五、實際範例:一段「典型需要重構的程式」

❌ 重構前(問題示範)

public void ProcessPayment(Order order) { if (order == null) throw new Exception("Order is null"); if (order.Amount <= 0) throw new Exception("Invalid amount"); if (order.PaymentType == "CreditCard") { // 信用卡付款流程 Console.WriteLine("Credit card payment"); } else if (order.PaymentType == "ATM") { // ATM 付款流程 Console.WriteLine("ATM payment"); } else if (order.PaymentType == "Cash") { // 現金付款流程 Console.WriteLine("Cash payment"); } // 寫 log Console.WriteLine("Payment processed"); }

問題點:

  • 業務邏輯與流程混在一起

  • 新增付款方式一定要改這個方法

  • 使用 magic string

  • 難以單元測試


✅ 重構後(改善結構,不改行為)

1️⃣ 抽象付款行為

public interface IPaymentStrategy { void Pay(Order order); }

2️⃣ 各付款方式各自負責

public class CreditCardPayment : IPaymentStrategy { public void Pay(Order order) { Console.WriteLine("Credit card payment"); } } public class AtmPayment : IPaymentStrategy { public void Pay(Order order) { Console.WriteLine("ATM payment"); } }

3️⃣ 主流程只負責「協調」

public class PaymentService { private readonly IDictionary<string, IPaymentStrategy> _strategies; public PaymentService(IDictionary<string, IPaymentStrategy> strategies) { _strategies = strategies; } public void ProcessPayment(Order order) { Validate(order); _strategies[order.PaymentType].Pay(order); Log(); } private void Validate(Order order) { if (order == null) throw new Exception("Order is null"); if (order.Amount <= 0) throw new Exception("Invalid amount"); } private void Log() { Console.WriteLine("Payment processed"); } }

🎯 重構後的好處

  • 新增付款方式 不用改主流程

  • 每個類別責任清楚

  • 更容易寫單元測試

  • 風險集中、修改範圍可控


六、結語:重構不是選項,而是責任

重構不是:

  • 為了程式漂亮

  • 為了滿足潔癖

  • 為了炫技

而是:

為了讓系統在需求變動下,仍然能被人類維護

如果你的系統還會活超過一年,
那麼 重構不是要不要做,而是「什麼時候做」

留言

熱門文章