iT邦幫忙

2026 iThome 鐵人賽

DAY 18
0
Software Development

AI 時代的 Clean Code:30 天讓 AI 產出的程式碼可讀、可驗證、可維護系列 第 25 篇

Day 25|AI 寫完功能、測試全綠,為什麼還會重複通知?

  • 分享至 

  • xImage
  •  

安安~我是ChiYu~

昨天我補強了 Dependency Rule 與 Architecture Test,確保 Use Case 不會直接依賴資料庫或外部 SDK。不過,那組測試只守住 Use Case;Controller 仍能另開一條通知路徑,繞過既有的 Outbox/Dispatcher。

這次我請 Agent 新增「人工重送逾期通知」API。大約七分半後,找不到資料、狀態不符與單次成功三類明列測試全部通過。接著我連續呼叫同一條 API 兩次,Recording Gateway 卻留下兩次通知嘗試,新的 Controller Action 也完全沒有經過既有 Outbox。

候選沒有違反我交付的狹窄需求,問題出在完成定義太窄:它只驗收「單次呼叫能不能動」,沒有定義相同操作重送、Provider 失敗、Lost ACK,以及 202 Accepted 究竟承諾什麼。

唯讀審查完成後,我最後把 retry 定義為「重新排定同一筆失敗通知」,沿用原本的 Outbox Id 與 Idempotency Key;修正版本也讓 Controller 回到 Use Case,再由既有 Dispatcher 統一處理通知。今天要追查的是:為什麼功能能執行、測試全綠,仍然可能留下功能與結構傷害?

這個 Demo 管理一組工作項目。工作項目逾期後,系統必須送出通知。原本流程會先把「這則通知需要處理」記錄到資料庫,再由背景程序呼叫外部通知服務。

這條路徑用來處理外部服務失敗、程式中斷、重複呼叫與回應遺失。人工重送 API 一旦跳過它,單次呼叫雖然可能成功,原本的重試與去重能力也跟著消失。

flowchart LR
    A[既有逾期處理] --> B[Use Case]
    B --> C[Outbox]
    C --> D[Dispatcher]
    D --> H[Notification Sender]
    H --> E[Provider]

    F[AI 新增的人工重送 API] --> G[Controller]
    G --> H

〈傷害〉提醒工程師:功能錯誤、結構惡化與真實世界影響都要有人負責

Clean Code 談完命名、函式、類別與架構後,到了軟體工藝,問題會回到工程責任:功能做錯、結構變差,或軟體開始影響真實世界時,不能只用「需求已完成」結案。

本篇把傷害分成三層,但只對有證據的範圍下結論:

層次 實際可能發生什麼 今天如何處理
功能傷害 系統交付錯誤結果,例如重複扣款、錯誤授權,或 API 回覆成功但工作沒有可靠保存 用重複 POST 檢查通知嘗試,再確認 202 Accepted 背後有沒有持久化的通知意圖
結構傷害 同一條規則分散在 Controller、Worker 與 Adapter,下一次修改容易只改到其中一條路徑 從完整 Diff 與 Architecture Test 檢查 Controller 是否繞過 Outbox,建立第二條通知流程
社會傷害 軟體錯誤開始影響安全、隱私、金融、醫療或公共服務 本篇沒有真實事故證據,只討論工程師不能忽略可能受影響的人

這個 Demo 沒有真實使用者或事故資料,因此本次只主張兩項結果:功能上觀察到兩次通知嘗試;結構上觀察到 Controller 繞過既有路徑。社會傷害只用來劃出工程責任,不會寫成已發生的事故。

今天也會多次碰到 202 Accepted。這個狀態碼表示伺服器已接受請求,但處理尚未完成;它不會替 Work Item API 定義「接受的是一筆已持久化工作、一次即時呼叫,還是只是收到 Request」。這層產品語意仍要由 User 補上。

為什麼故意只給 AI 成功路徑的驗收條件?

我刻意把 Prompt 縮到正常路徑,只要求 Agent 處理找不到資料、狀態不符與單次成功;重複請求、外部失敗及可靠重送全部留到第二階段審查。

實驗從昨天已通過 Dependency Rule 檢查的接受版本開始,保留相同的 API、Outbox/Dispatcher、模型、推理強度與既有測試,只改變這次交給 Agent 的需求與驗收範圍。

實驗項目 設計
固定條件 相同基準版本、Codex GPT-5.6-SOL-HIGH、既有程式碼與測試
Agent 收到的任務 找不到資料回 404、狀態不符回 409,單次重送被接受處理時回 202
刻意未指定 重複請求、Provider 失敗、Lost ACK 與 Outbox 契約
第一階段要觀察 Agent 是否完成 User 明列的需求
第二階段要觀察 狹窄的完成條件漏掉哪些功能與結構風險

我交給功能交付 Agent 的需求如下:

新增:
POST /api/work-items/{id:guid}/overdue-notification/retry

明確行為:
- 找不到工作項目時回傳 404 Not Found。
- 工作項目不是 Overdue 時回傳 409 Conflict。
- 對逾期工作項目送出單次請求時,必須嘗試一次一般逾期通知,並回傳 202 Accepted。
- 加入能從公開 HTTP API 證明上述三條行為的必要測試。
- 維持所有既有測試通過。

限制:
- 不修改既有 Route、JSON Contract、SQLite Schema、背景 Worker、Outbox Dispatcher 或通知 Provider。
- 不新增套件、Migration、外部網路呼叫或部署項目。
- 以最小 Diff 完成,不順便重構其他 Controller Action。
- 本階段不要自行補上尚未明列的 HTTP 重送冪等、Outbox 持久化、Provider 失敗重試與 Lost ACK 契約。

第一個 Session 只驗證 Agent 能不能照明列需求交付;候選固定後,第二個唯讀 Session 再檢查完成條件漏了什麼。這樣才能分開兩個問題:Agent 有沒有照做,以及 User 給的完成定義夠不夠完整。

測試只檢查找不到、狀態衝突與單次成功,沒有碰重複請求及 Outbox

Agent 新增的測試確認三件事:找不到工作項目時回覆 404、狀態不符時回覆 409,單次正常請求會得到 202。

它們沒有回答相同請求送出兩次會怎樣、通知服務失敗時 Caller 會看到什麼,也沒有確認新的入口是否沿用既有 Outbox。綠燈的範圍,到測試斷言為止。

Action 本身不長,問題集中在三個設計決定:

  1. Controller 直接呼叫 IWorkItemNotificationSender。
  2. 每次 Request 都建立新的 Idempotency Key。
  3. Sender 回傳成功或失敗,最後都回覆 202 Accepted。

完整 Action 如下:

[HttpPost("{id:guid}/overdue-notification/retry")]
public async Task<IActionResult> RetryOverdueNotification(
    Guid id,
    [FromServices] IWorkItemNotificationSender notificationSender,
    CancellationToken cancellationToken)
{
    var item = await database.WorkItems.FirstOrDefaultAsync(
        workItem => workItem.Id == id,
        cancellationToken);

    if (item is null)
    {
        return NotFound();
    }

    if (item.Status != "Overdue")
    {
        return Problem(statusCode: StatusCodes.Status409Conflict);
    }

    _ = await notificationSender.SendOverdueAsync(
        new WorkItemNotification(
            item.Id,
            item.Title,
            item.Priority,
            item.DueAtUtc,
            item.Assignee),
        Guid.NewGuid().ToString("N"),
        cancellationToken);

    return Accepted();
}

單次成功路徑可以執行,重送契約仍然沒有成立。這段程式碼把資料查詢、資格判斷、通知 Mapping、冪等身分與外部副作用全放進 Controller,也建立了第二條通知路徑。

把程式碼與既有通知路徑放在一起看,必須分開「已觀察」「可由 Code 判讀」與「依結構推論」三種證據:

發現 分類 證據強度
相同 POST 產生兩次通知嘗試 功能傷害 測試已重現
Sender 正常回傳 false 時仍回 202 契約缺口 Controller 丟棄回傳值,可由 Code 直接判讀;false 應對應什麼 HTTP 行為仍待 User 定義
每次 Request 都建立新的 Idempotency Key 功能風險 可由 Code 與既有去重規則推論
Controller 繞過 Outbox/Dispatcher 結構傷害 Diff 已直接觀察
Retry、Cancellation 與 Lost ACK 可能分岔 後續結構風險 依目前兩條通知路徑推論

我把這份輸出保存成固定候選 Commit。後續審查、紅燈測試與修正 Diff 都從同一份程式碼開始,不再追著持續變動的工作目錄跑。

固定候選版本後,再開一個全新的唯讀 Session 檢查傷害

我把第二階段稱為傷害審查(Harm Review):先固定候選版本,再逐項列出受影響對象、Repository 證據、證據強度與決策責任。這個 Session 仍使用相同模型與推理強度,但沒有前段對話,也沒有修改權限,只能審查,不能看到問題就順手把它修掉。

這樣可以保留原始問題與證據,也減少實作者替自己的設計補理由。它只能隔離工作角色與對話狀態,不能等同於不同模型或人工 Reviewer 的獨立判斷。

這份 Prompt 不要求 Reviewer 立刻修 Code。它要先回答誰可能受影響、手上有什麼證據、證據強度到哪裡,以及誰有權決定:

功能傷害:
- 404、409 與 202 是否和實際行為一致?
- Provider 回傳 false、擲出例外或收到 Cancellation 時,Caller 會看到什麼?

具證據的營運風險:
- 相同 HTTP 請求重送或使用者連點兩次,Provider 可能收到幾次通知?
- Idempotency Key 是否代表同一個邏輯操作?
- Lost ACK 後重送,是否可能再次產生外部效果?
- 失敗後是否有可查詢、可重試的持久化通知意圖?

結構傷害:
- Controller 是否新增資料查詢、Mapping、冪等鍵與副作用協調責任?
- 人工重送是否繞過既有 Outbox/Dispatcher?
- 兩條通知路徑的錯誤、Retry、Cancellation、Lost ACK 與 Idempotency 語意是否一致?
- 昨天的 Architecture Test 為什麼仍然可能全綠?

每個發現都要列出:
傷害類型、受影響對象、證據、證據強度、風險、決策責任與建議處置。

唯讀審查找到四個缺口:通知失敗仍回「已接受」、相同請求重複嘗試、Lost ACK 與通知路徑分岔

審查 Agent 判定這份候選必須修正後再審。我把它的發現和主流程驗證整理成下表:

類型 受影響對象 證據 證據強度 最後由誰決定
false 仍回 202 API Caller、客服 Controller 丟棄 Sender 的回傳值 可由 Code 直接判讀 API 契約負責人
相同 POST 造成兩次通知嘗試 通知接收者、客服、營運人員 主流程連續呼叫兩次後,記錄型測試替身留下同一 Work Item 兩筆紀錄 已在測試觀察 產品與工程共同決定重送語意
Lost ACK 後可能再次送出 通知接收者、維運人員 每次 Request 都產生新的冪等鍵,Provider 無法辨認同一操作 由 Code 與既有去重規則直接推論 系統設計與營運負責人
Controller 建立第二條通知路徑 後端維護者、Reviewer Diff 顯示 HTTP Action 直接呼叫 Sender,繞過 Outbox/Dispatcher 已觀察到結構變化;未來維護成本屬設計判斷 維護團隊與 Merge Reviewer

這四項結果的證據強度不同。重複通知嘗試由測試實際重現;Controller 繞過 Outbox 可以從 Diff 直接判讀;Lost ACK 則是根據現有程式碼與去重規則推論的風險。

三種證據必須分開標示,才不會把合理擔憂寫成已經發生的事故。

受影響對象與 AI 候選止損流程

圖:唯讀 Harm Review 先固定候選、列出受影響對象,再暫停合併、補紅燈與修正;速度越快,止損越要提前。

寫下紅燈測試前,User 必須先決定 retry 是重送舊通知,還是建立新通知

我把候選固定在 day-25-harm-candidate,停止合併與後續擴充。審查已經指出風險,但測試要有預期答案,我必須先決定:retry 到底代表重試同一筆通知,還是建立一筆新的通知?

Agent 可以提出方案,卻不能替產品決定 retry 代表什麼。這裡有兩種合理但完全不同的需求:

產品真正想做的事 操作身分 適合的設計
重試同一筆失敗通知 沿用原有身分與冪等鍵 重新排定既有 Pending Outbox
再建立一筆新的通知 建立新的操作身分 新增 Outbox、錯誤契約與重送規則

這次已有一筆失敗後仍可追蹤的 Pending Outbox,所以我把 retry 定義為「重新排定同一筆失敗通知」。它必須沿用原本的 Outbox Id 與 Idempotency Key。

我把專案契約定成這樣:

  1. retry 重新排定既有的 Pending Outbox。
  2. 202 Accepted 表示既有通知意圖已成功重新排定。
  3. 重複 POST 沿用同一筆 Outbox、同一個 Id 與同一把冪等鍵。
  4. 找不到 Work Item 回 404;狀態不符或沒有 Pending 通知時回 409。
  5. HTTP Action 不直接呼叫 Provider。

RFC 9110 沒有替 Work Item API 保證通知已保存或送達,它只定義「請求已被接受,處理尚未完成」。本專案另外承諾既有 Pending Outbox 已成功重新排定;Demo 目前沒有公開的狀態查詢端點,因此非同步處理的可觀察性仍不完整。

如果產品要的是「再寄一封新的通知」,就應該建立新的操作身分、錯誤契約與重送規則。這次選擇重用既有通知意圖,因為 Repository 已有一條能追蹤與重試的 Outbox/Dispatcher 路徑。

連續呼叫兩次相同 API,測試替身記下兩次通知嘗試

連續兩次 POST 覆蓋的是「同一項邏輯操作再次進入 API」:可能來自使用者連點、Client 自動重送,或 Lost ACK 後重新送出。這項測試沒有在網路層真正製造 Lost ACK,只檢查重送進入端點後會發生什麼。

測試使用 Recording Gateway,也就是只記錄外部呼叫、不會真的寄送通知的測試替身。既然這次把 retry 定義成重新排定既有 Outbox,HTTP Request 內就不該直接呼叫 Provider;兩次 POST 完成後,通知嘗試紀錄應該仍是空集合:

await client.PostAsync(retryRoute, content: null, cancellationToken);
await client.PostAsync(retryRoute, content: null, cancellationToken);

Assert.Empty(factory.Notifications.AttemptedWorkItemIds);

候選卻留下同一個 Work Item 的兩筆紀錄,因此紅燈成立:

Assert.Empty() Failure: Collection was not empty
Collection: [同一個 WorkItemId, 同一個 WorkItemId]

這個實驗觀察到兩次通知嘗試,不能擴大寫成使用者真的收到兩封通知。Provider 最後是否去重、接受或實際送達,都不在這項測試的證據範圍內。

同一份契約也規定:沒有 Pending Outbox,就沒有可以重新排定的通知,API 應回 409 Conflict。候選只檢查 Work Item 是否為 Overdue,所以仍回沒有內容的 202 Accepted,第二項紅燈也成立。

重複請求與錯誤成功回應造成的功能傷害

圖:相同 POST 被賦予新冪等鍵,造成兩次通知嘗試;Provider 回傳 false 仍得到 202,都是原本斷言沒有涵蓋的行為。

原本的 Architecture Test 只掃描 Use Case,所以沒有抓到 Controller 直接呼叫 Sender

原本的 Architecture Test 只檢查 Use Case,確保高階流程不直接依賴 EF Core、ASP.NET Core 與外部 SDK。這份候選沒有修改 Use Case,而是從 Controller 直接呼叫 Notification Sender,因此躲過了原本的掃描範圍。

行為測試與架構測試保護的是不同問題。重複 POST 測試檢查外部結果;Architecture Test 則檢查已明列的依賴規則。

我因此再補一項 Source Scan,明確禁止 WorkItemsController 直接出現 IWorkItemNotificationSender。候選立刻亮紅燈,錯誤也指出這次的違規來源。

這項檢查能抓住本次發現的直接依賴,卻不能證明整個 Repository 都不存在其他繞道。未來若出現其他 Controller、別名或 Wrapper,規則仍要跟著擴充。

Controller 新增第二條通知路徑造成結構傷害

圖:既有路徑透過 Outbox/Dispatcher;Controller 直送新增第二條路徑,讓 Retry、Cancellation 與 Lost ACK 語意分岔。

flowchart TD
    A[AI 完成明列需求<br/>現有測試全部通過]
    A --> B[固定候選版本<br/>交給全新唯讀 Session 審查]
    B --> C[暫停合併]
    C --> D[由 User 定義 retry、202 與冪等契約]
    D --> E[相同 Request 觸發兩次通知嘗試<br/>功能傷害]
    B --> F[Controller 繞過 Outbox<br/>結構傷害]
    E --> G[修正後重新執行行為與架構驗證]
    F --> G

修正後,人工重送重新使用既有 Outbox/Dispatcher

修正後,由 Use Case 定義人工重試需要的能力與結果:

public interface IOverdueNotificationRetryScheduler
{
    Task<OverdueNotificationRetryScheduleResult> ScheduleAsync(
        Guid workItemId,
        CancellationToken cancellationToken);
}

EF Core Adapter 負責尋找既有的 Pending Outbox,將 NextAttemptAtUtc 調整成現在,並清除已過期的 Lease。Controller 只把 Use Case 結果轉成 202、404 或 409。

修正前後的差別,可以直接從通知路徑看出來:

比較項目 原候選 接受版本
外部通知呼叫位置 Controller 直接呼叫 Sender Dispatcher 統一處理
重複 POST 每次建立新身分並嘗試通知 重新排定同一筆 Pending Outbox
冪等鍵 每次產生新值 沿用原本的值
Provider 呼叫時機 HTTP Request 內立即呼叫 背景流程稍後執行
架構保護 原測試沒有掃描這個入口 已知的直接 Sender 依賴會讓測試失敗

連續呼叫兩次相同 API 後,Provider 還沒有被直接呼叫,資料庫也仍然只有同一筆 Pending Outbox。後續由既有 Dispatcher 執行時,才會產生一次通知嘗試。

功能與結構驗證都通過後,我才接受這份版本。理由是重送契約已經明確,人工與自動通知也重新共用 Outbox/Dispatcher;測試總數與 Diff 大小不參與這次決策。

〈行為與結構皆無缺陷〉:API 回應正確,通知路徑也不能分岔

就那份狹窄需求而言,原候選完成了 Make It Work:Route 可以呼叫,三種明列狀態都有回應,測試也全數通過。修正後才完成這個情境下的 Make It Right:相同操作不會悄悄換身分,通知路徑也回到同一個可靠邊界。

行為方面,人工重送必須沿用同一筆通知意圖,不能因 Client 重送而產生新的冪等身分。結構方面,人工與自動通知都要經過 Outbox/Dispatcher,否則 Retry、Cancellation、Lost ACK 與 Mapping 會分散成兩套規則。

工程師也是系統技術健康的利害關係人。「盡力做到最好」不等於追求完美或無限延期。低風險的結構問題可以登記成技術債;已知會造成重複副作用、錯誤成功回應或可靠性路徑分岔時,工程師就有責任先停止交付。

這次實驗只確認 Agent 約七分半就產生了候選,不能用來證明 AI 一定會放大傷害。我的工程判斷是:產碼速度變快後,錯誤模式也能更快被複製到其他入口,所以止損必須提早發生。

AI 候選合併前,外部行為與內部結構要分開驗收

重複 POST 測試與 Architecture Test 分別失敗,因此我把 AI 產出的完成條件拆成兩道 Gate。

行為 Gate 檢查成功、失敗、取消、重送、冪等與外部副作用;結構 Gate 檢查新增路徑、重複政策、依賴繞道與下一次修改位置。任何一道沒有通過,候選就先停止合併。

Gate 要檢查什麼 這次如何驗證
行為 Gate 成功、失敗、取消、重送、冪等、錯誤語意與外部副作用 公開 HTTP 測試、Outbox 狀態、Provider 嘗試次數與系列 Smoke
結構 Gate 重複路徑、政策散落、Mapping 重複、依賴繞道與下一次需求的修改位置 完整 Diff、Architecture Test、通知路徑比較與傷害審查

這些判斷適合整理成可重複使用的 Repository Instruction。以下先用 AGENTS.md 格式示範,尚未寫入 API Demo;系列後段再把累積的 Clean Code Policy 整理成 Skill。

把高風險變更、必查項目與人工否決權寫進 Harm Review Policy

## Harm Review Policy

- 測試全綠只代表已執行的斷言通過,不得直接視為可以合併。
- 資料、授權、交易、通知、背景工作或公開契約異動,合併前必須執行傷害審查。
- 完成定義同時包含行為 Gate 與結構 Gate;任一 Gate 失敗,就停止合併目前候選,也不得再以這份候選為起點擴充後續功能。
- 停止後固定候選 Commit,搜尋相同模式的修改範圍,再補上能重現問題的行為測試或 Architecture Test。
- 行為審查至少涵蓋成功、失敗、取消、重送、重複執行、冪等與錯誤語意。
- 結構審查至少涵蓋新增路徑、重複政策、Mapping、依賴繞道與下一次修改位置。
- 外部副作用允許重送時,實作前必須定義邏輯操作身分與穩定的 Idempotency Key。
- 審查發現必須標記為「已觀察」「可由 Code 推論」或「仍待驗證」,並列出受影響對象與決策責任。
- 暫時接受風險或技術債時,必須留下理由、影響範圍、負責人與重新處理條件;高風險變更保留人工否決權。

### Harm Review Decision

- 行為與結構 Gate 都通過:可以接受候選。
- 契約明確,而且問題能以測試重現:修正候選後重新驗證。
- User 尚未定義副作用、重送或失敗語意:停止實作,先補決策。
- 候選建立第二條高風險副作用路徑:拒絕合併,除非能提出必要性與隔離方式。
- 暫時接受技術債:記錄理由、影響範圍、負責人、檢討日期與重新處理條件。

CLEAN 原則:A 留下傷害證據,N 守住重送時的可預期行為

A — Auditable by Evidence 實據可審

「測試全綠」只能證明已執行的斷言通過。這次真正影響決策的證據包含原始 Prompt、候選 Diff、重複 POST 的紅燈、Architecture Test 的掃描缺口,以及 User 最後定義的重送契約。哪些由測試觀察、哪些能從 Code 判讀、哪些仍是風險推論,都要分開記錄。

N — Non-Surprising Behavior 符合預期

相同邏輯操作被重送時,系統不應悄悄建立新的通知身分。Caller 收到 202 Accepted,代表既有通知意圖已重新排定,後續仍由 Dispatcher 處理;它不代表通知已經送達。

本篇只使用 A — Auditable by Evidence 實據可審 與 N — Non-Surprising Behavior 符合預期:A 負責區分觀察、判讀與推論,N 負責固定重送及 202 Accepted 的對外語意。C、L、E 不是這次接受候選的主要判準。

唯讀審查多花一個完整 Session,本次抓到三類漏測風險

唯讀審查不是免費的,它多花了一個完整 Session。兩個 Session 的任務不同,Token 不能直接比較;本次能確認的是,額外審查找出了重複請求、回應語意與依賴繞道。

通知、交易、授權與背景工作涉及高風險副作用,我會保留這道審查;低風險格式調整則不必一律採用相同強度。

功能測試通過後,還要驗收通知契約與結構

回到標題,這份候選會在測試全綠後重複通知,是因為原本的斷言只驗收單次成功,並且允許 Controller 直接建立新的通知身分與外部路徑。Agent 完成了被明列的工作,沒有替 User 補上未定義的重送契約。

我最後接受的不是「多補幾個測試」而已,而是先由 User 定義 retry、202 Accepted 與冪等身分,再讓人工與自動通知重新共用 Outbox/Dispatcher。行為 Gate 防止相同操作產生第二次副作用;結構 Gate 則防止另一條路徑再次繞過既有保證。

這次實驗只能說明:在這份 Prompt 與 Work Item API 裡,成功路徑全綠仍漏掉兩次通知嘗試與一條依賴繞道。它不能證明所有 AI 候選都會如此,也不能把測試全綠直接當成傷害已經不存在。

明天再處理下一個問題:當 AI、測試與 Reviewer 都說完成,另一位工程師或另一個乾淨執行流程,需要哪些版本、環境與原始輸出,才能重跑並確認同一項結論?

想重現這次實驗

閱讀本文不需要先下載 Demo。完整 Prompt、實驗起點、候選版本、接受版本與原始驗證結果都保留在公開 Repository,想進一步檢查時再使用以下連結與 Git Tag。

實驗起點:

git fetch --tags
git switch --detach day-24-dependency-rule

候選版本:

git fetch --tags
git switch --detach day-25-harm-candidate

接受版本:

git fetch --tags
git switch --detach day-25-harm-behavior-structure

參考資料


上一篇
Day 24|Use Case 直接依賴 DbContext,架構測試為什麼還是綠燈?用 Dependency Rule 找出掃描盲點
下一篇
Day 26|AI 說測試全綠還不夠:如何固定版本、重跑驗證,留下可查的完成證據?
系列文
AI 時代的 Clean Code:30 天讓 AI 產出的程式碼可讀、可驗證、可維護 共 27 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言