iT邦幫忙

2025 iThome 鐵人賽

DAY 18
0
Software Development

消除你程式碼的臭味系列 第 18

Day 18- 狀態管理:用資料定義流程

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20250903/20124462P1N8QjGguI.png

消除你程式碼的臭味 Day 18- 狀態管理:用資料定義流程

狀態機不該是 if/else 的迷宮,它的本質是一張規則流程表

把轉移規則寫成表,讀表執行就好。

先釐清「狀態」與「動作」

  • 狀態:當前所處的位置(created、paid、shipped)。
  • 動作:嘗試發生的事件(pay、ship、cancel)。
  • 轉移:在某個狀態接受某個動作,走到下一個狀態。

建模步驟

  1. 列出所有狀態與動作。
  2. 定義一張轉移表:狀態 × 動作 => 下一個狀態。
  3. 未列出的組合都視為「不變」或「拒絕」。
  4. 副作用(寄信、記錄)與轉移分開,轉移只負責回傳下一個狀態。

經典案例:用程式碼寫成的義大利麵條

// 🔴 臭味道:這不是狀態機,這是一個 bug 產生器
function nextStatus(status, action) {
  if (status === 'created') {
    if (action === 'pay') return 'paid';
    if (action === 'cancel') return 'canceled';
  } else if (status === 'paid') {
    if (action === 'ship') return 'shipped';
    // 如果這裡要加一個 'refund' 動作呢?繼續加 if?
  }
  // 如果要加一個 'shipped' 狀態呢?再加一個 else if?
  return status;
}
  • 規則被寫死在程式碼裡: 這些 if 判斷就是業務規則。

    • 它們和執行邏輯(return ...)混在一起,無法輕易地被檢視、修改或理解。
  • 維護性為零: 每增加一個狀態或動作,都必須修改程式碼的控制流程。

    • 這違反了開閉原則,而且極易出錯。你敢讓一個新手來修改這個檔案裡的規則嗎?當然不敢。
  • 它在說謊: 這段程式碼的複雜度隱藏了它背後簡單的真相:它只是一張二維表格的查詢。
    https://ithelp.ithome.com.tw/upload/images/20250920/20124462vWXflQb4B7.png

查表法:資料驅動

現在把規則從程式碼裡解放出來,讓它回歸其本質——資料

// 🟢 好味道:規則就是規則,程式碼就是程式碼

// 這就是你的狀態機。它是一份設定檔,不是一段程式。
// 你可以把它印出來貼在牆上,任何人都能看懂。
const transitions = {
  created: { pay: 'paid', cancel: 'canceled' },
  paid:    { ship: 'shipped', refund: 'refunded' },
  shipped: { complete: 'completed' },
  // 新增狀態或動作,只需要在這裡加一行資料。
};

// 這段程式碼變得極其愚蠢和穩定。它不需要知道任何業務規則。
// 它的唯一工作就是查表。這段程式碼可能永遠都不需要再改了。
function nextStatus(status, action) {
  return transitions[status]?.[action] ?? status;
}
  • 資料與程式碼分離: transitions 物件是「什麼」(What),nextStatus 函式是「如何」(How)。

    • 我們把複雜、易變的「什麼」變成了簡單的資料,而把穩定、通用的「如何」變成了簡單的程式碼(好設計。
  • 可讀且可擴展: 任何人,甚至非開發者,都能讀懂 transitions 表。

    • 增加一個新規則,只需要在資料裡加一個鍵值對,完全不用碰那段穩定的查表邏輯。
  • 好程式碼的體現: 這就是我說的「消除特殊情況」。

    • if-else 版本裡,每個 if 都是一個特殊情況。在查表法裡,沒有特殊情況,所有規則都只是表裡的一行資料。
      https://ithelp.ithome.com.tw/upload/images/20250920/20124462nxxhofSFiC.png

副作用處理

絕對不要把副作用(寄信、寫資料庫)跟狀態轉移的純邏輯混在一起。

正確的做法是兩步:

  1. 計算新狀態(純函式): const newState = nextStatus(oldState, action); 這一步不應該有任何副作用。

  2. 觸發副作用(如果不純): if (newState !== oldState) { triggerSideEffects(oldState, newState, action); }

把這兩者分開,你可以獨立測試你的狀態轉移邏輯,而不需要真的去寄一封 email。

用資料結構思考

在下次準備寫一長串 if-elseswitch 之前,停下來問自己:

  1. 我處理的這些規則,它們的本質是什麼?是一系列的判斷,還是一張查詢表?
  2. 我能否把這些規則提取成一個獨立的資料結構(物件、Map、陣列)?
  3. 如果我這樣做了,我的主要邏輯會不會變得更簡單、更通用

今日重點

  • 用表描述狀態與轉移,流程只讀規則表。
  • 減少分支,讓規則可視化。
  • 擴充時只改資料,避免動到流程。

Code is complicated. Data is simple.
把你的複雜性放進資料結構裡,用查表法讓狀態清晰可控,然後讓你的程式碼變優雅且簡單。

這才是真正的專業除臭。


上一篇
Day 17- 分離關注點:設定與主要邏輯分開
系列文
消除你程式碼的臭味18
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言