iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
AI Engineering

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

Day 13:案例——AI 為了「遵守分層」過度抽象化一個簡單功能

  • 分享至 

  • xImage
  •  

前言:規則立對了,就一定用對了嗎?

「我們已經把架構規則寫清楚了:業務邏輯一律經過 Service 層,不能讓 Controller 直接處理。這樣 AI 應該不會再亂寫了吧?」

昨天談到,架構邊界的價值在於限制 AI「能改到哪裡」;但邊界本身不會告訴 AI「這件事值不值得被邊界收編」。今天用一個具體案例,看規則立對了、卻被用錯地方時,會長什麼樣子——架構規則制止了一種錯誤(邏輯散落各處),卻換來另一種錯誤(把不需要規則的東西也硬塞進規則裡)。

今日目標

  • 看一個「五行函式被包裝成三層架構」的具體案例
  • 理解「這個功能值不值得被架構規則收編」是另一個需要判斷的維度
  • 分辨「過度抽象」跟「保留彈性」這兩件事表面相似、實際不同的地方
  • 建立一個簡單的判斷問題:這一層抽象,是為了因應哪個已知或即將發生的變化?

案例:格式化一個顯示名稱,為什麼需要三個檔案

假設專案裡有一條架構規則:業務邏輯一律要經過 Service 層,Controller 只負責接request、呼叫 Service、回傳結果。 這條規則的初衷很合理——先前的教訓是 Controller 裡塞了太多判斷邏輯,散落各處,改一個規則要改十幾個地方。

現在有個新需求:把使用者的姓名跟暱稱組合成畫面上顯示的名稱,規則是「有暱稱就顯示『暱稱(本名)』,沒有就只顯示本名」。這段邏輯,老實說五行就能寫完:

❌ AI 遵照架構規則,把五行邏輯包裝成三層:

interface DisplayNameFormatterInterface
{
    public function format(User $user): string;
}

class DisplayNameFormatter implements DisplayNameFormatterInterface
{
    public function format(User $user): string
    {
        if ($user->nickname !== null && $user->nickname !== '') {
            return sprintf('%s(%s)', $user->nickname, $user->name);
        }
        return $user->name;
    }
}

class DisplayNameFormatterFactory
{
    public static function create(): DisplayNameFormatterInterface
    {
        return new DisplayNameFormatter();
    }
}

// 呼叫端
$formatter = DisplayNameFormatterFactory::create();
echo $formatter->format($user);
✅ 同樣遵守「邏輯要經過 Service 層」的規則,但沒有過度包裝:

class UserDisplayService
{
    public function formatDisplayName(User $user): string
    {
        if ($user->nickname !== null && $user->nickname !== '') {
            return sprintf('%s(%s)', $user->nickname, $user->name);
        }
        return $user->name;
    }
}

// 呼叫端
echo (new UserDisplayService())->formatDisplayName($user);

兩個版本都沒有違反「業務邏輯要經過 Service 層」的規則——第二版一樣把邏輯放進了 Service,Controller 一樣沒有直接處理判斷邏輯。差別在於,第一版多出了一個介面、一個 Factory,卻沒有任何一個地方用得上「可以替換實作」這個介面帶來的能力,也沒有任何一個地方需要 Factory 決定「該建立哪一種實作」。

為什麼 AI 特別容易這樣做

架構規則對 AI 來說是一種可以被機械套用的模式:「這是業務邏輯」→「業務邏輯要進 Service」→「Service 通常搭配介面,方便將來替換/測試」→「有介面就常常搭配 Factory 決定要 new 哪個實作」。 這一整串推導在很多情境下是對的,問題是 AI 傾向把它當成一個固定公式,套用在任何被規則認定為「業務邏輯」的東西上,而不會回頭問一句:這個功能有沒有替換實作的需求?有沒有多種可能的建立方式需要 Factory 決定?

如果答案都是沒有,那介面跟 Factory 就只是純粹的間接層,除了讓閱讀這段程式碼的人(不管是人還是下一次接手的 AI)多繞兩層路以外,沒有實際的架構效益。架構規則防住的是「邏輯不知道該放哪裡」,防不住「該不該把這段邏輯包裝成可替換的抽象」這個獨立判斷。

過度抽象跟保留彈性的差異在哪裡

這裡容易有一個誤解:「介面讓你能替換實作,聽起來永遠是好事,為什麼不乾脆每個 Service 都先包一層介面?」

差別在於:這個彈性有沒有一個具體、已知或高機率會發生的變化在等著它。 如果這個顯示名稱格式化邏輯,將來真的會因為不同語系(中文姓名習慣 vs 西方姓名習慣)需要不同的組合規則,那介面 + 多個實作 + Factory 決定用哪個,就是提前佈好的合理彈性。但如果只是「理論上以後可能會變」這種沒有具體依據的假設,這層抽象就只是在賭一個可能永遠不會發生的未來,賭輸的成本卻要每個讀這段程式碼的人天天付。

一個簡單能問的問題是:「如果我現在拿掉這個介面,直接呼叫具體類別,會不會讓任何一個已知的需求變得做不到?」 如果答案是不會,這層抽象目前就是多餘的——不代表永遠不能加,而是等到真的出現第二個實作、真的需要替換的那一刻,再加也不遲,而且到那時候你才知道介面該長什麼樣(現在猜的介面形狀,常常猜錯)。

今日思考題

回想你手上系統裡某個「介面只有一個實作、Factory 只會建立一種東西」的地方:如果拿掉這層間接,會不會讓任何一個已知需求變得做不到?如果答案是不會,這層抽象是在因應真實的變化,還是在因應一個「以後可能會需要」的猜測?

今日重點回顧

  • 架構規則能防止邏輯散落各處,防不住「該不該把這段邏輯包裝成可替換的抽象」這個獨立判斷
  • AI 容易把「業務邏輯要進 Service」這條規則,機械式地延伸成「Service 要搭配介面跟 Factory」,即使沒有任何地方真的需要這層間接
  • 判斷一層抽象該不該存在,看的不是「未來理論上可能需要」,而是「現在有沒有具體、已知的變化在等著它」
  • 遵守架構規則跟過度包裝不衝突——兩個版本都合規,差別只在有沒有多餘的間接層

明日預告

明天要換一個角度看架構約束力:依賴注入容器本身,究竟是幫架構邊界加分的工具,還是可能悄悄變成另一層隱藏耦合?


延伸閱讀:本系列另外三個並行系列——《用 AI Agent 重構一套無框架的 legacy PHP 系統》《AI 時代的 TDD》《讓 AI Agent 維護一個 Open Source Project》——會從不同角度處理相關的經驗,有興趣可以一起追。


上一篇
Day 12:架構邊界 vs 過度設計——AI 容易把邊界當成「該加更多抽象層」
下一篇
Day 14:依賴注入容器——架構約束力還是另一層隱藏耦合?
系列文
AI 寫 Code 之後,我們還需要 Software Architecture 嗎?15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言