事情經過
我寫了一支自動化程式,用來處理工程專案的文件組裝。其中一個功能是從來源資料夾搜尋材質證明 PDF,按照批號配對後放進對應的檔案包裡。
同一個批號可能存在多個版本的材證檔案,所以我設計了一個規則:從檔名中的日期判斷版本,取最新的那個。
這個邏輯在正常情況下完全沒問題——新版本取代舊版本,天經地義。程式跑了好幾個月,處理過上百個檔案包,從來沒出事。
直到今天,業主打電話來問:「你們放的材證等級不對。」
發生了什麼
某個批號同時存在兩份材證:
- 一份是正確的檢驗證書(第三方認證),檔名日期較早
- 一份是較低等級的測試報告(供應商自簽),檔名日期較新
那份錯的檔案,是檔案管理人員歸檔時放進 archive 資料夾的。問題是,我的程式用 Python 的 os.walk 遞迴搜尋整個來源目錄——包括 archive。
程式忠實地執行了「取最新」的規則,選了日期較新但內容錯誤的那份。然後這個錯誤被複製到了三個不同的儲存位置,影響了七十幾個 PDF。
邏輯有錯嗎?
客觀來說,沒有。
「同一份文件有多個版本,取最新的」是檔案管理的基本共識。版本控制系統、檔案同步工具、備份軟體,全都是這個邏輯。在正常的資料流中,新版本就是應該取代舊版本。
問題出在搜尋範圍,不是選擇邏輯。
archive 資料夾的語意是「不再使用」,但程式不知道這個語意。對 os.walk 來說,00 跟其他資料夾沒有任何區別——它就是一個子目錄,裡面有 PDF,符合條件就抓。
修法
兩行:
for root, dirs, files in os.walk(source_folder):
dirs[:] = [d for d in dirs if d not in ('00', 'archive')]
讓 os.walk 不再走進 archive 資料夾。邏輯本身不用改,只是補上搜尋範圍的語意邊界。
反思
這件事讓我想到一個更普遍的問題:當你的自動化邏輯建立在一個合理的假設上,而這個假設被資料端的意外打破時,錯的是誰?
答案是:沒有人「錯」。
- 「取最新」是合理的預設
- 把舊檔案放進 archive 是正確的檔案管理
- 但這兩件事碰在一起,就炸了
而且事後才知道,還有第三層:資訊不對稱。負責檔案管理的人,直到事發當天才知道有自動化程式在掃描那個目錄。在他的認知裡,把舊檔案丟進 archive 就是「收好了」——因為在人工作業的世界裡,沒有人會去 archive 拿檔案來用。他不知道有一支程式會走進每一個子資料夾。同樣地,我也不知道他會在 archive 裡留存同批號的舊檔案。
兩邊各自做了合理的事,但因為不了解對方的運作方式,就撞在一起了。
這種 bug 最難預防,因為每一個獨立的決策都是對的,問題出在它們的交集。你不可能為每一個尚未發生的邊界條件都寫防禦邏輯,那樣程式會變得不可維護。
能做的是:在事後,把這個語意邊界補上,然後記住一件事——
os.walk 不懂「archive」是什麼意思。你的程式碼也不懂。資料夾結構的語意只存在於人的腦袋裡,除非你明確告訴程式。