Theme / v4.19.4

Oh My OpenAgent

AI 代理編排系統

實戰範例

實戰範例 010:用 OMO 把一個上帝函式拆成三個,Prometheus 把關影響範圍

中階範例。一個 200 行的 processOrder 上帝函式同時混了驗證、持久化與通知邏輯。Prometheus 先做 inbound 影響分析,再由 Sisyphus 抽出三個聚焦函式、Athena 同步補回歸測試,全程在 Hash-Anchored Edit 保護下零衝突合併。

實戰背景

「上帝函式 (God Function)」是 legacy code 最常見的痛點之一:一個函式同時做太多件事,任何修改都會引發連鎖風險。本範例示範如何用 OMO 的多代理協同,把一個 200 行的 processOrder 拆成三個聚焦函式,並由架構代理 Prometheus 在事前與事後把關影響範圍。

痛點與問題描述

我們的訂單服務有這樣一個函式:

// src/services/orderService.ts(簡化示意)
export async function processOrder(order: Order): Promise<OrderResult> {
  // ── 區塊 A:欄位驗證 ──
  if (!order.items?.length) throw new ValidationError("empty items");
  if (order.totalCents < 0) throw new ValidationError("negative total");
  // ... 約 50 行驗證

  // ── 區塊 B:資料庫持久化 ──
  const conn = await db.connect();
  try {
    await conn.query("BEGIN");
    await conn.query("INSERT INTO orders ...");
    // ... 約 80 行持久化
    await conn.query("COMMIT");
  } catch (e) {
    await conn.query("ROLLBACK");
    throw e;
  }

  // ── 區塊 C:通知(Email / Slack / Webhook)──
  await email.send(order.customerEmail, "訂單成立");
  await slack.notify("#orders", `新訂單 ${order.id}`);
  // ... 約 70 行通知
  return { ok: true };
}

三個關注點全擠在一起,重構時最大的風險不是「拆不開」,而是「拆完之後沒發現某個呼叫端依賴了原本的副作用」。我們讓 Prometheus 來回答這個問題。


規格定義

本次變更計畫的 ID 為 omo-split-process-order

# 實戰範例 010:openspec/changes/omo-split-process-order/specs/order-service/spec.md

## MODIFIED Requirements

### Requirement: Separated order processing concerns
`processOrder` 必須(SHALL)僅負責流程編排,實際工作必須(SHALL)委派給三個獨立函式:
`validateOrder``persistOrder``notifyOrder`

#### Scenario: Behavior preserved after split
- **WHEN** 執行既有訂單整合測試套件
- **THEN** 所有既有測試必須(SHALL)在不修改呼叫端的前提下通過。
- **AND** 三個新函式必須(SHALL)各自有獨立的回歸測試。

端到端 8 輪 AI 協作紀錄

── 第 1 輪:Prometheus 做 inbound 影響分析 ──

  • You:
    幫我釐清 processOrder 在 codebase 裡被誰呼叫,以及這些呼叫端有沒有依賴它的副作用。
  • AI:

    我派出 Prometheus 做架構層級的 inbound 分析,沿著呼叫圖回溯 processOrder 的所有呼叫端。

    Prometheus 找到的 inbound 呼叫路徑:

    processOrder  ←  CheckoutController.placeOrder (src/api/checkout.ts:42)
    processOrder  ←  ReplayJob.replayStaleOrder   (src/jobs/replay.ts:88)
    processOrder  ←  TestHelper.seedOrder         (tests/helpers/order.ts:15)

    Prometheus 的判讀:

    • CheckoutControllerReplayJob 都只消費 OrderResult,沒有讀取 processOrder 內部的通知副作用。
    • TestHelper.seedOrder 直接呼叫 email / slack mock,所以通知邏輯抽到獨立函式後,這個 helper 必須一起更新。

    結論:這次拆分安全,唯一要連動修改的是 TestHelper.seedOrder

── 第 2 輪:起草 OpenSpec 提案 ──

  • You:
    /opsx:propose "將 processOrder 拆成 validateOrder / persistOrder / notifyOrder,並補回歸測試"
  • AI:

    提案 omo-split-process-order 已建立。

    • proposal.md:記錄 Prometheus 的影響分析結論,標明 TestHelper.seedOrder 為連動修改點。
    • specs/order-service/spec.md:定義三個新函式的職責與驗收 scenario。
    • tasks.md:拆成 4 個任務(拆出 3 個函式 + 補 3 套回歸測試 + 更新 helper)。

    輸入 /opsx:apply 即可讓 OMO 開始實作。

── 第 3 輪:啟動 Team Mode 三代理 ──

  • You:
    /opsx:apply
  • AI:

    我以 3 個 worker 啟動 Team Mode,把 4 個任務分配出去:

    任務分派:

    1. Worker: impl-1 (Sisyphus) ── 抽出 validateOrderpersistOrder,掛回 processOrder 編排。
    2. Worker: impl-2 (Sisyphus) ── 抽出 notifyOrder,更新 TestHelper.seedOrder
    3. Worker: tests (Athena) ── 為三個新函式各補 3 個回歸測試。

    因為三個 worker 都會動到 src/services/orderService.ts,Hash-Anchored Edit 控制器已上線。

── 第 4 輪:觀察 Hash-Anchored Edit 在多方寫入時的行為 ──

  • You:
    報告 orderService.ts 上目前的鎖定狀況。
  • AI:

    三個 worker 同時要動同一個檔案,鎖調度特別關鍵。

    Hash-Anchored Edit 目前在 src/services/orderService.ts 上的鎖定分佈:

    • 區塊 validateOrder(第 12-58 行)→ 鎖主 Worker: impl-1
    • 區塊 persistOrder(第 60-140 行)→ 鎖主 Worker: impl-1
    • 區塊 notifyOrder(第 142-205 行)→ 鎖主 Worker: impl-2
    • 區塊 processOrder 編排(第 207-220 行)→ queue,等 impl-1 完成後交接

    Athena 想在檔尾新增 export 語句,但發現它依賴 impl-1 還沒寫完的 validateOrder 宣告,於是自動排在編排區塊之後。零強制覆蓋。

── 第 5 輪:Athena 平行補回歸測試 ──

  • You:
    Athena 那邊在寫什麼測試?
  • AI:

    Athena 在 tests/services/orderService.test.ts 補了三組回歸測試,這裡展示 validateOrder 的部分:

    describe("validateOrder", () => {
      it("rejects empty items", () => {
        expect(() => validateOrder({ ...baseOrder, items: [] }))
          .toThrow(ValidationError);
      });
      it("rejects negative total", () => {
        expect(() => validateOrder({ ...baseOrder, totalCents: -1 }))
          .toThrow(ValidationError);
      });
      it("passes a well-formed order", () => {
        expect(() => validateOrder(baseOrder)).not.toThrow();
      });
    });

    persistOrdernotifyOrder 各有對應的事務回滾與通知失敗重試測試。

── 第 6 輪:合併與全測試 ──

  • You:
    合併三個 worker 的成果,然後跑全測試。
  • AI:

    三個 worker 的變更已合併回主分支,因為全程使用 Hash-Anchored Edit,這次合併零衝突。

    ✔ orderService - validateOrder rejects empty items
    ✔ orderService - validateOrder rejects negative total
    ✔ orderService - persistOrder rolls back on db error
    ✔ orderService - notifyOrder retries on slack failure
    ... (共 18 tests)
    Tests: 18 passed, 18 total.

    包含 TestHelper.seedOrder 在內的所有既有測試也全數通過,印證 Prometheus 在第 1 輪的影響分析正確。

── 第 7 輪:Prometheus 跑 outbound 影響驗證 ──

  • You:
    拆出來的三個新函式,有沒有被意外外露到不該外露的地方?
  • AI:

    再派 Prometheus 做一次 outbound 分析,沿著呼叫圖往下游追 notifyOrder 的對外依賴。

    Prometheus 找到的 outbound 呼叫路徑:

    notifyOrder  →  email.send       (vendor)
    notifyOrder  →  slack.notify     (vendor)
    notifyOrder  →  webhook.retry    (vendor, with backoff)

    三個新函式的對外依賴都落在預期的 vendor 邊界,沒有意外外露到其他業務模組。封裝乾淨。

── 第 8 輪:歸檔與重構收益 ──

  • You:
    收尾,並總結這次重構的收益。
  • AI:

    Archive Complete

    計畫 omo-split-process-order 已歸檔。

    指標 重構前 重構後
    processOrder 行數 200 行 14 行(純編排)
    單一函式最大認知複度 高(混三種關注) 低(各自單一職責)
    該檔案回歸測試數 4 18
    合併衝突 N/A 0(Hash-Anchored Edit 保護)

關鍵學習點

  • 重構前先派 Prometheus 做 inbound 分析,可以事前抓出像 TestHelper.seedOrder 這類「隱性連動點」,避免事後才發現呼叫端壞掉。
  • 上帝函式拆分時,Hash-Anchored Edit 的價值在多 worker 同檔案寫入。即使三個 worker 都改 orderService.ts,鎖調度讓合併依舊零衝突。
  • 事後的 outbound 分析跟事前一樣重要。確認新函式的對外依賴落在預期邊界,等於封裝驗收。
  • OpenSpec 提案在這種規模才真正發揮作用。範例 009 的小工具函式可以略過,但拆分上帝函式這種牽一髮動全身的變更,先把 proposal.mdtasks.md 寫清楚,能讓多代理的分工有明確依據。