分離命令與查詢 (Command-Query Separation)

★★★★★Draft

FromClean Code

📚 From the Books

分離命令與查詢 (Command-Query Separation)Functions

函式要嘛做一件事(改變狀態),要嘛回答一個問題(回傳資訊)——不要兩者兼做。一個既改狀態又回傳值的函式,會讓呼叫端在讀程式碼時無法確定:這行是為了副作用,還是為了那個回傳值?

🧠 Why It's a Problem

WARNING

最隱蔽的違例是「順手回傳」:addItem 加完商品後「順便」回傳總價。呼叫端很可能只想加商品,卻被迫接收一個它沒要的值;而真正想拿總價的人,又得先產生一次副作用才拿得到。

⚖️ Case Study

前 — 一個方法又改狀態又回傳
class Cart {
  constructor() {
    this.items = [];
  }

  // 混合 command 與 query:加了商品,又回傳總價
  addItemAndReturnTotal(item) {
    this.items.push(item);
    return this.items.reduce((sum, i) => sum + i.price, 0);
  }
}

// 呼叫端:這行是為了「加商品」還是為了「拿總價」?兩者都有,意圖不明
const total = cart.addItemAndReturnTotal(book);
後 — 命令歸命令,查詢歸查詢
class Cart {
  constructor() {
    this.items = [];
  }

  // Command:改變狀態,不回傳值
  addItem(item) {
    this.items.push(item);
  }

  // Query:回傳資訊,不改變狀態(可安全反覆呼叫)
  totalPrice() {
    return this.items.reduce((sum, i) => sum + i.price, 0);
  }
}

// 呼叫端意圖分明:先命令,再查詢
cart.addItem(book);
const total = cart.totalPrice();

addItem 只做事、totalPrice 只答問。每一行的目的都寫在臉上,totalPrice() 也能隨意多呼叫幾次而不改變任何狀態。

TIP

判準很簡單:函式名是動詞命令(savedeleteadd)就別回傳領域資料;函式名是問句(isEmptytotalPricegetUser)就別偷改狀態。 名字的詞性應該和它的職責一致。

🔑 Takeaways

✍️ My Notes

No notes yet — jot your takeaways or Q&A here.

📖 Further Reading