iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0
AI Engineering

AI 寫 Code 之後,我們還需要 Software Architecture 嗎?系列 第 8

Day 08:案例——AI 在沒有清楚模組邊界時,如何不小心跨模組耦合

  • 分享至 

  • xImage
  •  

前言:明明只是修一個小功能,為什麼牽連了另一個模組?

「我只是想讓訂單頁面多顯示一個欄位,AI 怎麼會改到用戶模組的程式碼?」

這是進入第二部後我想先處理的問題。前面幾天講過 Repository 分層、介面切分、模組邊界這些工具本身該怎麼設計;但工具存在,不代表 AI 會自動遵守它。今天用一個具體案例,示範「沒有清楚模組邊界」時,AI 怎麼在解決一個看起來很小的問題時,不小心把兩個原本該獨立變更的模組焊死在一起——當下能動、測試也過,代價要等到下一次「只想改其中一個模組」時才會顯現。

今日目標

  • 看一個具體案例:AI 為了完成一個小功能,跨模組直接依賴了另一個模組的內部實作
  • 理解「當下能動」跟「架構上安全」是兩個獨立的驗收標準
  • 認識隱性依賴為什麼比顯性依賴更危險——它不會讓任何測試變紅
  • 建立辨識「這個改動是不是跨了模組邊界」的具體檢查方法

案例:訂單頁面多顯示一個欄位

情境是這樣:訂單模組(Order)跟用戶模組(User)是兩個獨立的業務模組,各自有自己的資料模型、自己的服務層,理論上只透過對方模組公開的介面互動。需求是「訂單列表頁多顯示一個『會員等級』欄位」。

AI 拿到這個任務,最快的路徑是這樣:

❌ 直接依賴另一個模組的內部實作:
// 在 Order 模組裡
use App\User\Internal\UserRecord; // 這是 User 模組內部的資料模型,不是對外公開的介面

class OrderListPresenter
{
    public function present(Order $order): array
    {
        $userRecord = UserRecord::find($order->userId); // 直接查 User 模組的內部資料表
        return [
            'orderId' => $order->id,
            'memberTier' => $userRecord->tier,
        ];
    }
}

這段程式碼能動,測試也會過——因為測試只驗證了「輸出的陣列裡有沒有 memberTier 這個欄位」,沒有驗證「這個欄位是怎麼拿到的」。但 Order 模組現在直接依賴了 User 模組一個沒有被設計成公開介面的內部類別(UserRecord)。

這正是「隱性依賴」最陰險的地方:它不會在任何測試報告裡顯示成紅字。 測試只檢查「輸出結果對不對」,不會檢查「這個結果是透過什麼路徑拿到的」——除非有專門的架構層級檢查,否則沒有任何自動化機制會告訴你「這裡跨了一條不該跨的線」。

對照有清楚邊界時 AI 會怎麼做:

✅ 透過模組公開的介面互動:
// User 模組公開一個介面
interface UserQueryService
{
    public function getMemberTier(int $userId): string;
}

// Order 模組只依賴這個介面
class OrderListPresenter
{
    public function __construct(private UserQueryService $userQuery) {}

    public function present(Order $order): array
    {
        return [
            'orderId' => $order->id,
            'memberTier' => $this->userQuery->getMemberTier($order->userId),
        ];
    }
}

兩個版本的輸出結果一模一樣,測試都會綠燈。差別在於:後者,User 模組往後不管怎麼調整內部資料結構(換資料表、加快取層、改成呼叫另一個服務),只要 getMemberTier() 這個介面的行為不變,Order 模組完全不用跟著改;前者,User 模組任何內部調整都可能悄悄弄壞 Order 模組,而且沒有人會在改 User 模組的當下想到要去檢查 Order 模組有沒有受影響。

為什麼 AI 特別容易寫出第一種版本

AI 面對「訂單列表多顯示一個欄位」這種需求時,會傾向找「最快能拿到這個資料」的路徑——如果 User 模組沒有現成的公開介面可以查會員等級,AI 直接查內部資料表往往比「先去 User 模組新增一個公開方法,再回頭呼叫它」快得多。這不是 AI 判斷力不足,是因為沒有任何東西告訴它「這條路徑是禁止的」。 語言本身不會阻止你 use 另一個目錄底下的類別,除非有模組邊界的規則(不管是文件約定還是工具強制)明確畫出「這裡不能跨」。

跟 Day 02 的案例對照一下:Day 02 講的是「一個功能全部塞進同一個 Controller」,那是模組內部缺乏分層;今天這個案例是模組之間缺乏邊界。兩者是同一種根因在不同尺度上的樣貌——AI 在沒有約束的情況下,永遠會選擇「當下最短的路徑」,差別只在於這條捷徑跨越的是函式邊界、類別邊界,還是模組邊界。

今日思考題

回想你的專案裡有沒有類似的情況:某個模組直接依賴了另一個模組的內部實作,而不是透過公開介面?如果現在要調整被依賴那個模組的內部結構,你有把握列出所有會被波及的地方嗎?

今日重點回顧

  • 隱性的跨模組依賴不會讓任何測試變紅,因為測試驗證的是輸出結果,不是資料取得的路徑
  • AI 在沒有模組邊界約束時,會選擇「當下最短能動」的路徑,這不是判斷力問題,是缺少「這裡不能跨」的訊號
  • 透過公開介面互動 vs 直接依賴內部實作,兩者當下的輸出結果可能完全一樣,差別只在未來能不能獨立變更
  • 這是「模組內部缺乏分層」(Day 02)在「模組之間」尺度上的同一種根因

明日預告

明天要往前一步:光靠介面邊界還不夠,AI 還需要知道「這個設計為什麼是這樣」,不然它很可能把一個刻意的架構決策誤判成技術債——這就是架構決策紀錄(ADR)要解決的問題。


上一篇
Day 07:模組邊界——package/module 隔離對 AI 改動範圍的實際影響
下一篇
Day 09:架構決策紀錄(ADR)——讓 AI 讀得懂「為什麼這樣設計」而不是照抄現狀
系列文
AI 寫 Code 之後,我們還需要 Software Architecture 嗎?11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言