iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0
Software Development

文科生的軟體工程啟蒙:用一個代購 App,看懂 30 個系統設計觀念系列 第 20 篇

Day 20:重構策略(Refactoring)——如何在不改變外部行為的前提下安全調整內部結構

  • 分享至 

  • xImage
  •  

過去幾天談的 OCP、DIP、模組化、介面抽象、錯誤處理,都是「理想中程式碼該有的樣子」。但現實中大部分程式碼都是先求能動、再求好——今天要談的重構,就是把已經存在、能動但不夠理想的程式碼,逐步調整成前面幾天描述的樣子,而不需要把整個系統推倒重寫。

一、重構的定義

Martin Fowler 的定義是:重構是「在不改變軟體外部可觀察行為的前提下,調整其內部結構,讓它更容易被理解、日後修改的成本更低」的過程。

關鍵字是「外部可觀察行為不變」——同樣的輸入,重構前後都必須得到同樣的輸出。這正是重構跟「改功能」、「修 bug」最根本的差異:改功能和修 bug 都是在改變系統的行為,而重構只改變程式碼的內部結構,不改變它對外呈現的結果。

二、為什麼要用小步驟進行

Fowler 強調,重構是「一連串很小、而且每一步都會保留原本行為的轉換(transformation)」。因為每一步改動都很小,出錯的機率也隨之降低——就算某一步真的改壞了,也很容易回頭找出是哪一步造成的,而不是要在一大片改動裡大海撈針。

測試扮演的角色:在每一個小步驟之後,重新執行測試,確認外部行為真的沒有改變,才進行下一步。沒有測試網的重構,等於是在沒有安全繩的狀態下走鋼索——這也是為什麼「先補測試,再重構」常常是實務上的第一步。

三、代購 App 案例

延續 Day 13 的義大利麵程式碼範例(processOrder 同時做了商品抓取、訂單計算、資料庫連線、Line 通知四件事),示範怎麼用重構的方式,一步一步走向 Day 14 分層架構、Day 15 SRP 的最終樣貌,而不是直接重寫整個系統。

// 重構前:義大利麵程式碼(Day 13 的範例)
async function processOrder(order) {
  const item = await fetchProduct(order.itemId);
  const total = item.price * order.qty + order.shippingFee;
  await db.query(`UPDATE orders SET total = ${total} WHERE id = ${order.id}`);
  await lineNotify(order.buyerId, `您的訂單總額為 ${total} 元`);
}

// 第一步重構:把「計算總金額」抽成獨立函式,行為完全不變,只是換了位置
function calculateTotal(item, order) {
  return item.price * order.qty + order.shippingFee;
}
async function processOrder(order) {
  const item = await fetchProduct(order.itemId);
  const total = calculateTotal(item, order); // 呼叫端拿到的結果跟重構前一模一樣
  await db.query(`UPDATE orders SET total = ${total} WHERE id = ${order.id}`);
  await lineNotify(order.buyerId, `您的訂單總額為 ${total} 元`);
}
// 這一步做完,先重新跑一次測試,確認輸出結果沒有改變,才進行下一步

// 第二步重構:把資料庫存取也抽成獨立函式
async function saveOrderTotal(orderId, total) {
  await db.query(`UPDATE orders SET total = ${total} WHERE id = ${orderId}`);
}
async function processOrder(order) {
  const item = await fetchProduct(order.itemId);
  const total = calculateTotal(item, order);
  await saveOrderTotal(order.id, total);
  await lineNotify(order.buyerId, `您的訂單總額為 ${total} 元`);
}
// 重複這個「抽出一小塊、跑測試、確認行為不變」的過程,
// 最終才會走到 Day 14 分層架構、Day 15 SRP 看到的那個各司其職的乾淨版本

每一步重構做的事情都很小——只是把一段程式碼「搬」到一個新的函式裡,呼叫端的輸出結果完全沒有變。但這些小步驟累積起來,最終就能把一開始糾結成一團的 processOrder,逐步拆成前幾天談到的分層架構與符合 SRP 的獨立模組。

四、結論

重構不是一次性的大改版,而是用一連串「外部行為不變、內部結構變好」的小步驟,把程式碼慢慢帶往 OCP、DIP、SRP 所描述的理想狀態。這也回頭說明了為什麼這系列會從 Day 13 的壞味道,一路講到 Day 15 的 SRP,再到這幾天的 OCP、DIP、模組化——它們不只是抽象的原則,更是重構時「該往哪個方向走」的具體指引。


上一篇
# Day 19:錯誤處理架構——全域異常攔截、自訂 Error 類別與優雅降級(Graceful Degradation)
下一篇
Day 21:可測試性架構(Testability)——純函式(Pure Functions)與無副作用(Side Effects)的價值
系列文
文科生的軟體工程啟蒙:用一個代購 App,看懂 30 個系統設計觀念 共 21 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言