iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
AI Engineering

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

Day 14:依賴注入容器——架構約束力還是另一層隱藏耦合?

  • 分享至 

  • xImage
  •  

前言:用了 DI 容器,架構就自動變乾淨了嗎?

「我們全面導入依賴注入容器了,所有類別都透過建構子拿依賴,架構應該乾淨很多了吧?」

這句話只對了一半。依賴注入容器確實是這個系列前幾天講的架構原則(介面切分、依賴反轉)最常見的實現工具——它強迫你把依賴宣告在建構子上,逼你依賴介面而不是具體實作。但今天要講一個容易被忽略的另一面:DI 容器在強制一種耦合消失的同時,悄悄引入了另一種耦合——只是這種耦合不在程式碼裡,在容器設定裡,程式碼本身完全看不出來。

今日目標

  • 理解 DI 容器如何同時扮演「架構約束工具」跟「隱藏耦合來源」這兩個角色
  • 看清楚「讀程式碼看不出實際依賴關係」這件事,對 AI 協作造成什麼具體麻煩
  • 認識容器設定本身也是一種需要被查證、而不是被假設的資訊
  • 建立「型別提示是介面」不等於「依賴關係是透明的」這個判斷習慣

DI 容器解決的問題:把依賴宣告攤在建構子上

先講清楚 DI 容器的架構價值。沒有 DI 容器時,一個類別要用到另一個依賴,最直接的寫法就是在方法內部直接 new 出來,或者呼叫一個全域單例——這兩種寫法都讓依賴關係藏在方法內部,讀介面/建構子完全看不出來,測試時也沒辦法替換掉這個依賴。

DI 容器強迫依賴宣告攤在建構子的參數列表上,這件事本身確實是架構約束——它讓「這個類別依賴什麼」變成一個可以從簽章一眼看出的事實,也讓依賴反轉(型別提示介面而不是具體類別)有了落地的地方。

但「依賴什麼」不等於「依賴哪個實作」

問題出在下一步:建構子參數寫的是 PaymentGatewayInterface,這件事只回答了「這個類別依賴一個付款閘道的抽象契約」,完全沒有回答「執行時,容器實際會塞進哪一個具體實作」。這個答案通常寫在容器的綁定設定裡——可能是一份獨立的設定檔,可能是啟動時執行的一段綁定程式碼,而這份設定不會出現在使用這個依賴的類別附近,甚至可能散落在專案的另一個角落。

用一組對照來看這個落差:

❌ 只看建構子,看不出實際依賴的實作:
class CheckoutService
{
    public function __construct(
        private PaymentGatewayInterface $gateway
    ) {}
}
→ 讀到這裡,只知道「依賴某個付款閘道」,
  不知道正式環境實際綁定的是哪一個具體實作,
  也不知道這個實作有沒有被其他綁定條件(環境變數、feature flag)覆寫過

✅ 容器綁定設定跟依賴使用處放在一起查證,而不是分開假設:
// container-bindings.php
$container->bind(PaymentGatewayInterface::class, function () {
    return match (env('PAYMENT_PROVIDER')) {
        'stripe' => new StripeGateway(),
        'sandbox' => new SandboxGateway(),
        default => throw new RuntimeException('unknown provider'),
    };
});
→ 實際綁定的實作,取決於一個環境變數,
  這個資訊只存在綁定設定裡,讀 CheckoutService 本身完全看不到

這正是 DI 容器架構兩面性的核心:它讓「依賴什麼抽象」變透明,卻讓「依賴哪個具體實作」變得更不透明——因為這個答案從「就寫在呼叫的那一行」搬到了「要另外去查容器設定才知道」。

為什麼這件事對 AI 特別麻煩

一個人類工程師在專案裡待久了,通常會累積出「這個介面在這個環境下綁的是哪個實作」的直覺記憶。但 AI 每次接手一個任務時,這種記憶並不存在——它讀到 CheckoutService 的建構子,看到型別提示是介面,如果它止步於「型別提示介面,代表架構是乾淨的」這個結論,就漏掉了「這個介面實際指向哪裡」這個必須另外查證的事實。

這跟這個系列的主題句是同一件事的另一種樣貌:AI 對「這段程式碼依賴什麼」給出的「已確認」結論,如果只查了型別提示、沒有查容器的實際綁定設定,那個結論的查證範圍就沒有涵蓋到真正決定執行期行為的地方。 一個常見的具體後果是:AI 要修改或測試一段依賴付款閘道的邏輯時,以為自己面對的是介面契約,實際上正式環境綁定的實作有著介面沒有寫明的額外行為(例如重試邏輯、超時設定),AI 沒有查證綁定設定,就對這段邏輯的行為做出了錯誤假設。

這不是要你別用 DI 容器

這裡要澄清一個容易被誤解的方向:這篇的重點不是「DI 容器有問題,不該用」——依賴反轉、可測試性這些好處是真實的,放棄 DI 容器只會讓耦合問題變得更直接、更難處理。重點是誠實面對 DI 容器把耦合「搬家」了,而不是「消除」了——耦合從程式碼裡的具體依賴,變成容器設定裡的綁定關係,這個耦合依然存在,只是換了一個更難被隨手讀到的地方存在。

具體的因應做法,是把容器的綁定設定當成跟程式碼同等重要的查證對象——AI(或任何協作者)在對一段有依賴注入的程式碼做判斷之前,同樣要去查一次「這個介面在當前情境下實際綁定的是什麼」,不能只憑型別提示是介面,就假設依賴關係已經完全透明。

今日思考題

回想你手上的專案:如果有人問你「這個介面在正式環境實際綁定的是哪個實作」,你能不看容器設定檔就答出來嗎?如果不能,那 AI 在讀這段程式碼時,跟你面對的是同一個資訊落差。

今日重點回顧

  • DI 容器的架構價值是把「依賴什麼」攤在建構子上,強制依賴反轉、支援可測試性
  • 但「依賴什麼抽象」不等於「依賴哪個具體實作」——後者的答案通常藏在容器綁定設定裡,跟依賴使用處分開存放
  • 這個落差對 AI 特別麻煩:AI 容易把「型別提示是介面」直接當成「依賴關係已經透明」,漏掉還要另外查證綁定設定這一步
  • 不是要放棄 DI 容器,是要把容器綁定設定當成跟程式碼同等重要的查證對象

明日預告

明天用一個具體案例,把今天講的兩面性落地:一次因為沒有查證容器綁定設定,讓架構邊界在實務上變得模糊的真實陷阱。


上一篇
Day 13:案例——AI 為了「遵守分層」過度抽象化一個簡單功能
下一篇
Day 15:案例——DI 容器讓架構邊界變得模糊的真實陷阱
系列文
AI 寫 Code 之後,我們還需要 Software Architecture 嗎?15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言