前一篇,我們從按鈕開始追查事件流程。但找到事件之後,還有另一個問題更容易讓人誤判:
畫面明明有值,資料就真的正確嗎?
前幾天整理這套祖傳系統時,我們先學會把需求與程式片段整理成 AI 看得懂的 Context,接著又從按鈕一路追到背後的事件。這次,維護旅程繼續往資料層深入。
使用者勾選畫面上的選項,DBGrid 立刻顯示新狀態,按下儲存也出現「儲存完成」。可是重新進入畫面後,勾選卻消失了。
如果直接問 AI:
DBCheckBox 明明有勾選,
為什麼資料沒有存進資料庫?
它通常會很快叫我們檢查 DataField、Post 與 ApplyUpdates。這些答案都合理,問題是:合理不等於適用於眼前這套系統。
Legacy System 的儲存動作可能藏在共用父類別、AP、Provider 或另一個 Dataset 裡。看到 Post 就直接補 ApplyUpdates,可能不是修好問題,而是多送一次更新。
所以這次我不讓 AI 直接猜答案。我要它先幫我把散落在 .pas 與 .dfm 的線索接起來,畫出資料從畫面到後端的路徑。
Delphi 只是今天的案例。真正要練習的,是工程師如何限制 AI 的推論範圍,讓它成為 Legacy Code 的閱讀助手,而不是自信滿滿的改碼機器。
過去我常把程式碼丟給 AI,再說「幫我找出問題並修改」。這等於要求它同時猜原始設計、判斷根因並提出修改。只要第一步猜錯,後面寫得再完整也沒有用。
第一輪,我只提供可重現的事實:
這是 Delphi 2009 的資料感知表單。
重現步驟:
1. 在 DBGrid 選取資料。
2. 勾選 DBCheckBox。
3. 按下儲存,畫面顯示成功。
4. 重新進入後,勾選恢復原值。
目前已知:
- 畫面會立即改變。
- 儲存時沒有例外。
- 重新查詢後資料恢復原值。
請先不要修改程式。
請列出還需要哪些 Context,才能判斷資料停在哪一層。
這裡刻意不說「一定漏了 ApplyUpdates」,也不把「畫面有勾選」寫成「資料已存入」。我要 AI 先承認資訊缺口,而不是用通用知識補滿空白。
.pas 與 .dfmAI 回覆需要元件繫結與儲存事件後,我才提供最小片段。
// .dfm:畫面綁定
object ActiveCheckBox: TDBCheckBox
DataSource = AccountSource
DataField = 'IS_ACTIVE'
end
object AccountSource: TDataSource
DataSet = AccountData
end
// .pas:儲存事件
procedure TAccountForm.SaveButtonClick(Sender: TObject);
begin
if AccountData.State in [dsEdit, dsInsert] then
AccountData.Post;
ShowMessage('儲存完成!');
end;
如果沒有接觸過 Delphi,可以先把這兩個檔案理解成不同的證人:
.dfm 告訴我們「畫面上的元件綁到哪個資料來源」。.pas 告訴我們「使用者操作後,程式實際執行哪些事件」。在這個片段裡,.dfm 證明勾選框會把值交給 AccountData;.pas 則只證明按下儲存後執行了 Post。它們都沒有單獨證明資料已經送到遠端 AP 或寫進資料庫。
這正是 AI 容易太快下結論的地方:看到 Post 就以為大功告成,卻還沒確認 ClientDataSet 後面是否存在 ApplyUpdates、共用儲存函式或其他遠端更新流程。
以前我可能在數千行 .dfm 裡反覆搜尋,再切回上萬行 .pas 對名稱。這種機械式比對正適合交給 AI,但任務必須縮小:
請交叉比對兩段內容:
1. 列出 DBCheckBox → DataSource → Dataset → 欄位。
2. 列出儲存事件中的資料操作順序。
3. 只根據已提供內容分析,不假設未提供的函式。
4. 分成「已確認」與「仍需人工驗證」。
5. 不要產生修正版程式碼。
如果沒有後面三項,AI 很容易從「看見 Post」直接跳到「缺少 ApplyUpdates」。但目前只能證明這個事件有 Post,不能證明整套系統沒有共用更新流程。
我期待 AI 回傳的是:
已確認
ActiveCheckBox綁定AccountSource。AccountSource指向AccountData。- 勾選值對應
IS_ACTIVE。- 儲存事件會執行
Post。- 成功訊息在
Post後顯示。仍需驗證
AccountData是否為ClientDataSet。- 後端查詢是否包含
IS_ACTIVE。Post後是否另有共用更新流程。- AP 或 Provider 是否收到更新。
- 顯示成功時,後端是否真的完成儲存。
AI 還沒有找出唯一答案,卻已把兩個檔案的線索整理成待查清單。
AI 負責縮小範圍
工程師負責證明結論
這比直接產生修正版更有價值。因為 Legacy System 最麻煩的通常不是語法,而是眼前十行程式和其他 Unit、Dataset、AP 之間到底有什麼關係。
接著才由工程師實際觀察 State、ChangeCount、共用函式與後端結果。
假設得到:
- 勾選後 State = dsEdit
- Post 後 State = dsBrowse
- Post 後 ChangeCount = 1
- 目前呼叫鏈找不到 ApplyUpdates
- 重新查詢後 IS_ACTIVE 仍為 N
我會把結果回餵 AI:
請根據新的執行證據更新判斷:
1. 哪些假設可以排除?
2. 最可能中斷在哪一層?
3. 修改前還要確認哪些共用流程?
4. 只提出最小修改位置,不要重寫表單。
這時 AI 才有足夠證據逐步收斂,而不是看到 DBCheckBox 就背誦 Delphi 標準答案。
AI 不會因為檔案很長而眼睛疲勞,但仍可能漏掉動態指定的 DataSource、父類別事件、其他 Unit、AP 端 SQL,以及 Trigger、Transaction 或 Lock。
因此,我會補上一條限制:
如果目前檔案無法證明,請標示為「尚未找到」,
不要改寫成「系統不存在」。
在 Legacy System 裡,沒有看到通常只代表還沒挖到那一層。老系統最不缺的,就是地下室。
這篇的實作不是寫修正版,而是整理一份可重複使用的協作格式:
【實際現象】
- 使用者做了什麼?
- 畫面立即出現什麼?
- 重新查詢後變成什麼?
【已知事實】
- 哪些結果已重現?
- 哪些狀態已用除錯器確認?
- 後端是否真的收到請求?
【相關 Context】
- .dfm 的元件片段
- .pas 的事件與直接呼叫函式
- Dataset、DataSource 與欄位用途
- 共用儲存、Transaction、Lock 的範圍
【限制 AI】
- 先建立資料流,不要直接改程式
- 區分已確認事實與待驗證假設
- 找不到時寫「尚未找到」
- 只列出下一步需要的證據
【第二輪回餵】
- Breakpoint 觀察結果
- Dataset State/ChangeCount
- 後端或 SQL 查詢結果
- 實際呼叫到的共用函式
整個協作流程可以濃縮為:
flowchart TD
A[描述現象] --> B[提供最小 Context]
B --> C[要求 AI 建立資料流]
C --> D[區分事實與假設]
D --> E[人工執行驗證]
E --> F[回餵新證據]
F --> G[最後才討論修改]
這張圖最重要的不是箭頭有多漂亮,而是「人工驗證」位在 AI 建立資料流之後、討論修改之前。少了這一步,AI 的合理推測很容易被誤當成已經證實的根因。
今天雖然用到 DBGrid、DBCheckBox 與 ClientDataSet,重點卻不是教 Delphi 元件。
真正重要的是:面對畫面、程式與資料庫結果不一致時,不要直接把問題交給 AI 猜,也不要因為它很快產生一段合理的程式,就以為根因已經找到。
這次採用的協作方式是:
.pas 與 .dfm,讓 AI 跨檔案對照。AI 最適合做的,不是替我們一槍打中 Legacy System 的答案,而是把散落、混亂又難讀的線索,整理成一條可以逐步驗證的路。
如果沒有 Context,也沒有約束推論方式,AI 只能拿通用知識補空白。最後就可能得到一份「每一句都合理,合起來卻不是這套系統」的答案。
所以,與 AI 協作維護 Legacy System 的關鍵,是讓它知道:
哪些是事實、哪些只是推測、下一步要用什麼證據證明。
至於案例最後是否真的缺少 ApplyUpdates,反而不是今天最重要的答案。只要資料流還沒被證明,就不應該急著把一行看似正確的程式塞進舊系統。
下一篇,我們會把今天的流程直接帶進下一個維護現場:從畫面上一個「內部預算」選項開始,不先相信元件名稱,也不先猜資料表,而是把畫面設定、事件、Dataset 與 AP 交給 AI 建立第一版資料流。
接著再用實際查詢結果逐層驗證,看看這個看似普通的選項,最後究竟把資料帶往哪裡。今天學的是如何追一個「沒有存進去的值」;下一篇,則要追一個「到底從哪裡來的值」。