iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0

轉換一個場景,到另外一個專案!

同一個團隊、差不多的時期,另一個大型系統的遷移案。這個案子的失敗方式,跟前面那個剛好相反。前四天講的那個案子是:功能做錯了。多欄位、驗證失效、按鈕沒反應。這個案子是:

功能測試全部正常,但客戶認為不符合開發規範。

這裡有三個詞要先分開,不然後面會讀錯——而這三個詞的差別,是這整個系列最常被混用的地方

  回答的問題 誰說了算 可以模糊嗎
需求 為什麼要做這件事? 業務/使用者 ✅ 可以,它本來就是願望
規格 做出來要長什麼樣、怎麼驗? 寫規格的人 ❌ 不行,模糊的規格驗不了
開發規範 要怎麼寫這段程式? 維護這套系統的團隊 ❌ 不行,但它常常只存在習慣裡

差別最好記的講法是:

需求      「我要能快速找到訂單」
規格      「查詢支援訂單編號、日期區間、客戶名稱;回應時間 2 秒內」
開發規範  「查詢邏輯放 Repository,不可以寫在 Controller 裡」

三句話講的是同一個功能,但沒有一句可以互相取代。

而這兩個案子壞的位置不一樣:

  需求 規格 開發規範
前一個案子 ⚠️ 有,但只在客戶腦子裡 只有一份 Prototype
這一個案子 ✅ 清楚 沒問題(348/361 條測試通過) 問題全部在這裡

兩件不一樣的東西,而它們的失效方式也不一樣。

而中間那個「翻譯」的動作,以前是隱形的

上面那三行——需求、規格、開發規範——中間有兩次翻譯:

需求  →(翻譯一)→  規格  →(翻譯二)→  程式碼

翻譯一是把「我要能快速找到訂單」變成「支援三種查詢條件、兩秒內回應」。
翻譯二是把規格變成符合團隊寫法的程式碼。

在 AI 進來之前,這兩次翻譯常常是同一個人在做,而且是在寫程式的過程中順手做掉的。他讀了需求、心裡補出規格、然後照著他熟悉的團隊慣例寫下去。沒有人為那兩次翻譯估過工時,因為它們沒有名字。

而現在,翻譯二可以交給 AI——它產碼的速度是人的好幾倍。

翻譯一不行。「快速」是多快、「找到」是查得到還是查得準、要不要支援模糊比對——這些只有懂業務的人答得出來,而 AI 只會挑一個看起來合理的答案填進去。

所以真正的變化是這個:

翻譯二變快了十倍,翻譯一沒有變。
而以前它們是同一個人順手做完的,現在中間裂開了。

那個裂縫,就是前一個案子掉下去的地方——Prototype 被當成規格,等於宣布「翻譯一已經完成了」,但它其實一個字都沒翻。

而這一個案子掉的是另一個裂縫:翻譯二的依據錯了。 我們拿到的那份開發規範,不是那個團隊真正在用的那套。

先講清楚一件事:規範我們有,而且做成了機器檢查

這一段我原本寫得太簡略,讓它讀起來像「我們沒理規範」。事實剛好相反。對方確實提供了開發規範文件。 而且不是薄薄幾頁。是兩三百頁。我們也沒有把它當參考。我們把它做成了 AI 產出的品管站:規範裡的每一條,變成產碼之後要跑的檢核。

拿到兩三百頁的規範,還把它自動化了。在那個當下,我認為這件事沒有比這更紮實的做法。

所以當時我是很有信心的。我可以說:這個產出絕對符合規範。

然後客戶說不符合。

兩把尺,都在量「合規度」

問題出在哪,是後來才弄明白的:

客戶真正在用的規範,跟他提供給我們的那份文件不一樣。

這不是「文件過期」那麼單純。真正的問題在那份文件的身分:

那兩三百頁是一份「交付文件」,不是「人們心中的規範」。

它存在的理由是合約裡要有這樣一份東西。它被寫出來、被交付、被歸檔。但沒有人真的拿它當日常判斷的依據。他們真正在用的那套,是團隊內部的共識、是習慣、是「大家都這樣寫」,從來沒有被寫下來過。

而這裡有一個很反直覺的後果:頁數多,反而更危險。

五頁的規範   →  你知道它不完整,你會去問
三百頁的規範 →  你以為規範已經講清楚了,你不會去問

我們沒有去問。因為兩三百頁看起來太像「已經講清楚了」。

更難看的是:我們把它自動化了。 而自動化只會讓這件事更嚴重。照著一份不是共識的文件做得越徹底,產出離他們心裡那套就越遠,而且過程中一路都是綠燈。

於是變成這樣:

我們的尺(照那份文件建的檢查)    →   通過
他們的尺(沒有寫下來的那一套)    →   60%

兩把尺都在量「合規度」,量出兩個數字。

而這件事最難處理的地方是:我們的檢查是綠的,而且它沒有壞掉。 它忠實地執行了我們拿到的那份規範。壞掉的是規範本身。

所以這個案子留下的那句話是這樣:

在 AI 開發的世界,你很在意的東西務必要講清楚、說明白。
否則 AI 會完成你的目標——而你看起來就是很奇怪。

而「講清楚」不等於「寫很多」。那兩三百頁就是最好的反證。

我後來認為真正該做的動作只有一個,而且它一點都不技術:

拿著那份文件去問一句:「這裡面哪幾條是你們真的在看的?」

這句話花不到十分鐘。而我們沒有問,因為文件看起來已經夠厚了。

一件我不太願意承認的事:AI 時代,溝通反而更重要

這個系列從頭到尾都在講機制。把規則推到機器擋得住的那一層、把規格寫成可以翻成斷言的形狀、把檢查做成閘門。

而這個案子提醒我一件跟那個方向相反的事。

機制只能承載已經被講清楚的東西。它沒有辦法幫你發現「你以為講清楚了、其實沒有」。因為機制不會質疑它拿到的那份規範,它只會忠實地執行。以前這件事沒那麼要命,因為工程師會在做的過程中察覺不對勁,然後回頭問一句。而 AI 不會問。 它拿到兩三百頁,就照兩三百頁做,而且做得又快又整齊。

所以自動化提高的是「執行的忠實度」,不是「那份規範的正確度」。
而前者提高之後,後者的錯誤會被放得更大。

這就是為什麼我認為在 AI 時代,那個「花十分鐘去問一句」的動作變得比以前更值錢,不是更沒必要。

第三種失敗:規格說了、AI 做了、驗收驗錯地方

架構改善計畫(三階段,一週 + 兩週 + 一個月)留下的量化指標:

指標 現況 目標
架構合規度 60% 95%
程式碼重複率 40% < 15%
錯誤處理一致性 60% 95%
單元測試覆蓋率 85% 90%
API 文件完整度 70% 90%

把兩個數字並排看:

單元測試覆蓋率  85%   ← 測試是綠的
架構合規度      60%   ← 但驗收不過

這個案子的測試361 條裡 348 條通過(96%)。功能大致是對的,剩下的十幾條是零星案例,不是結構性錯誤。

而客戶看的不是功能。

https://ithelp.ithome.com.tw/upload/images/20260906/20178262ZtXuI02EJQ.png

三階段改善計畫裡,沒有一項是在修 bug

三階段的改善計畫裡,做的事情長這樣:

移除 60 幾個 try-catch。

每一支 Controller 都自己包 try-catch 做錯誤處理。改成統一由一個基底控制器處理,錯誤回應格式標準化。

移除 19 個查詢方法。

Service 層讀寫混在一起。同一個 Service 既有 GetXxx 又有 UpdateXxx。要拆成讀寫分離:查詢走 Queries、命令走 Service。

手動映射改成映射框架。

DTO 和 Entity 之間全部手寫轉換程式碼,所以重複率 40%。還有 Infrastructure 分層、Controllers 分層、ViewModels 與 Validators 建立⋯⋯

注意這些改動的共同點:沒有一項是在修 bug。

功能一開始就是對的。改的全部是結構。

它不是在違反規範,是在遵守它唯一知道的範本

這一段是重點。

你想一下:如果你叫一個 AI 寫一支 .NET 的 API Controller,沒有給它任何範例,它會寫成什麼樣子?它會寫成:

[HttpGet]
public async Task<IActionResult> GetData(...)
{
    try
    {
        var result = await _service.GetDataAsync(...);
        return Ok(result);
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "...");
        return StatusCode(500, "發生錯誤");
    }
}

每一支都自己包 try-catch。

這個寫法沒有錯。網路上九成的 .NET 教學都這樣寫,它能跑、能過測試、錯誤也有處理。如果你請一個沒看過這個專案規範的工程師來寫,他大概也會這樣寫。

問題是這個專案的規範是:**統一由基底控制器處理例外,Controller 不准出現 try-catch。**那份規範,AI 沒看到。或者說,它看到了,但它不在產碼當下的注意力範圍內。同樣的道理套用到另外幾項:

  • Service 讀寫混寫?一般 .NET 專案就是這樣寫,CQRS 是選配
  • 手動映射?寫起來最直觀,而且不用多裝套件
  • 沒有 ViewModels 分層?小專案確實不用

AI 產出的每一個決定,單獨看都是「合理的一般做法」。

加起來就是架構合規度 60%。

測試量的是行為,而驗收量的是結構

這就是我覺得這個案子最值得講的地方。

前一天講 UI 的沉默約束時提過一組對照。這裡要再加一行:

API 違反 OpenAPI    →  CI 紅燈
DB  違反 schema     →  寫入失敗
UI  違反設計稿      →  什麼都不會發生
程式碼違反開發規範  →  什麼都不會發生,而且測試還是綠的

**最後這一行最陰險,因為它不只沒有紅燈,還有一個綠燈在誤導你。**348/361 條測試通過、覆蓋率 85%。這些數字全部是真的,而且看起來很健康。團隊每天看著綠燈開發,直到驗收。

我後來把這件事想成一句話:

你的測試在驗證「你自己定義的對」,驗收在驗證「客戶定義的對」,而這兩者之間沒有橋。

單元測試驗的是行為:輸入 A 得到 B。它天生驗不了結構。它不會告訴你「這支 Controller 不該有 try-catch」「這個 Service 不該有查詢方法」。

順帶一提:客戶感受得到的,只有他碰得到的那一面

這個案子的驗收標準是「架構合規度」,那是很技術的東西。而前一個案子的驗收標準是客戶的印象——而印象只來自他碰得到的那一面

那個案子的後端幾乎沒出事,但客戶最後認定「品質不好」,全部是因為前端驗證。你很難用「可是我們後端有三百條測試」去扭轉那個印象。

推論很現實:驗證資源不該平均分配。 不是每個模組都要 80% 覆蓋率,而是要問——哪一塊是客戶會直接碰到、碰壞了會形成印象的?(後面會講到同一個現象的另一個版本:安全網分布不均,會決定你敢改什麼。)

代價:三階段重工

三階段的改善計畫:緊急修正一週、重要優化兩週、持續改善一個月。那是重工。 功能沒有變、使用者感受不到任何差別、對客戶來說沒有新增任何價值。純粹是把已經寫好而且會動的程式碼,改成符合規範的樣子。

這是最好的情況。如果那份規範是在專案後期才被拿出來對照,或者中間已經蓋了更多東西上去,成本會再翻倍。

那規範到底該怎麼寫

先講一個事實:那份客戶的開發規範是存在的,是一份正式文件。但它從頭到尾只是一份文件——沒有任何一支腳本、任何一道 CI 檢查、任何一個 hook,會因為違反它而擋下來。

寫到這裡我想插一句實務上的觀察。

很多人第一次聽到「架構合規度」會覺得這是形式主義。**程式會動就好,管它有沒有 try-catch。**我以前也這樣想過。但做過幾個大型專案之後,我的看法變了:

  • 那 60 幾個散落的 try-catch,代表錯誤回應格式不一致 → 前端要為每支 API 寫不同的錯誤處理
  • 讀寫混在一起,代表沒辦法對查詢單獨做快取或讀寫分離 → 效能優化沒有著力點
  • 重複率 40%,代表同一個邏輯改一次要改四個地方 → 維護期的成本

規範不是為了好看。它是為了讓這套系統在交出去之後還改得動。而客戶(特別是要接手維護的客戶)非常清楚這件事。

**但這一篇的教訓是另一個方向:規範有價值,不代表「規範文件」有價值。**那兩三百頁裡,真正被拿來判斷的可能只有十幾條。而我們花力氣把兩三百頁自動化,不如花十分鐘問出那十幾條是哪幾條。

規範的價值不在它涵蓋了多少,在它有多少條是真的會被拿來判斷的。
前者可以外包給文件,後者只能靠問。

兩種失敗,斷在同一條線的兩端

現在把兩個案子放在一起,用開頭那三個詞看:

  前一個案子 這一個案子
需求 有,但只在客戶腦子裡 ✅ 清楚
翻譯一(需求 → 規格) 沒有發生——Prototype 被當成規格 ✅ 完成了
規格 ❌ 只有畫面 ✅ 沒問題(348/361 條通過)
翻譯二(規格 → 程式碼) 照 Prototype 做了 ⚠️ 依據錯了
開發規範 ❌ 拿到的不是真正在用的那套
結果 客戶說品質不好 客戶說不符開發規範
代價 187 張問題單、9 輪測試、停擺約三週 三階段重工

看起來是兩種完全不同的失敗。而它們斷在同一條線的兩端。

一個是翻譯一沒有人做——沒有人把「我要一個跟舊系統一樣的新系統」翻譯成「這一頁應該檢查哪些東西」。
一個是翻譯二的依據是錯的——我們照著一份文件翻,而那份文件不是他們心裡那套。

而兩件事的共同點只有一句:

AI 兩次翻譯都做得又快又好。
問題出在沒有人檢查它拿到的東西對不對。

最後留一個問題給你

這兩個案子,你猜哪一個先發生?

我到現在都覺得這個問題比答案本身有意思,因為不管答案是哪一個,推論都一樣不舒服

  • 如果規範案先發生,那我們學到的是「規範要寫清楚、要做成機器檢查」——然後下一個案子敗在規格。
  • 如果遷移案先發生,那我們學到的是「規格不能只給 Prototype」——然後下一個案子規格很清楚,敗在規範。

兩種順序,同一個結局:上一個案子學到的教訓,剛好防不住下一個案子。

而這件事我在 Day 03 講過一次,只是那時候的尺度小得多——一份回顧擋不住下一輪的問題單。現在把尺度放大到專案層級,它長的是同一個樣子。

答案我明天講。(如果你已經猜到了,那你大概也猜得到明天那句「同一個原因」是什麼。)

本系列所有案例均經去識別處理,不指涉任何特定客戶、系統或產業。


上一篇
Day 7 - 技術升級,還是規格變更?
下一篇
Day - 9 兩個案子,同一個原因
系列文
綠燈不等於做對:AI 開發的驗收工程 ——從兩個失敗的案子,到驗收交付13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言