iT邦幫忙

2026 iThome 鐵人賽

DAY 26
0
AI Engineering

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

Day 26:案例——忽略依賴方向規則的教訓,一次「看起來方便」的捷徑

  • 分享至 

  • xImage
  •  

前言:那個捷徑,三週後找上門了

Day 06 講過依賴方向規則為什麼容易被忽略——它不會讓測試變紅,也不會讓程式碼跑不起來。今天用一個具體案例,走完一次違反這條規則後,代價實際上是怎麼發生的:不是當下爆炸,而是幾週後,另一個完全不相干的任務,被這個捷徑卡住了。

今日目標

  • 看一個「看起來方便」的捷徑,怎麼在事後變成一個實際的阻礙
  • 理解反向依賴的代價為什麼常常是「延遲兌現」,不是立即發生
  • 認識「這段邏輯以後會不會被別的地方重用」這個問題,AI 通常沒有能力預判
  • 學會在做出這類捷徑決定時,至少把代價記下來,而不是讓它悄悄發生

案例:一個修 bug 時的順手 import

情境是這樣:系統裡有一個底層的「金額格式化」工具函式,原本只負責把數字轉成帶千分位的字串。某次修 bug 時,需求是「格式化的時候,如果使用者的介面語系設定是某幾種特定語系,千分位符號要跟著換」。

這個語系設定,原本存在一個屬於「使用者偏好設定」模組的物件裡——那是一個明確的上層模組,底下管理使用者的通知偏好、介面主題這些跟使用者互動相關的設定。AI 接到這個任務時,最短路徑很直接:讓格式化工具函式直接 import 使用者偏好設定模組,取出語系欄位,加一個 switch 判斷就解決了。

這個修法當下能動,測試也綠燈——測試給定一個使用者偏好設定物件,驗證格式化結果符合預期,完全沒有問題。

用一組對照來看這個決定:

❌ 順手捷徑:底層格式化工具直接依賴上層使用者偏好設定
namespace App\Formatting;

use App\UserPreference\UserPreferenceRepository; // 底層依賴了上層!

class MoneyFormatter
{
    public function __construct(
        private UserPreferenceRepository $preferences
    ) {}

    public function format(float $amount, int $userId): string
    {
        $locale = $this->preferences->findByUserId($userId)->locale;
        return $this->formatWithLocale($amount, $locale);
    }
}
→ 測試只需要塞一個假的使用者偏好設定就能過,
  但 Formatting 這個「應該是最底層、最通用」的工具模組,
  現在跟 UserPreference 模組綁死了

✅ 依賴方向正確:呼叫端把需要的資訊傳進來
namespace App\Formatting;

class MoneyFormatter
{
    public function format(float $amount, string $locale): string
    {
        return $this->formatWithLocale($amount, $locale);
    }
}
→ Formatting 模組不需要知道「語系是從哪個模組查出來的」,
  呼叫端(不管是網頁請求、批次任務、還是報表產生器)
  自己負責把 locale 準備好再傳進來

三週後,另一個團隊想重用同一段邏輯

這個捷徑上線後,沒有任何人注意到它——直到三週後,另一個團隊要做一個完全獨立的批次任務:定期產生財務報表,把系統裡累積的金額資料格式化成報表檔案。這個批次任務跑在背景排程裡,沒有「目前登入的使用者」這個概念,自然也拿不到任何使用者的偏好設定。

這個團隊原本以為可以直接重用 MoneyFormatter,畢竟這就是系統裡負責格式化金額的地方——結果發現這支類別的建構子綁死了 UserPreferenceRepository,而批次任務的執行環境裡根本沒有「使用者」這回事,硬塞一個假的使用者 ID 進去顯得很怪,也完全不符合語意。最後這個團隊選擇另外寫一段一模一樣的格式化邏輯,繞過 MoneyFormatter,因為那比搞清楚怎麼在批次環境裡生出一個合法的使用者偏好設定物件要快。

這正是反向依賴的典型代價:它不會讓最初那個任務失敗,卻讓「格式化金額」這件事在系統裡,從一個可以被任何地方安心呼叫的工具,變成一個綁死在特定情境(有登入使用者)的東西——重複實作,就是這個限制的直接後果。

為什麼 AI 沒能預判這件事

在那個當下,AI 判斷「這樣改能不能解決眼前的問題」是對的——需求確實被滿足了,測試也確實驗證了正確的行為。它沒有、也沒辦法預判到「三週後會有另一個團隊,在一個完全沒有使用者概念的情境下,想重用這段邏輯」。這不是 AI 能力不足的問題,是這類判斷本質上需要對系統未來的使用情境有預期,而這種預期通常不寫在任何一份需求文件裡。

依賴方向規則的價值,正是在這裡填補這個空缺:它不要求 AI(或任何人)預判未來所有可能的重用情境,只要求一條簡單、可以立即檢查的原則——底層工具模組不依賴任何特定的上層業務情境。守住這條規則,「這段邏輯以後能不能在其他情境重用」這個問題,就不需要每次都靠預判來回答,答案是「本來就可以」。

今日思考題

回想你手上系統裡有沒有一段「工具/共用邏輯」,其實悄悄綁死了某個特定情境的物件?如果今天有人想在完全不同的情境下重用它,你有把握這件事能順利做到嗎?

今日重點回顧

  • 反向依賴的代價常常是延遲兌現的——當下能動、測試能過,代價在未來某個不相干的任務裡才浮現
  • 底層工具模組一旦綁死某個上層情境的具體物件,就從「任何地方都能安心呼叫」變成「只能在那個情境下使用」
  • AI 判斷一個修法安不安全,依據的是眼前的需求跟測試,沒辦法預判未來所有可能的重用情境——這正是依賴方向規則要負責補上的空缺
  • 守住依賴方向,等於不用每次都靠預判回答「這段邏輯以後能不能被重用」,答案內建在架構裡

明日預告

明天要換一個更大的題目:遺留系統的架構重建——手上一套完全沒有清楚邊界的舊系統,該先畫邊界,還是先補測試?兩條路徑各自的風險是什麼。


上一篇
Day 25:案例——清楚的介面邊界如何讓一次大範圍重構變得可控
系列文
AI 寫 Code 之後,我們還需要 Software Architecture 嗎? 共 26 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言