前一篇提到,在企業 Legacy System 裡,AI 最危險的地方不是完全答錯,而是只看到局部資訊,卻很有自信地說:「這樣改應該就可以了。」
知道 Context 很重要之後,下一個問題馬上就來了:
面對一支超過一萬行的 Delphi 程式,我到底該先把什麼交給 AI?
最直覺的做法,是把整支 .pas、整個專案,甚至資料庫結構全部丟進去,再請 AI 自己研究。看起來資料越多,答案應該越準;實際上卻常常相反。
當資訊一次湧入太多時,AI 可能抓錯重點、忽略真正的事件入口,或把名稱相似但用途不同的 Dataset 混在一起。更麻煩的是,企業專案裡還可能包含客戶名稱、帳號、連線資訊與實際業務資料,不能為了除錯方便就整包上傳。
所以我現在不會先問:「要貼多少程式碼?」
而會先問:
AI 要回答這個問題,還缺少哪一種資訊?
以 Delphi 表單問題來說,我通常會先準備六類材料:需求背景、錯誤現象、.pas、.dfm、資料表與欄位的用途,以及資料異動的交易範圍。它們各自回答不同的問題,少了任何一塊,都可能讓推論走偏。
程式碼只能告訴 AI「現在怎麼做」,不一定能告訴它「原本應該怎麼做」。
例如使用者回報:
按下儲存後金額沒有更新。
這句話仍然不夠。AI 不知道金額應該在何時重算、哪些項目要納入,也不知道儲存失敗時是否可以保留部分結果。
我會把需求改寫成可以驗證的描述:
這些內容不是漂亮的需求文件,卻先替 AI 畫出「正確行為」的邊界。否則它可能只幫忙補一行加總,卻完全沒注意到計算時機、資料範圍與失敗處理。
錯誤截圖的價值不只是那一行錯誤訊息。
一張完整的畫面通常還能提供:
如果只貼出「List index out of bounds」,AI 只能列出一長串通用原因;若同時說明「刪除最後一筆後,程式仍嘗試存取目前列」,推論範圍就會縮小很多。
不過,上傳前仍要先處理敏感資訊。實際的公司名稱、客戶代號、專案編號、使用者帳號與金額,可以遮蔽或換成測試資料。錯誤現象要保留,真實身分不必陪它一起上鏡。
.pas:讓 AI 看見程式行為Delphi 的 .pas 是主要程式碼檔,包含表單類別、事件處理、函式與商業邏輯。當問題發生在按鈕、資料集事件或計算流程時,它通常是最主要的材料。
但我不會一開始就貼完整的一萬行,而是先從「事件入口」開始。
假設問題發生在儲存按鈕,我會先提供類似下面的匿名化片段:
procedure TOrderForm.SaveButtonClick(Sender: TObject);
begin
SaveDetailData;
RecalculateTotal;
ShowMessage('儲存完成');
end;
接著再補上這三個呼叫相關的函式,而不是把所有查詢、列印、權限控制和其他按鈕事件一起丟進去。
在提供 .pas 時,我會特別保留:
OnClick、AfterScroll、BeforePost。Open、Close、Edit、Post、ApplyUpdates 等會改變資料狀態的操作。相反地,與問題無關的報表格式、畫面美化與其他模組,可以暫時拿掉。目的不是隱瞞資訊,而是先讓事件因果關係浮出來。
.dfm:讓 AI 知道畫面實際綁了什麼只提供 .pas,有時候仍然不夠。
Delphi 表單的元件設定與事件綁定通常存放在 .dfm。程式裡雖然有一個 SaveButtonClick,但究竟是哪個按鈕觸發它?某個欄位綁定哪個 DataSource?Dataset 的 ProviderName、CommandText 或參數是什麼?這些資訊可能都藏在 .dfm。
例如:
object SaveButton: TButton
Caption = '儲存'
OnClick = SaveButtonClick
end
object DetailSource: TDataSource
DataSet = DetailData
end
.pas 告訴 AI「按下後做了什麼」,.dfm 則補上「畫面上的誰觸發它,以及元件彼此如何連接」。
這在 Delphi 特別重要。很多問題並不是函式本身寫錯,而是事件重複綁定、DataSource 指錯 Dataset,或元件屬性與程式假設不一致。如果沒有 .dfm,AI 只能猜測畫面配置;猜得再有禮貌,終究還是在猜。
就算 AI 已經拿到 .pas 與 .dfm,它仍可能不知道資料真正代表什麼。
Legacy System 經常使用縮寫、流水號式欄位或沿用多年的狀態碼。人類維護者可能早已知道某個欄位代表「整張單據狀態」,但 AI 只看名稱,很容易把它誤認為「目前簽核關卡狀態」。兩者都叫狀態,修改後的後果卻完全不同。
例如,以下程式碼雖然不複雜,但如果沒有欄位說明,AI 仍然無法判斷條件是否合理:
if SignData.FieldByName('DOCUMENT_STATUS').AsString = 'PROCESSING' then
Exit;
AI 至少還需要知道:
DOCUMENT_STATUS 是整張單據狀態,還是單一簽核關卡狀態?PROCESSING 代表簽核中、資料處理中,還是暫存中?因此,我會再附上一份簡短的欄位字典:
| 匿名化欄位 | 用途 | 範例狀態值 | 主要規則 |
|---|---|---|---|
DOCUMENT_ID |
識別同一張業務單據 | 一般字串 | 同一張單據的各流程關卡使用相同值 |
STEP_NUMBER |
表示流程關卡順序 | 一般整數 | 數字越小,代表流程越前面 |
STEP_STATUS |
記錄單一關卡狀態 | WAITING、COMPLETED |
不等同於整張單據的狀態 |
DOCUMENT_STATUS |
記錄整張單據狀態 | DRAFT、PROCESSING、RETURNED、COMPLETED |
由整體流程更新 |
EDIT_LOCK |
表示業務資料是否暫停修改 | UNLOCKED、LOCKED |
進入流程後鎖定,退回後依規則解除 |
上表中的欄位名稱與狀態值都是為文章重新設計的虛構資料,不對應實際公司的資料庫結構。使用完整單字而不是沿用真實代碼,一方面能保護內部設計,另一方面也能讓讀者理解案例的角色分工。
除了逐欄解釋,還要說明欄位之間的關係。例如:
DOCUMENT_STATUS 表示整張單據的狀態。
STEP_STATUS 表示單一流程關卡的狀態,兩者不可混用。
當單據退回時:
1. 指定關卡之後的 STEP_STATUS 要回到 WAITING。
2. DOCUMENT_STATUS 要改為 RETURNED。
3. 相關業務資料的 EDIT_LOCK 要依規則改為 UNLOCKED。
匿名化也不能只把原始欄位改成 FIELD_A、FIELD_B。這雖然藏住了名稱,卻連欄位意義也一起藏起來。比較好的方式,是改成具有相同角色、但不暴露真實設計的虛構名稱,例如 DOCUMENT_STATUS、STEP_STATUS 與 EDIT_LOCK。
在 Delphi 企業系統中,看到 Post 或 Delete,不能立刻判定資料已經永久寫入資料庫。
以 TClientDataSet 為例,幾個動作代表的層次可能完全不同:
Post:把目前編輯內容寫入 ClientDataSet,資料可能仍只存在用戶端。Delete:把目前資料標示為待刪除,不一定已經刪除資料庫紀錄。ApplyUpdates:把累積的異動送往 Provider、AP Server 或資料庫。Commit:確認資料庫交易中的異動。Rollback:交易失敗時撤銷尚未提交的異動。例如:
DetailData.Edit;
DetailData.FieldByName('QUANTITY').AsFloat := NewQuantity;
DetailData.Post;
// Post 可能只完成 ClientDataSet 內部異動
DetailData.ApplyUpdates(0);
// 是否真正 Commit,仍要確認 AP、Provider 或資料庫端的交易控制
因此,AI 除了要看事件與 Dataset 操作,還需要知道:
ClientDataSet → Provider → AP Server。Post 後是否立即執行 ApplyUpdates。ApplyUpdates。StartTransaction 位於 Delphi、AP、預存程序或資料庫哪一層。Rollback。UPDLOCK 或 HOLDLOCK 等鎖定機制。尤其要注意,多次 ApplyUpdates 不一定屬於同一個資料庫交易:
// 以下名稱皆為匿名化範例
WorkflowData.ApplyUpdates(0); // 流程資料寫入成功
DocumentData.ApplyUpdates(0); // 業務單據更新失敗
如果這兩次更新沒有共同的 Transaction,可能出現第一步已成功、第二步失敗的「半完成」狀態。此時問題不只是畫面沒有更新,而是資料庫中的兩組資料已經不一致。
提供給 AI 時,可以補上一段交易背景:
交易背景:
1. 此功能會依序更新流程資料與業務單據。
2. 兩個 ClientDataSet 目前分別執行 ApplyUpdates。
3. Delphi 前端沒有呼叫 StartTransaction。
4. 是否由 AP 或資料庫建立共同 Transaction,仍待確認。
5. 請先判斷可能產生的半提交風險,不要假設兩次 ApplyUpdates
一定屬於同一個交易。
如果目前只能從程式註解得知「此段操作包在 Transaction 中」,也不能直接把註解當成已證實的事實。仍應追查實際的 StartTransaction、Commit、Rollback,或後端預存程序的交易範圍。否則人類寫下的過期註解,也可能成為餵給 AI 的錯誤 Context。
假設現在遇到的問題是:刪除最後一筆明細後,程式出現存取錯誤。
我不會只貼出整支表單程式並說「幫我仔細檢查」,而會先整理成以下內容。
功能:刪除訂單明細
預期結果:
1. 使用者確認後刪除目前明細。
2. 若刪除後已無資料,畫面清空並結束處理。
3. 不可再讀取已不存在的目前列。
重現步驟:
1. 開啟一張只有一筆明細的單據。
2. 選取該筆明細。
3. 按下刪除。
實際結果:
資料刪除後出現 Access Violation。
.pas 的事件入口與相關函式procedure TOrderForm.DeleteButtonClick(Sender: TObject);
begin
if DetailData.IsEmpty then
Exit;
if MessageDlg('確定刪除目前明細?', mtConfirmation,
[mbYes, mbNo], 0) <> mrYes then
Exit;
DetailData.Delete;
// 問題可能發生在這裡:刪除最後一筆後仍讀取目前列
RefreshSummary(DetailData.FieldByName('ITEM_NO').AsString);
end;
.dfm 中的相關元件object DeleteButton: TButton
Caption = '刪除'
OnClick = DeleteButtonClick
end
object DetailSource: TDataSource
DataSet = DetailData
end
資料異動背景:
1. DetailData 是 ClientDataSet。
2. Delete 後尚未確認是否立即執行 ApplyUpdates。
3. 是否由 AP 或資料庫控制 Transaction,目前資訊不足。
4. 請勿假設 Delete 已經永久刪除資料庫紀錄。
5. 若判斷需要交易資訊,請指出還需要查看哪一段程式。
請先不要直接改寫整支程式。
回答時請分成以下三部分:
1. 已知事實:
只能列出目前程式碼、錯誤畫面與需求明確提供的內容。
2. 可能原因:
每項原因都要標示為「推測」,並說明判斷依據。
3. 尚缺資訊:
明確列出還需要查看的 Procedure、事件、全域變數、
Dataset 設定、DFM 元件或交易程式碼。
如果上述程式碼不足以判定,請直接回答「資訊不足」,
並明確列出還需要查看哪一個 Procedure、全域變數或元件設定。
不要假設未提供的元件設定、資料庫交易或程式行為,
也不要先產生完整修改版本。
這樣做的重點,不是發明一條神奇 Prompt,而是讓 AI 能區分三件事:
Delete 後是否觸發其他 Dataset 事件,或 RefreshSummary 是否又重新查詢資料。只要這三者沒有混在一起,AI 的答案就比較不容易從「合理推測」瞬間變成「自信定案」。
防禦性 Prompt 不能保證 AI 不會答錯,但可以迫使它把推測與證據分開。工程師看到「尚缺資訊」後,也比較知道下一步要找哪支 Procedure、哪個 Dataset 事件,或哪一層的交易控制,而不是直接接受一份看似完整的修改版本。
所謂不要一次丟整個專案,並不是程式碼越少越好。
如果只貼一個事件,卻省略它呼叫的函式、Dataset 狀態與 .dfm 綁定,AI 同樣無法判斷。真正的目標是:
先提供足以建立第一個假設的材料,再依照 AI 指出的缺口補充證據。
這比較像醫生問診,而不是把整本病歷往桌上一放,期待對方在三秒內找到十年前哪一次感冒埋下了伏筆。
對 Delphi Legacy System 而言,我目前會先按這個順序整理:
.pas 找到最接近使用者操作的事件入口。.dfm 補足元件、事件與資料來源綁定。Post、ApplyUpdates、Transaction 與 Lock 的所在層次。這份順序不保證 AI 一次答對,但能讓錯誤推論更早被看見,也讓工程師知道下一步該查哪裡。
面對一萬行 Delphi 程式,第一步不是把一萬行全部交給 AI,而是先建立一個清楚的問題入口。
今天整理出的六類 Context,各自負責不同任務:
.pas:呈現事件、函式與資料操作流程。.dfm:補足元件設定、事件綁定與資料來源關係。AI 不會自動知道一套企業系統的歷史,也不會憑空理解某個欄位背後的商業意義。我們提供的 Context 越接近真正的因果鏈,它就越可能成為可靠的閱讀助手,而不是一本很會即興創作的錯誤訊息百科。
下一篇,我會進一步處理今天留下的問題:
程式彼此呼叫、事件互相觸發,相關檔案越找越多時,該如何整理「最小但足夠」的 Context,讓 AI 看得懂,又不被整個專案淹沒?
.pas 用來追行為,.dfm 用來確認元件與事件綁定,兩者不能互相取代。Post、Delete、ApplyUpdates 與資料庫 Commit 不一定是同一件事,必須確認真正的交易邊界。