「這段程式碼明明已經檢查過一次了,後面怎麼又檢查一次?看起來像是複製貼上留下的重複邏輯,我幫你清掉了。」
如果你用過 AI agent 幫忙整理程式碼,這句話大概不陌生。AI 很擅長抓出「看起來多餘」的東西——重複的條件判斷、看似冗餘的檢查、繞了一圈才到終點的邏輯。大多數時候,這種抓錯直覺是對的。但今天要講一個它判斷錯的案例:一段「看起來多餘」的檢查,其實是整個系統唯一擋住一個真實 bug 的防線,而它會被清掉,根源不是 AI 不夠仔細,是沒有任何地方寫著「這段程式碼為什麼長這樣」。
假設有一個訂單服務,負責接收付款閘道回傳的付款結果通知(webhook),並把訂單狀態更新為「已付款」。程式碼裡有一段邏輯,在真正更新訂單狀態之前,會先查一次「這筆訂單是不是已經是已付款狀態」,如果是,就直接回傳成功、不做任何事:
❌ AI 看到的樣子(沒有任何說明):
function handlePaymentWebhook(orderId, payload) {
const order = orderRepository.findById(orderId);
// 這段檢查看起來跟上面 findById 之後應該做的事重複了?
if (order.status === 'paid') {
return { success: true };
}
order.markAsPaid(payload.transactionId);
orderRepository.save(order);
return { success: true };
}
AI 讀到這段程式碼時,看到的只有語法本身:一個訂單物件、一個狀態檢查、一個更新動作。從語意上看,「查出訂單、判斷狀態、標記已付款」這個流程裡,「如果已經是已付款就提早回傳」看起來像是一段可有可無的保護,甚至可能被解讀成「開發者當初寫的時候還沒想清楚流程,順手加了一個防呆檢查」——這正是「看起來像技術債」的典型樣貌:多一道檢查、多一層分支,卻說不出具體在防什麼。
於是 AI 給出的重構建議是:拿掉這段檢查,因為 markAsPaid 這個方法內部理論上可以自己判斷要不要真的執行更新,重複的狀態檢查只是增加程式碼複雜度。
實際上,這個付款閘道有一個已知但沒有文件記錄的行為:同一筆付款結果,偶爾會因為閘道端的重送機制,被送達兩次 webhook 通知——可能間隔幾秒,也可能間隔幾分鐘。這不是這個系統的 bug,是外部依賴的已知限制,團隊當初面對這個限制時做了一個明確決定:在真正執行「標記付款」的動作之前,先確認這筆訂單還不是已付款狀態,如果已經是了,直接視為成功但不重複執行——避免重複觸發下游動作(例如重複發送出貨通知、重複扣減庫存)。
這個決定沒有寫成 ADR,只存在寫這段程式碼的人腦子裡,或者曾經在某個已經被關閉的 issue 討論串裡出現過。當時的程式碼審查者知道這件事,所以沒有多問;但這個知識沒有被沉澱下來,變成任何人(不管是後來加入的工程師,還是 AI agent)讀這段程式碼時能查得到的東西。
AI 把這段檢查拿掉之後,程式碼看起來更「乾淨」,單元測試(如果測試本身沒有涵蓋「同一筆訂單收到兩次 webhook」這個情境)可能還是綠燈——直到正式環境某一天,同一筆付款真的被重送了兩次 webhook,系統對同一筆訂單重複執行了下游動作,才有人發現這段「多餘的檢查」原來不多餘。
AI 判斷一段程式碼「看起來多餘」的依據,只有它當下讀到的語法結構,沒有任何管道能取得「這段程式碼是為了迴避哪個外部系統的已知限制」這種脈絡。 這跟這個系列前面幾天討論的其他情境是同一個根本問題的不同外殼:AI 對眼前這段程式碼的判斷,永遠只能建立在它實際查得到的資訊範圍內;沒有 ADR,這段程式碼「為什麼長這樣」這個資訊就完全不存在於 AI 能查到的範圍,它只能靠程式碼本身的語意去猜,而語意猜測猜不出「這是為了迴避一個外部依賴的已知限制」這種脈絡。
用一組對照來看,如果這段程式碼旁邊有 ADR 支撐,情況會完全不同:
✅ 有 ADR 支撐的版本:
// 見 ADR-0007:付款 webhook 冪等性處理
// 付款閘道已知會偶發重送同一筆付款通知(間隔數秒至數分鐘),
// 這段檢查確保重複通知不會重複觸發下游動作(出貨通知、庫存扣減)。
// 拿掉這段檢查前,請先確認付款閘道的重送行為是否已經改變。
if (order.status === 'paid') {
return { success: true };
}
同一段程式碼,多了三行註解跟一個 ADR 連結,AI 讀到的資訊完全不同——它現在知道「這段檢查有具體要防的情境」,就算它還是想確認這個理由現在還成不成立,至少不會在完全不知情的狀況下把它當成技術債清掉。這正是這個系列反覆講的一件事:不是要求 AI 更小心,而是把它需要的查證資訊,放進它查得到的地方。
回想你手上系統裡有沒有一段「看起來多餘、但沒人敢動」的程式碼?你自己知道它為什麼存在嗎?如果答案是「不知道,但聽說不能動」,這正是下一個會被 AI(或新加入的工程師)誤判成技術債的候選對象。
明天要往上一層,講 CLAUDE.md/skill 這類規範文件,跟今天講的 ADR 這類決策紀錄文件,兩者的分工在哪裡——為什麼不能把 ADR 的內容全部塞進 CLAUDE.md,也不能指望 skill 取代 ADR 的角色。