iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0
Claude AI

用 AI Agent 重構一套無框架的 legacy PHP 系統系列 第 11

Day 11:廠商 API 遷移實戰——legacy 接線如何拆成獨立 package

  • 分享至 

  • xImage
  •  

前言:介面化一支函式很簡單,一整家廠商呢?

「昨天講的 PSR-17/18 介面化,聽起來就是把一支函式包一包,套用到整套廠商接線應該也差不多吧?」

如果只有一支函式,確實差不多。但 legacy 系統裡一家外部廠商的接線,很少只有一支函式——認證邏輯、資料查詢邏輯、狀態同步邏輯、回應格式解析,往往散落在好幾支檔案裡,甚至同一段邏輯被複製貼上到不同的呼叫端,各自加了一點微調。這時候問題已經不是「這支函式能不能測試」,而是「AI 根本沒辦法窮舉這家廠商的邏輯到底散落在系統裡多少個地方」。

這正是 Day 01 那句話的另一種樣貌:AI 對著眼前這一處呼叫端說「已經確認過了,改這裡是安全的」,但它沒有辦法確認的是——系統裡還有沒有其他呼叫端,也依賴著同一家廠商、同一套沒被它看到的邏輯。

今日目標

  • 理解為什麼「散落各處的廠商接線」比「單一函式沒有介面」更難讓 AI 安全動手
  • 認識把 legacy 廠商接線拆成獨立 package 時,Config/Client 的職責怎麼切分
  • 看一組「接線散落各處」vs「收斂進獨立 package」的具體對照
  • 建立「package 邊界」這個讓 AI 改動範圍可控的具體工具

散落各處的接線,AI 窮舉不完的不只是「幾支檔案」

裸 curl 呼叫(Day 10 講的問題)是「這一段程式碼沒辦法被驗證」,而散落各處的廠商接線是更大一層的問題:這家廠商的邏輯,到底被複製貼上到系統裡多少個地方?各自有沒有悄悄修改過? AI 改掉它找到的那一處,測試綠燈,看起來沒問題——但它沒有辦法保證,還有沒有另一個呼叫端,用著同一家廠商但版本不同步的邏輯。

這不是危言聳聽的假設。廠商的 API 協定會改版、認證方式會調整、回應格式會微調——如果同一家廠商的接線邏輯散落在三、四個地方,每次協定變動,理論上要同步改三、四處,但實務上常常只改了 AI 當下看到的那一處,其他地方繼續用著舊版邏輯,直到某天正式環境噴錯才被發現。

把接線收斂進獨立 package:職責先切乾淨

解法是把一家廠商的整套接線邏輯,拆成一個獨立的 package,內部至少切成兩個明確的職責:

  • Config:這家廠商的連線資訊怎麼來(host、認證憑證、逾時設定),跟「怎麼打這支 API」的邏輯完全分開。呼叫端只需要知道「我要打這家廠商」,不需要知道憑證存在哪裡、怎麼組出來。
  • Client:實際組 request、送出、解析 response 的邏輯,內部依賴 Day 10 講的 PSR-17/18 介面,測試時可以換成 mock client。Client 只暴露「查詢清單」「查詢狀態」這類業務語意的方法,不暴露任何 HTTP 傳輸細節給呼叫端。

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

❌ 接線散落各處:
// 呼叫端 A
$response = curl_post($vendorHost . '/order/list', $authHeader, $params);
// 呼叫端 B(另一個功能,複製貼上時微調過參數順序)
$response = curl_post($vendorHost . '/order/list', $params, $authHeader);
→ 同一家廠商,兩處呼叫端的參數順序不一致;
  AI 改動任何一處都無法確定另一處是否也需要同步修改,
  甚至可能根本不知道還有另一處存在

✅ 收斂進獨立 package:
$client = VendorFactory::client($vendorId);
$orders = $client->listOrders($params);
→ 呼叫端只依賴一個穩定介面,
  參數順序、認證方式、URL 組成全部收斂在 package 內部,
  改動這家廠商的邏輯只需要改一個地方,
  且 package 邊界本身就是 AI 改動範圍的具體邊界

package 邊界的價值,不是「程式碼比較整齊」這種表面理由,而是把「AI 沒辦法窮舉的散落範圍」,變成「AI 只需要在這個資料夾裡找」的具體邊界。 呼叫端還有多少處在用這家廠商,仍然要靠 grep 或 IDE 的引用查詢去確認,但至少「這家廠商的邏輯本身長什麼樣子」不再是一個要在好幾支檔案裡拼湊的問題。

遷移時最容易被忽略的一步:先確認舊接線的行為,不是先寫新 package

實際遷移時,AI 最容易犯的錯誤是太快動手:看到散落的呼叫端,覺得邏輯不複雜,直接照著自己的理解寫一個新 package。但舊接線裡往往藏著只有踩過坑才會知道的細節——某個欄位在特定情況下要轉型、某個錯誤碼要特殊處理、某個逾時設定是為了因應這家廠商偶爾回應很慢。這些細節可能沒有寫在任何文件裡,只存在於舊程式碼本身。

正確的順序是:先把舊接線的行為,用測試斷言下來(哪怕測試本身很醜、只是把現有行為釘住),再開始寫新 package,最後用新舊行為比對(呼應 Day 06 講的乾淨基準比對)確認新 package 的行為跟舊接線完全一致,才把呼叫端一個個切換過去。先釘住行為、再重構,而不是先重構、再祈禱行為沒變——這條紀律在單一函式的介面化裡已經重要,在整套廠商接線的遷移裡更重要,因為要重建的邏輯範圍更大,AI 憑理解重寫時漏掉細節的機率也更高。

原則語言無關,package 化的具體做法是這個生態的選擇

把散落的第三方整合邏輯收斂成一個邊界清楚的模組是語言無關的設計原則——這件事跟 Day 08 講的 Repository 收斂裸寫 SQL 是同一種思路,只是收斂的對象從「資料庫存取」換成「外部廠商整合」。今天用 PHP 的 composer package 當實現方式,換成 Java 可能是獨立的 Maven 模組,換成 Node.js 可能是獨立的 npm 套件或 monorepo 裡的一個 package——工具不同,但「先把散落邏輯圈出一個邊界,讓改動範圍變得可控」這個判斷邏輯是一致的。

今日思考題

回想你手上系統裡跟某個外部服務整合的邏輯:如果要列出「這家服務的邏輯到底散落在系統裡哪些地方」,你有把握一次列完嗎,還是得靠反覆 grep、每次都可能漏掉一兩處?

今日重點回顧

  • 散落各處的廠商接線,比單一函式沒有介面更危險——AI 沒辦法窮舉這家廠商的邏輯到底散落在系統裡多少地方
  • 收斂進獨立 package 時,Config(連線資訊)跟 Client(業務邏輯 + PSR-17/18 傳輸介面)要切分清楚
  • package 邊界把「AI 沒辦法窮舉的散落範圍」變成「AI 只需要在這個資料夾裡找」的具體邊界
  • 遷移順序:先用測試釘住舊行為,再寫新 package,最後比對新舊行為一致才切換呼叫端
  • 核心原則語言無關:把散落的第三方整合邏輯收斂成邊界清楚的模組,PHP 用 composer package,其他語言各有等效工具

明日預告

明天要講收斂進 package 之後、容易被忽略的下一個問題:這個廠商 package 本身,能不能依賴外層專案的 vendor 目錄?答案是不能,而背後的理由,關係到這個 package 未來能不能被安全地獨立測試、獨立升級。


上一篇
Day 10:外部 API 呼叫的介面化——從裸 curl 到 PSR-17/PSR-18
下一篇
Day 12:package 隔離規則——為什麼廠商 package 不能依賴外層專案的 vendor
系列文
用 AI Agent 重構一套無框架的 legacy PHP 系統13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言