本篇是故事四的「踩雷」篇。
本篇要回答:所有工具都報告成功、程式正常啟動,為什麼查詢結果永遠是空的?
一次例行的程式碼整理,跑了 lint 與自動修正。表面證據漂亮得無可挑剔:lint 通過、格式符合規範、程式正常啟動、資料庫連線正常、查詢不拋任何例外。
實際結果只有一個地方不對:一條以布林欄位過濾的 ORM 查詢,從此永遠回傳空集合——資料庫裡明明躺著大量符合條件的資料。
(去識別化與物證聲明:當時的 Git Diff、工具名稱、版本與規則編號已不在手邊——這件事本身就是 Day 17 的主題。因此本故事刻意不寫死修改方向是把哪種寫法改成哪種寫法;沒有物證就把方向寫死,等於用虛構冒充回憶。機制層面的示範一律使用可執行驗證的通用範例,見 Day 18。)
當時對自動修正的信任建立在一條推理上:工具是靜態分析專家,它建議的寫法一定至少「等價」。這條推理在一般 Python 程式裡多半成立,於是我把「多半成立」升級成了「必然成立」,並且跳過了 review 那份自動產生的 diff——反正工具不會錯。
查詢壞掉之後,第一輪懷疑全部射向別處:資料是不是被清了?連線是不是指到別的庫?快取是不是舊的?沒有例外、沒有紅字,唯一的症狀是「結果為空」,而空集合是合法的查詢結果——系統連撒謊都不必,它只是安靜地正確執行了一條語意已經不同的查詢。
止血的轉折點是一個換位:不再問「哪裡壞了」,改問「這條查詢送到資料庫時長什麼樣子」。把 ORM 實際產生的 SQL 印出來,跟事故前的版本對照——條件子句不一樣了。工具改寫的是 Python 表面上的布林比較,但那行程式不是普通的布林運算,它是 ORM 的查詢表達式(expression),改寫前後對 Python 等價,對 ORM 不等價。
區分證據等級。已確認事實:lint/自動修正全綠與查詢為空可以同時成立(機制於 Day 18 以可執行範例驗證)。已不可考:當時的確切 diff 與規則——列入 Day 17 的證據保存教訓。
本篇結論:
工具完成自己的規則檢查,不代表程式仍然完成原本的業務查詢。
下一篇(Day 17)先處理最痛的自我抓包:想寫這篇復盤時,我發現自己拿不出 diff——回憶只能當線索,物證才能定案。